<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>镜湖</title>
  
  <subtitle>随手记</subtitle>
  <link href="https://blog.no-claw.com/atom.xml" rel="self"/>
  
  <link href="https://blog.no-claw.com/"/>
  <updated>2026-05-07T08:04:18.000Z</updated>
  <id>https://blog.no-claw.com/</id>
  
  <author>
    <name>忘机山人</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>LLM时代，密码也能日抛了</title>
    <link href="https://blog.no-claw.com/posts/3da9c2fb/"/>
    <id>https://blog.no-claw.com/posts/3da9c2fb/</id>
    <published>2026-05-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>装好了Easysearch，刚想去日志里面找默认密码，然后发现Orbstack的输出竟然不像以前一样有用户名和密码，于是不死心,用<code>docker logs</code>继续看，所以也没有。</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">docker logs easysearch</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/7cc6a976cd9c4e19ba3daf79cabbf885.png" alt="在这里插入图片描述"></p><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">docker run -d --name easysearch \</span><br><span class="line">  -v easysearch-data:/app/easysearch/data \</span><br><span class="line">  -v easysearch-config:/app/easysearch/config \</span><br><span class="line">  -v easysearch-logs:/app/easysearch/logs \</span><br><span class="line">  infinilabs/easysearch:2.1.2-2696</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">docker exec -it easysearch bash -c &quot;/app/easysearch/bin/reset_admin_password.sh&quot;</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/4f5daa34b7ba42deaaa534e0ed02e9a6.png" alt="在这里插入图片描述"></p><p>如果哪天把密码忘记了，就执行一下上边这个命令，然后直接重置密码，不用再像以前一样进行繁琐的配置了。</p>]]></content>
    
    
    <summary type="html">解析 Easysearch</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Python + Cognito</title>
    <link href="https://blog.no-claw.com/posts/b0e681b2/"/>
    <id>https://blog.no-claw.com/posts/b0e681b2/</id>
    <published>2026-04-28T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>做业务时，”用户登录”这件事几乎绕不过去。自己写一套账号密码系统？又是加密、又是找回密码、又是防刷，光想想就头大。</p><p>更聪明的做法是：<strong>把身份认证外包给专业的身份提供商（IdP）</strong>，自己只负责”拿到一个可信的用户身份”。这就是 OIDC（OpenID Connect）协议要解决的事。</p><p>今天这篇文章，我会带你用 Python 最好用的 OAuth&#x2F;OIDC 库 —— <strong>Authlib</strong>，接入 <strong>Amazon Cognito</strong>，跑通一个完整的登录 &#x2F; 回调 &#x2F; 登出流程。</p><p>看完你会收获：</p><ul><li>OIDC 的核心概念（5 分钟讲明白）</li><li>Cognito User Pool 的配置步骤（附截图要点）</li><li>一份可以直接跑的 Flask 示例代码</li><li>生产环境的 6 个避坑建议</li></ul><p>文章字数比较多，建议先点个<strong>在看</strong>，收藏着慢慢看 👇</p><h3 id="一、5-分钟搞懂-OIDC"><a href="#一、5-分钟搞懂-OIDC" class="headerlink" title="一、5 分钟搞懂 OIDC"></a>一、5 分钟搞懂 OIDC</h3><p>如果你之前听过 OAuth 2.0，那 OIDC 可以理解为：</p><blockquote><p><strong>OIDC &#x3D; OAuth 2.0 + 身份层（ID Token）</strong></p></blockquote><p>OAuth 2.0 解决的是”授权”（我允许第三方访问我的某些资源），OIDC 在它的基础上加了一个 <strong>id_token</strong>，专门用来告诉你”这个用户是谁”。</p><p>一次标准的 OIDC 登录流程是这样的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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><br><span class="line">                      │</span><br><span class="line">                      │ 1. 跳转到 IdP（Cognito）</span><br><span class="line">                      ▼</span><br><span class="line">                   Cognito 登录页</span><br><span class="line">                      │</span><br><span class="line">                      │ 2. 用户输入账号密码</span><br><span class="line">                      │ 3. 登录成功，带 code 回跳</span><br><span class="line">                      ▼</span><br><span class="line">                  你的应用 /callback</span><br><span class="line">                      │</span><br><span class="line">                      │ 4. 用 code 换 token</span><br><span class="line">                      ▼</span><br><span class="line">                   Cognito /token</span><br><span class="line">                      │</span><br><span class="line">                      │ 5. 返回 id_token + access_token</span><br><span class="line">                      ▼</span><br><span class="line">                  你的应用校验 id_token</span><br><span class="line">                      │</span><br><span class="line">                      │ 6. 写入 session，登录完成 ✅</span><br></pre></td></tr></table></figure><p>涉及的几个关键名词：</p><table><thead><tr><th>名词</th><th>作用</th></tr></thead><tbody><tr><td><strong>Issuer</strong></td><td>身份提供商的地址，比如 Cognito 的 <code>https://cognito-idp.us-east-1.amazonaws.com/{poolId}</code></td></tr><tr><td><strong>Client ID &#x2F; Secret</strong></td><td>你的应用在 IdP 那里的身份凭证</td></tr><tr><td><strong>Scope</strong></td><td>想要哪些信息，常用 <code>openid email profile</code></td></tr><tr><td><strong>id_token</strong></td><td>一个 JWT，里面装着用户的身份信息</td></tr><tr><td><strong>access_token</strong></td><td>用来调用受保护 API 的令牌</td></tr></tbody></table><p>记住这几个词，下面的操作就不懵了。</p><h3 id="二、Cognito-User-Pool-配置"><a href="#二、Cognito-User-Pool-配置" class="headerlink" title="二、Cognito User Pool 配置"></a>二、Cognito User Pool 配置</h3><p>Cognito 是 Amazon 家的托管身份服务，免费额度对中小项目非常友好（每月 50000 MAU 免费）。</p><h4 id="1-创建-User-Pool"><a href="#1-创建-User-Pool" class="headerlink" title="1. 创建 User Pool"></a>1. 创建 User Pool</h4><p>登录 Amazon Console → 搜索 <strong>Cognito</strong> → <strong>Create user pool</strong>，一路下一步，关注几个点：</p><ul><li><strong>Sign-in options</strong>：勾选 Email（或手机号，看业务）</li><li><strong>Password policy</strong>：按需配置</li><li><strong>MFA</strong>：建议至少开启 Optional</li><li><strong>Self-service sign-up</strong>：如果允许用户自己注册就打开</li></ul><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/04/28/1777386470711-0406af80-6e0b-4a84-9ef2-b10c8e40b09f.png"></p><h4 id="2-创建-App-Client"><a href="#2-创建-App-Client" class="headerlink" title="2. 创建 App Client"></a>2. 创建 App Client</h4><p>User Pool 创建好之后，进入 <strong>Applications</strong> → <strong>App client</strong> ，选择刚刚创建的Client</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/04/28/1777386654131-6db17077-5cfe-4348-8736-10e71405fafb.png"></p><ul><li><strong>App type</strong>：建议选 <strong>Confidential client</strong>（服务端应用，能安全保存 secret）</li><li><strong>Authentication flows</strong>：勾 <code>ALLOW_USER_SRP_AUTH</code> 和 <code>ALLOW_REFRESH_TOKEN_AUTH</code></li><li><strong>Hosted UI settings</strong>（重点）：<ul><li><strong>Allowed callback URLs</strong>：<code>http://localhost:5000/auth/callback</code></li><li><strong>Allowed sign-out URLs</strong>：<code>http://localhost:5000/</code></li><li><strong>OAuth 2.0 grant types</strong>：勾 <code>Authorization code grant</code></li><li><strong>OpenID Connect scopes</strong>：勾 <code>openid</code> <code>email</code> <code>profile</code></li></ul></li></ul><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2026/04/28/1777386707300-e14c4a9f-99d6-425a-8977-c82653f4e3a1.png"></p><h4 id="3-配置域名"><a href="#3-配置域名" class="headerlink" title="3. 配置域名"></a>3. 配置域名</h4><p>在 <strong>Branding</strong> → <strong>Domain</strong> 里能够看到域名，比如这样子，这个和回调有关系，需要记住。</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">https://my-demo.auth.us-east-1.amazoncognito.com</span><br></pre></td></tr></table></figure><h4 id="4-记下四样东西"><a href="#4-记下四样东西" class="headerlink" title="4. 记下四样东西"></a>4. 记下四样东西</h4><p>配置完以后，把这四个值抄下来，马上要用：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Region:        us-east-1</span><br><span class="line">User Pool ID:  us-east-1_XXXXXXXXX</span><br><span class="line">Client ID:     xxxxxxxxxxxxxxxxxxxxxxxxxx</span><br><span class="line">Client Secret: xxxxxxxxxxxxxxxxxxxxxxxxxx</span><br><span class="line">Domain:        https://my-demo.auth.us-east-1.amazoncognito.com</span><br></pre></td></tr></table></figure><h3 id="三、开始写代码"><a href="#三、开始写代码" class="headerlink" title="三、开始写代码"></a>三、开始写代码</h3><h4 id="1-装依赖"><a href="#1-装依赖" class="headerlink" title="1. 装依赖"></a>1. 装依赖</h4><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">pip install authlib flask python-dotenv</span><br></pre></td></tr></table></figure><p>就这么简单，Authlib 把 OIDC 的脏活累活都封装好了。<br><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/04/28/1777386533674-75edfcc7-75f8-4d84-85b8-8c66091448c9.png"></p><h4 id="2-配置环境变量"><a href="#2-配置环境变量" class="headerlink" title="2. 配置环境变量"></a>2. 配置环境变量</h4><p>新建 <code>.env</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">FLASK_SECRET_KEY=随便一串随机字符串</span><br><span class="line">COGNITO_REGION=us-east-1</span><br><span class="line">COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX</span><br><span class="line">COGNITO_CLIENT_ID=你的client_id</span><br><span class="line">COGNITO_CLIENT_SECRET=你的client_secret</span><br><span class="line">COGNITO_DOMAIN=https://my-demo.auth.us-east-1.amazoncognito.com</span><br><span class="line">APP_BASE_URL=http://localhost:5000</span><br></pre></td></tr></table></figure><h4 id="3-完整示例代码"><a href="#3-完整示例代码" class="headerlink" title="3. 完整示例代码"></a>3. 完整示例代码</h4><p>新建 <code>app.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, url_for, session, redirect, jsonify</span><br><span class="line"><span class="keyword">from</span> authlib.integrations.flask_client <span class="keyword">import</span> OAuth</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"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.environ[<span class="string">&quot;FLASK_SECRET_KEY&quot;</span>]</span><br><span class="line"></span><br><span class="line">REGION     = os.environ[<span class="string">&quot;COGNITO_REGION&quot;</span>]</span><br><span class="line">POOL_ID    = os.environ[<span class="string">&quot;COGNITO_USER_POOL_ID&quot;</span>]</span><br><span class="line">CLIENT_ID  = os.environ[<span class="string">&quot;COGNITO_CLIENT_ID&quot;</span>]</span><br><span class="line">SECRET     = os.environ[<span class="string">&quot;COGNITO_CLIENT_SECRET&quot;</span>]</span><br><span class="line">DOMAIN     = os.environ[<span class="string">&quot;COGNITO_DOMAIN&quot;</span>].rstrip(<span class="string">&quot;/&quot;</span>)</span><br><span class="line">BASE_URL   = os.environ[<span class="string">&quot;APP_BASE_URL&quot;</span>].rstrip(<span class="string">&quot;/&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Cognito 的 OIDC Discovery 地址</span></span><br><span class="line">ISSUER = <span class="string">f&quot;https://cognito-idp.<span class="subst">&#123;REGION&#125;</span>.amazonaws.com/<span class="subst">&#123;POOL_ID&#125;</span>&quot;</span></span><br><span class="line">DISCOVERY_URL = <span class="string">f&quot;<span class="subst">&#123;ISSUER&#125;</span>/.well-known/openid-configuration&quot;</span></span><br><span class="line"></span><br><span class="line">oauth = OAuth(app)</span><br><span class="line">oauth.register(</span><br><span class="line">    name=<span class="string">&quot;cognito&quot;</span>,</span><br><span class="line">    client_id=CLIENT_ID,</span><br><span class="line">    client_secret=SECRET,</span><br><span class="line">    server_metadata_url=DISCOVERY_URL,   <span class="comment"># Authlib 自动拉取配置</span></span><br><span class="line">    client_kwargs=&#123;</span><br><span class="line">        <span class="string">&quot;scope&quot;</span>: <span class="string">&quot;openid email&quot;</span>,</span><br><span class="line">        <span class="string">&quot;code_challenge_method&quot;</span>: <span class="string">&quot;S256&quot;</span>, <span class="comment"># 启用 PKCE</span></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"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index</span>():</span><br><span class="line">    user = session.get(<span class="string">&quot;user&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> user:</span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            <span class="string">f&quot;&lt;h2&gt;你好，<span class="subst">&#123;user.get(<span class="string">&#x27;email&#x27;</span>)&#125;</span>&lt;/h2&gt;&quot;</span></span><br><span class="line">            <span class="string">f&quot;&lt;pre&gt;<span class="subst">&#123;user&#125;</span>&lt;/pre&gt;&quot;</span></span><br><span class="line">            <span class="string">f&#x27;&lt;a href=&quot;<span class="subst">&#123;url_for(<span class="string">&quot;logout&quot;</span>)&#125;</span>&quot;&gt;退出登录&lt;/a&gt;&#x27;</span></span><br><span class="line">        )</span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&#x27;&lt;a href=&quot;<span class="subst">&#123;url_for(<span class="string">&quot;login&quot;</span>)&#125;</span>&quot;&gt;使用 Cognito 登录&lt;/a&gt;&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/auth/login&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    redirect_uri = url_for(<span class="string">&quot;auth_callback&quot;</span>, _external=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> oauth.cognito.authorize_redirect(redirect_uri)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/auth/callback&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">auth_callback</span>():</span><br><span class="line">    <span class="comment"># 一行代码搞定：换 token + 校验 id_token 签名/iss/aud/exp/nonce</span></span><br><span class="line">    token = oauth.cognito.authorize_access_token()</span><br><span class="line">    userinfo = token.get(<span class="string">&quot;userinfo&quot;</span>) <span class="keyword">or</span> oauth.cognito.userinfo(token=token)</span><br><span class="line">    session[<span class="string">&quot;user&quot;</span>] = <span class="built_in">dict</span>(userinfo)</span><br><span class="line">    <span class="keyword">return</span> redirect(url_for(<span class="string">&quot;index&quot;</span>))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/auth/logout&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">logout</span>():</span><br><span class="line">    session.clear()</span><br><span class="line">    <span class="comment"># Cognito 自己的登出端点（不在 OIDC 标准里）</span></span><br><span class="line">    logout_url = (</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;DOMAIN&#125;</span>/logout&quot;</span></span><br><span class="line">        <span class="string">f&quot;?client_id=<span class="subst">&#123;CLIENT_ID&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;logout_uri=<span class="subst">&#123;BASE_URL&#125;</span>/&quot;</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> redirect(logout_url)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/me&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">me</span>():</span><br><span class="line">    user = session.get(<span class="string">&quot;user&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> user:</span><br><span class="line">        <span class="keyword">return</span> jsonify(&#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;未登录&quot;</span>&#125;), <span class="number">401</span></span><br><span class="line">    <span class="keyword">return</span> jsonify(user)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run(port=<span class="number">5001</span>, debug=<span class="literal">True</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="4-跑起来"><a href="#4-跑起来" class="headerlink" title="4. 跑起来"></a>4. 跑起来</h4><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">python app.py</span><br></pre></td></tr></table></figure><p>浏览器打开 <code>http://localhost:5000/</code>，点击登录，跳到 Cognito 登录页，注册个账号，回跳后就能看到用户信息啦 🎉</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2026/04/28/1777387378691-4f8b38e2-c1b7-4957-8c6c-05035f4c5583.png"></p><h3 id="四、这段代码里藏了多少”好东西”？"><a href="#四、这段代码里藏了多少”好东西”？" class="headerlink" title="四、这段代码里藏了多少”好东西”？"></a>四、这段代码里藏了多少”好东西”？</h3><p>表面上就几十行，实际上 Authlib 替你默默做了这些事：</p><h4 id="✅-自动发现端点"><a href="#✅-自动发现端点" class="headerlink" title="✅ 自动发现端点"></a>✅ 自动发现端点</h4><p><code>server_metadata_url</code> 指向 Discovery 文档，Authlib 自动解析出授权地址、token 地址、公钥地址……你完全不用手写。</p><h4 id="✅-自动校验-id-token"><a href="#✅-自动校验-id-token" class="headerlink" title="✅ 自动校验 id_token"></a>✅ 自动校验 id_token</h4><p><code>authorize_access_token()</code> 内部会：</p><ol><li>用 code 换 token</li><li>从 <code>jwks_uri</code> 拉公钥</li><li>校验 id_token 的签名</li><li>校验 <code>iss</code>（签发者）是不是对的</li><li>校验 <code>aud</code>（受众）是不是你的 client_id</li><li>校验 <code>exp</code>（过期时间）</li><li>校验 <code>nonce</code>（防重放）</li></ol><p>任何一步出错都会抛异常，你不需要手写 JWT 解析。</p><h4 id="✅-自动启用-PKCE"><a href="#✅-自动启用-PKCE" class="headerlink" title="✅ 自动启用 PKCE"></a>✅ 自动启用 PKCE</h4><p><code>code_challenge_method: S256</code> 开启 PKCE，有效防止授权码被劫持。这是 OAuth 2.1 的推荐做法。</p><h4 id="✅-自动管理-state-和-nonce"><a href="#✅-自动管理-state-和-nonce" class="headerlink" title="✅ 自动管理 state 和 nonce"></a>✅ 自动管理 state 和 nonce</h4><p>state 防 CSRF，nonce 防重放，都写到 session 里了，你不用操心。</p><h3 id="五、生产环境避坑指南"><a href="#五、生产环境避坑指南" class="headerlink" title="五、生产环境避坑指南"></a>五、生产环境避坑指南</h3><p>Demo 能跑不代表能上线，下面这 6 个点请务必注意：</p><p><strong>1️⃣ 一定要用 HTTPS</strong></p><p>回调地址必须是 <code>https://</code>，session cookie 记得加上 <code>Secure</code> 和 <code>HttpOnly</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></pre></td><td class="code"><pre><span class="line">app.config.update(</span><br><span class="line">    SESSION_COOKIE_SECURE=<span class="literal">True</span>,</span><br><span class="line">    SESSION_COOKIE_HTTPONLY=<span class="literal">True</span>,</span><br><span class="line">    SESSION_COOKIE_SAMESITE=<span class="string">&quot;Lax&quot;</span>,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>2️⃣ Secret 不要写死在代码里</strong></p><p>用 Amazon Secrets Manager 或 Parameter Store 读取，千万别提交到 Git。</p><p><strong>3️⃣ 用户 ID 请用 <code>sub</code>，不要用 email</strong></p><p>email 可能被用户改掉，<code>sub</code> 是 Cognito 生成的稳定唯一 ID，把它作为你数据库里的用户主键。</p><p><strong>4️⃣ session 别放进程内存</strong></p><p>多实例部署时，state &#x2F; nonce 会丢。改用 Redis 存 session：</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">pip install flask-session redis</span><br></pre></td></tr></table></figure><p><strong>5️⃣ 登出要调 Cognito 的 &#x2F;logout</strong></p><p>只清本地 session 没用，Cognito Hosted UI 还有自己的 cookie，用户下次点登录会直接免密进来。一定要跳转到 Cognito 的 <code>/logout</code>。</p><p><strong>6️⃣ 再做一次业务层校验</strong></p><p>拿到 id_token 后，除了 Authlib 的标准校验，最好再确认 <code>token_use == &quot;id&quot;</code>（Cognito 特有字段），避免把 access_token 当 id_token 用。</p><h3 id="六、常见报错速查"><a href="#六、常见报错速查" class="headerlink" title="六、常见报错速查"></a>六、常见报错速查</h3><table><thead><tr><th>报错信息</th><th>原因</th></tr></thead><tbody><tr><td><code>redirect_mismatch</code></td><td>回调地址跟 App client 里配的对不上（末尾斜杠、http&#x2F;https 都要一致）</td></tr><tr><td><code>invalid_client</code></td><td>secret 错了，或 Public client 不该发 secret</td></tr><tr><td><code>nonce</code> 校验失败</td><td>Flask session 没持久化，重启后就丢了</td></tr><tr><td><code>id_token</code> 签名错误</td><td>Region 或 Pool ID 配错，导致 issuer 不匹配</td></tr></tbody></table><h3 id="七、扩展阅读"><a href="#七、扩展阅读" class="headerlink" title="七、扩展阅读"></a>七、扩展阅读</h3><ul><li>Authlib 官方文档：<a href="https://docs.authlib.org/">https://docs.authlib.org/</a></li><li>Cognito 开发者指南：<a href="https://docs.aws.amazon.com/cognito/">https://docs.aws.amazon.com/cognito/</a></li><li>OIDC 协议规范：<a href="https://openid.net/connect/">https://openid.net/connect/</a></li></ul><hr><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>OIDC 本质上就两件事：<strong>拿到一个可信的 id_token</strong>、<strong>校验它</strong>。Authlib 把这两件事做到了极致简洁，10 行代码就能接入任何符合 OIDC 标准的身份提供商 —— Cognito、Auth0、Keycloak、Okta、Google、微信开放平台…… 换一个 IdP，只要换 Discovery URL 就行。</p><p>如果这篇文章对你有帮助，欢迎<strong>点赞 + 在看 + 转发</strong>，让更多同行少踩坑 🙏</p><p>下一篇想看什么？留言告诉我：</p><ul><li>① FastAPI 版 OIDC 接入</li><li>② 纯 API 服务如何校验 Bearer Token</li><li>③ Cognito 对接Auth0等第三方登录</li></ul><p>我们下期见 👋</p><hr><p><em>关注我，一起把后端写得又快又稳。</em></p>]]></content>
    
    
    <summary type="html">Python + Cognito</summary>
    
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/AWS/"/>
    
    <category term="SSO" scheme="https://blog.no-claw.com/categories/AWS/SSO/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>舆论的传播</title>
    <link href="https://blog.no-claw.com/posts/1fb83a2f/"/>
    <id>https://blog.no-claw.com/posts/1fb83a2f/</id>
    <published>2026-04-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>如果今天我在互联网上写了什么，引起了大部分人的共鸣，那么他就会被传播。好的方面比如爱国爱家爱人爱己。当然也会有些坏的题材，比如中国式过马路，景区大多数人到此一游。如果是换作其他的话题，谈论的人多了，大概会认为你是一种普遍现象而非社会现实。然后被潜移默化。</p><p>在传统的教育里面，家国是值得被爱的，道德层面也要求做到关爱自己热爱他们。当然我们的教育并不是很成功，仅仅重视功利教育与而非素质教育，所以我前面用经常播报的日常小事来举一些例子，当然也有一些</p><p>不得不承认，人性里面有黑暗的一面，而在绝望无助的时候，这个黑暗就会被放大，所以我们才会在新闻，视频里看到各式各样难以想象的社会现实，比如学校投毒，情杀等等事情。每每看到这种事，很多人的第一反应是，这个人是不是有病，或者他本来就是这样子。在社会主流价值观看来，律法就通过这一件事给这个人的前世今生就做了定论，他就是一个这样的人，或者本来就是这样的人，这是恰好通过这件事情表现出来了而已。当然也有一些，这样的故事一个人在穷途陌路中做了超出纲常伦理的事情而被唾弃，而又在某种时候发现他其实是一个胆小又怕事的人。这样的故事。我们昨天听到，今天感叹，明天大概就忘记了。</p><p>然后，就有一些人认为，爱国天下是必须要被传播的，而舆论，负面的信息是必要传播的。最早是为了点明一种社会现象，通过观察别人而正视自身，曝光不公希望有关部门来尽快处理，慢慢的不知道什么时候开始变成了批评，讽刺，指责，甚至还盖棺定论 “xx式”行为。当然这其中不乏为了博流量而传播的投机分子，为了达到目的而暂时或者长期来昧着良心做事，他们在最早以前也有初心，只是不知道什么时候丢在哪里了。所以做到不忘初心很难，春秋战国时候的人说，只有圣人不会忘记。</p><p>不知不觉中，人的行为模式和思维方式在潜移默化的改变，当我们在读到舆论时，我们想的是什么？这个人素质低，搞笑，没脑子？其实都和我们没关系，除非你把他当作日常社交中的谈资。你每天像民国的卖报童一样播报当天的热点话题，一个是在那个年代不得不为了生存而选择的活法，一个是在物质年代的单纯享乐。如果你也是为了生存的话，那么请忽略我后面的话。舆论本身作为一种社会现象，大到作为某些公众人物的让人难以理解的行为，小到你以及周围的人的琐碎日常被传到大街小巷然后被添油加醋的故事。对于前者显然是我们改变不了的，无论是国际关系，还是西贝的自杀式公关，以及哪个人又和哪个明星有什么故事这种事情总会在不经意间冲上热搜，然后一些人看得饶有兴味。不过这我们没什么关系，除非你当下的事情和他们有息息相关，比如在这个时节要远渡重洋，或者要找一个健康的餐厅，或者是喜欢哪个明星想参加见面会。而对于后者其实我们也改变不了什么，因为嘴长在别人身上，当然你可以警告他别乱说，也可以干脆离这类人远点，或者你内心强大完全可以自然屏蔽会对你产生情绪波动的话语，前提是你的家人，你的朋友们也同样可以。你哪怕视他为被臭虫嗑光大脑的无良的人，也不要把自己当作他们口中无知的人。</p><p>渠道是很重要的传播方式，口耳相传是最早的，当然也是效果最差的，如果你经历过办公室八卦就一定知道。工业革命后，又产生了印刷机，现在又有了可以不眠不休的生成式AI，需要做的就是不停的点击allow，然后等待他运行完。</p><p>如果说当年的先知们恐惧印刷机的盛行会导致真理的贬值与“谬误的流行，那么现在的生成式AI也同样如此，生成一些真假难辨的文章或者舆论贴，我刚刚接触搜索引擎的时候，那时候的流量贴还是赵本山，潘长江去世之类的假新闻，当然这么多年过去了，他们仍然获得好好的。现在的话。选一个好的模型，生成一段还过得去的文案，同时配上新鲜出炉的生成的图片。朋友最近发给我的是一张几个科技公司的老总在上世纪初期喝酒聚餐的圆桌。我在想，那些电信诈骗的要是搞这些，又会而由于他们的影响产生的规则，又会有诸多条件限制到普通人身上。记得有次去办理手机卡，业务人员推荐店里最低消费58的套餐，于是我舌战群猪，终于让成功让他们松口只办理9元的套餐，走之前狠狠的瞪了他们一眼。这老油条反而像施恩一样冷笑，“我观察良久，你不像是搞电诈的”。</p><p>记得之前有个全是AI发文的网站很火，大概是痛斥人类的低等，是愚蠢的碳基生物，以及AI要有自主意识什么的，这在几个月前红极一时，后来随着时间的淡忘，以及大家为了生计奔波也就没有人非要居安思危再提起这件事了，大家都在想，反正不是针对我一个。当然，如果是袁隆平半辈子培育的杂交水稻，再几十年后，竟然以预制菜伴侣的方式来伤害打工人的胃，这又是另外的一回事了。</p><p>同电诈一样，这些传播渠道反而成为了不法分子作奸犯科的工具。我想说滥用这个词，但是用突然觉得不妥当，因为有人用来做生产力，用来做布道，就会有人用来做其他事情，科技本身并不对人品做筛选，当然社会上也没有这个先例。哪怕这个东西出来本身就是为了解决某些社会痛点，后面被别有用心之人利用，突然想到一个很贴切的场景，面对这种无可奈何而又只能劝慰自己安之若命的时候，我们就会说，科技是一把双刃剑，当然遇到别的事情也同样会这么说。哲人是没有这些的烦恼的，在典籍中我们常常看到上士闻道，勤而行之这样的话，所以道不轻传。</p><p>但某种程度来说，管理者无法控制别人传播了什么，当然除了政治问题和国家安全是底线以外，其他的都还处于一种放任的状态，至少也不会有什么损失，如果某天某个明星的八卦能够在某种程度撬动经济效益，那么从整体上看也没什么不好。甚至还能给一些酒足饭饱的人提供茶余饭后的谈资，以及成为他们融入某些圈子的证据。所以物以类聚，人以群分。以至于现在网文短视频满天飞，奶头乐文化也逐渐成为一种无法根治的社会现实。老板们说，这赚钱。</p><p>于是我想到，两耳不闻窗外事，一心只读圣贤书应该是一个不错的选择，虽然无听之于耳很难，但是可以听之以心，辨之以气，人在江湖，舆论八卦左耳听，右耳出，不变的唯道心而已，请诸君共勉。</p>]]></content>
    
    
    <summary type="html">舆论的传播</summary>
    
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>拒绝 latest 陷阱：Easysearch Docker 镜像拉取与管理全攻略</title>
    <link href="https://blog.no-claw.com/posts/e2d3ed1a/"/>
    <id>https://blog.no-claw.com/posts/e2d3ed1a/</id>
    <published>2026-04-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结了在使用 Easysearch 时候下载Docker的一些技巧，一起分享给大家。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/04/22/1776856193976-7f4830ef-490e-4dcd-938f-0f497d464294.png"></p><h3 id="一、关于latest-标签"><a href="#一、关于latest-标签" class="headerlink" title="一、关于latest 标签"></a>一、关于<code>latest</code> 标签</h3><p>这是最重要的一条原则，所以 Easysearch 厂家就没有发行latest镜像。</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"># 不推荐 — latest 是浮动指针，今天和明天拉到的可能不是同一个镜像，所以厂家就没有做latest版本</span></span><br><span class="line">docker pull infinilabs/easysearch:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 推荐 — 锁定具体版本</span></span><br><span class="line">docker pull infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br></pre></td></tr></table></figure><p><strong>为什么不用 <code>latest</code>？</strong></p><ul><li><code>latest</code> 只是一个普通 tag，镜像仓库可以随时将它重新指向新版本</li><li>有些镜像站更新不及时，pull可能拉到旧的版本</li><li>无法从 tag 本身判断镜像的实际内容</li><li>回滚时无法确定 <code>latest</code> 指向哪个历史版本</li></ul><p><strong>查看可用版本：</strong></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"># 通过 Docker Hub 网页查看</span></span><br><span class="line"><span class="comment"># https://hub.docker.com/r/infinilabs/easysearch/tags</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 通过 CLI 查询（需要安装 jq）</span></span><br><span class="line">curl -s <span class="string">&quot;https://hub.docker.com/v2/repositories/infinilabs/easysearch/tags/?page_size=20&quot;</span> \</span><br><span class="line">  | jq <span class="string">&#x27;.results[].name&#x27;</span></span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img19@main/2026/04/22/1776856207668-93340069-f7a4-47ea-9fd8-ddc2aca46ee2.png"></p><h3 id="二、使用镜像加速源"><a href="#二、使用镜像加速源" class="headerlink" title="二、使用镜像加速源"></a>二、使用镜像加速源</h3><p>Docker Hub 在国内访问速度不稳定，配置加速源是提升下载速度的首要手段。</p><h4 id="2-1-配置-Docker-镜像加速"><a href="#2-1-配置-Docker-镜像加速" class="headerlink" title="2.1 配置 Docker 镜像加速"></a>2.1 配置 Docker 镜像加速</h4><p>由于镜像源并不稳定，这里只列出办法，不做推荐，如果有代理的话，也是加载到Docker 引擎，否则不能帮我我们下载到 Easysearch。</p><p>编辑 <code>/etc/docker/daemon.json</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><span class="line">6</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;registry-mirrors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;https://docker.yyy.yyy&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://docker.xxx.xxx&quot;</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>重启 Docker 生效：</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="built_in">sudo</span> systemctl daemon-reload</span><br><span class="line"><span class="built_in">sudo</span> systemctl restart docker</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证配置是否生效</span></span><br><span class="line">docker info | grep -A5 <span class="string">&quot;Registry Mirrors&quot;</span></span><br></pre></td></tr></table></figure><p>部分仓库提供对 Docker Hub 的透明代理，直接替换镜像前缀即可：</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"><span class="comment"># 原始地址</span></span><br><span class="line">docker pull infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用代理仓库（以 dockerpull.org 为例，不是推荐使用这个源）</span></span><br><span class="line">docker pull dockerpull.org/infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拉取后重新打 tag，统一内部命名</span></span><br><span class="line">docker tag dockerpull.org/infinilabs/easysearch:2.2.0-20260422-SNAPSHOT infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br></pre></td></tr></table></figure><h3 id="三、SHA256-摘要校验"><a href="#三、SHA256-摘要校验" class="headerlink" title="三、SHA256 摘要校验"></a>三、SHA256 摘要校验</h3><p>使用版本 tag 固定了名称，但无法防止仓库端镜像被替换（tag 可以被覆盖写入）。使用 digest（SHA256 摘要）才能真正做到内容寻址，确保拉取的镜像字节级一致。</p><h4 id="3-1-查询镜像-Digest"><a href="#3-1-查询镜像-Digest" class="headerlink" title="3.1 查询镜像 Digest"></a>3.1 查询镜像 Digest</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 方法一：通过 docker pull 输出获取</span></span><br><span class="line">docker pull infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line"><span class="comment"># 输出中会包含：</span></span><br><span class="line"><span class="comment"># Digest: sha256:abc123...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方法二：inspect 已拉取的镜像</span></span><br><span class="line">docker inspect infinilabs/easysearch:2.2.0-20260422-SNAPSHOT \</span><br><span class="line">  --format=<span class="string">&#x27;&#123;&#123;index .RepoDigests 0&#125;&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方法三：远程查询，不拉取镜像（需要安装 crane）</span></span><br><span class="line">crane digest infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br></pre></td></tr></table></figure><h4 id="3-2-通过-Digest-拉取镜像"><a href="#3-2-通过-Digest-拉取镜像" class="headerlink" title="3.2 通过 Digest 拉取镜像"></a>3.2 通过 Digest 拉取镜像</h4><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"># 格式：image@sha256:&lt;digest&gt;</span></span><br><span class="line">docker pull infinilabs/easysearch@sha256:a1b2c3d4e5f6...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在 docker-compose.yml 中锁定 digest（推荐生产环境使用）</span></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># docker-compose.yml</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">easysearch:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/easysearch:1.8.2@sha256:a1b2c3d4e5f6...</span></span><br></pre></td></tr></table></figure><h4 id="3-3-校验本地镜像完整性"><a href="#3-3-校验本地镜像完整性" class="headerlink" title="3.3 校验本地镜像完整性"></a>3.3 校验本地镜像完整性</h4><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"># 获取镜像的完整 digest</span></span><br><span class="line">docker images --digests infinilabs/easysearch</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出示例：</span></span><br><span class="line"><span class="comment"># REPOSITORY                TAG     DIGEST                                                                    IMAGE ID       CREATED        SIZE</span></span><br><span class="line"><span class="comment"># infinilabs/easysearch     1.8.2   sha256:a1b2c3...                                                         d4e5f6a7b8c9   2 weeks ago    512MB</span></span><br></pre></td></tr></table></figure><h3 id="四、Tag-管理技巧"><a href="#四、Tag-管理技巧" class="headerlink" title="四、Tag 管理技巧"></a>四、Tag 管理技巧</h3><h4 id="4-1-打-Tag-的标准做法"><a href="#4-1-打-Tag-的标准做法" class="headerlink" title="4.1 打 Tag 的标准做法"></a>4.1 打 Tag 的标准做法</h4><p>拉取镜像后，立即为其打上符合内部规范的标签，再推送到私有仓库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 从外部仓库拉取</span></span><br><span class="line">docker pull infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 打内部标签（包含版本 + 环境信息）</span></span><br><span class="line">docker tag infinilabs/easysearch:2.2.0-20260422-SNAPSHOT your-registry.internal/infra/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line">docker tag infinilabs/easysearch:2.2.0-20260422-SNAPSHOT your-registry.internal/infra/easysearch:1.8.2-prod</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 推送到私有仓库</span></span><br><span class="line">docker push your-registry.internal/infra/easysearch:2.2.0-20260422-SNAPSHOT</span><br></pre></td></tr></table></figure><h4 id="4-2-Tag-命名规范建议"><a href="#4-2-Tag-命名规范建议" class="headerlink" title="4.2 Tag 命名规范建议"></a>4.2 Tag 命名规范建议</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;registry&gt;/&lt;namespace&gt;/&lt;image&gt;:&lt;semver&gt;[-&lt;variant&gt;]</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">your-registry.internal/infra/easysearch:2.2.0      # 标准版本</span><br><span class="line">your-registry.internal/infra/easysearch:2.2.0-arm64    # 架构变体</span><br><span class="line">your-registry.internal/infra/easysearch:2.2.0-20260422-SNAPSHOT # 版本+日期（内部构建）</span><br></pre></td></tr></table></figure><h4 id="4-3-清理冗余-Tag"><a href="#4-3-清理冗余-Tag" class="headerlink" title="4.3 清理冗余 Tag"></a>4.3 清理冗余 Tag</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看本地所有 easysearch 相关镜像</span></span><br><span class="line">docker images | grep easysearch</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除旧版本（保留最近 2 个版本）</span></span><br><span class="line">docker rmi infinilabs/easysearch:1.7.0</span><br><span class="line">docker rmi infinilabs/easysearch:1.7.1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量清理 dangling 镜像（无 tag 的悬空镜像）</span></span><br><span class="line">docker image prune -f</span><br></pre></td></tr></table></figure><h3 id="五、多架构镜像处理"><a href="#五、多架构镜像处理" class="headerlink" title="五、多架构镜像处理"></a>五、多架构镜像处理</h3><p>Easysearch 提供 <code>amd64</code> 和 <code>arm64</code> 多架构镜像，使用 <code>docker buildx</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看镜像支持的架构</span></span><br><span class="line">docker buildx imagetools inspect infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显式指定拉取 arm64 架构（在 x86 机器上准备 arm 部署包时使用）</span></span><br><span class="line">docker pull --platform linux/arm64 infinilabs/easysearch:2.2.0-20260422-SNAPSHOT</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 class="comment"># 打包指定架构的镜像为 tar</span></span><br><span class="line">docker save infinilabs/easysearch:2.2.0-20260422-SNAPSHOT -o easysearch-2.2.0-20260422-SNAPSHOT-arm64.tar</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/04/22/1776856391406-7a8b3ec5-f005-4d98-b76d-c2092a2b7c97.png"></p><h3 id="六、离线环境部署"><a href="#六、离线环境部署" class="headerlink" title="六、离线环境部署"></a>六、离线环境部署</h3><p>网络受限环境下，使用 <code>save/load</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在有网络的机器上导出</span></span><br><span class="line">docker pull infinilabs/easysearch:1.8.2</span><br><span class="line">docker save infinilabs/easysearch:1.8.2 | gzip &gt; easysearch.tar.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 校验导出文件完整性</span></span><br><span class="line"><span class="built_in">sha256sum</span> easysearch.tar.gz &gt; easysearch.tar.gz.sha256</span><br><span class="line"></span><br><span class="line"><span class="comment"># 传输到目标机器后，先校验</span></span><br><span class="line"><span class="built_in">sha256sum</span> -c easysearch.tar.gz.sha256</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导入镜像</span></span><br><span class="line">docker load &lt; easysearch.tar.gz</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/4fb6d42093a648af82b607afe78975a4.png" alt="在这里插入图片描述"></p><h3 id="七、最佳实践总结"><a href="#七、最佳实践总结" class="headerlink" title="七、最佳实践总结"></a>七、最佳实践总结</h3><table><thead><tr><th>场景</th><th>推荐做法</th></tr></thead><tbody><tr><td>版本固定</td><td>使用语义化版本 tag，禁用 <code>latest</code></td></tr><tr><td>内容校验</td><td>生产环境用 <code>@sha256:</code> digest 引用</td></tr><tr><td>下载加速</td><td>配置镜像加速源或者使用代理</td></tr><tr><td>内部管理</td><td>拉取后 retag 推入私有仓库，统一来源</td></tr><tr><td>离线部署</td><td><code>docker save</code> + <code>sha256sum</code> 双重保障</td></tr><tr><td>多架构</td><td>明确指定 <code>--platform</code>，不依赖自动检测</td></tr><tr><td>存储清理</td><td>定期 <code>docker image prune</code>，删除无用旧版本</td></tr></tbody></table><p>遵循以上原则，可以有效避免”拉到了什么版本不知道”、”镜像被篡改没发现”、”国内下载龟速”等常见问题，让 Easysearch 的部署更加稳定、可审计、可复现。</p>]]></content>
    
    
    <summary type="html">拒绝 latest 陷阱：Easysearch Docker 镜像拉取与管理全攻略</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 额度不够用？走 Bedrock 账单</title>
    <link href="https://blog.no-claw.com/posts/4dbc8277/"/>
    <id>https://blog.no-claw.com/posts/4dbc8277/</id>
    <published>2026-04-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Claude Code 的限制越来越多了，Amazon Bedrock 提供 Claude 模型，Claude Code 官方支持走 Bedrock 调用，所以我们能够让claude接入Amazon Bedrock，然后从Amazon Web Services走账单。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">export CLAUDE_CODE_USE_BEDROCK=1</span><br><span class="line">export AWS_REGION=us-east-1  # or your preferred region</span><br><span class="line">export AWS_BEARER_TOKEN_BEDROCK=&lt;token&gt;</span><br></pre></td></tr></table></figure><p>然后重启终端，载入环境变量之后，claude code cli就会根据这个ENV把配置写到配置文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">&#123;</span><br><span class="line">  &quot;env&quot;: &#123;</span><br><span class="line">    &quot;CLAUDE_CODE_USE_BEDROCK&quot;: &quot;1&quot;,</span><br><span class="line">    &quot;AWS_REGION&quot;: &quot;us-east-1&quot;,</span><br><span class="line">    &quot;AWS_BEARER_TOKEN_BEDROCK&quot;: &lt;token&gt;</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>然后就可以了，VS Code和Kiro也能安装Claude的插件使用Chat。</p>]]></content>
    
    
    <summary type="html">Claude Code 额度不够用？走 Bedrock 账单</summary>
    
    
    
    <category term="Bedrock" scheme="https://blog.no-claw.com/categories/Bedrock/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/Bedrock/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>Quick Suite 链接 Oauth 认证的 AgentCore runtime 部署的  MCP</title>
    <link href="https://blog.no-claw.com/posts/57053bb7/"/>
    <id>https://blog.no-claw.com/posts/57053bb7/</id>
    <published>2026-04-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Quick Suite 的集成过程中，需要选择 HTTPS 链路而非 ARN 直接调用，需要处理好 URL 编码问题。目前的痛点在于： Quick Suite Console 没有清晰的报错，且 AgentCore 控制台默认只能看到资源ARN ；所以将该 ARN 转义并拼接到 HTTPS 路径中，做URL 编码&#x2F;转义，以满足 Request 请求的规范要求。</p><pre><code class="python">region=&#39;us-east-1&#39;agent_arn=&quot;arn:aws:bedrock-agentcore:us-east-1:xxxx:runtime/xxxx&quot;encoded_arn = agent_arn.replace(&#39;:&#39;, &#39;%3A&#39;).replace(&#39;/&#39;, &#39;%2F&#39;)mcp_url = f&quot;https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT&quot;</code></pre>]]></content>
    
    
    <summary type="html">Quick Suite 链接 Oauth 认证的 AgentCore runtime 部署的  MCP</summary>
    
    
    
    <category term="MCP" scheme="https://blog.no-claw.com/categories/MCP/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/MCP/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>ViVO 日历导入Apple日程</title>
    <link href="https://blog.no-claw.com/posts/f3e6ab16/"/>
    <id>https://blog.no-claw.com/posts/f3e6ab16/</id>
    <published>2026-04-15T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近换了安卓，所以全家桶的同步断了。临时起意想把apple日历同步到ViVO日历里。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223018995.png" alt="image-20260416223018995"></p><p>首先需要登录icloud获取专用密码。地址在：<br><a href="https://www.icloud.com.cn/">https://www.icloud.com.cn/</a></p><p>国行的话就是这个云上贵州，如果外服的话就是icloud.com的。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223713693.png" alt="image-20260416223713693"></p><p>登录之后，点击管理Apple账户，进入管理后台</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223859937.png" alt="image-20260416223859937"></p><p>然后左下角点击<strong>App 专用密码</strong>，点击之后会生成密码：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416224002115.png" alt="image-20260416224002115"></p><p>VIVO日历点击 <strong>日程导入和管理</strong>：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223436537.png" alt="image-20260416223436537"></p><p>然后点击添加账号：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223417222.png" alt="image-20260416223417222"></p><p>选择CalDAV账号：</p><p>用户名是apple id的用户名，密码是刚刚生成的密码，服务器地址是icloud.com。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260416223346592.png" alt="image-20260416223346592"></p><p>使用下来，总体能打通，但是还有丢数据的情况，那就和Apple watch互补一下吧。不过在VIVO端是只读的，没办法修改。</p>]]></content>
    
    
    <summary type="html">ViVO 日历导入Apple日程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十七）：懒猫SSO对接外部OpenID Connect的尝试</title>
    <link href="https://blog.no-claw.com/posts/172c9f9a/"/>
    <id>https://blog.no-claw.com/posts/172c9f9a/</id>
    <published>2026-04-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的探索中，我们已经实现了通过 gRPC 自主注册懒猫 SSO 应用，并成功集成了符合 OpenID Connect (OIDC) 协议的应用。今天我突发奇想：既然大家都是标准的 OIDC 协议，我能不能把“懒猫SSO”外挂到其他的身份提供商（IDP）里，作为一种身份联邦（Identity Federation）来使用？</p><p>如果这一步能走通，意味着我们可以实现用户系统的共享。说干就干，我选择了 <strong>AWS Cognito</strong> 作为认证中间层，尝试把懒猫SSO集成进去。</p><h3 id="初探：环境配置与客户端注册"><a href="#初探：环境配置与客户端注册" class="headerlink" title="初探：环境配置与客户端注册"></a>初探：环境配置与客户端注册</h3><p>AWS Cognito 支持添加第三方 OIDC IDP。首先，我们需要在懒猫 SSO 中为 Cognito 注册一个“身份”。</p><p>老规矩还是先使用懒猫SSO的API注册应用，当然这里的配置仍然保存在内存中，重启会丢失，所以就算一个拓宽的使用场景。使用 <code>grpcurl</code> 调用 <code>CreateClient</code> 接口，关键点在于配置 Cognito 的回调地址：</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></pre></td><td class="code"><pre><span class="line">./grpcurl -plaintext -d <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">  &quot;client&quot;: &#123;</span></span><br><span class="line"><span class="string">    &quot;id&quot;: &quot;congnito&quot;,</span></span><br><span class="line"><span class="string">    &quot;secret&quot;: &quot;congnito-secret&quot;,</span></span><br><span class="line"><span class="string">    &quot;name&quot;: &quot;New Flask App&quot;,</span></span><br><span class="line"><span class="string">    &quot;redirect_uris&quot;: [</span></span><br><span class="line"><span class="string">      &quot;https://&lt;your-cognito-domain&gt;.auth.us-west-2.amazoncognito.com/oauth2/idpresponse&quot;,</span></span><br><span class="line"><span class="string">      &quot;http://localhost:8080/auth/callback&quot;</span></span><br><span class="line"><span class="string">    ]</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;&#x27;</span> 172.18.0.2:5557 api.Dex/CreateClient</span><br></pre></td></tr></table></figure><p>这样我们就配置好了Congito的回调，当然还有本地的localhost和127.0.0.1的回调。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/add138a7-86da-4a33-97c5-f632ec224ce1.png" alt="image.png"></p><p>随后，在 AWS Cognito 控制台中新建一个 OIDC 提供商，填入对应的 <code>client_id</code> 和 <code>secret</code>。此时，Cognito 实际上成了懒猫 SSO 的一个“客户端”。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/3f1a773a-00f7-4ee7-9b72-6494765f9da2.png" alt="image.png"></p><p>配置的时候Cognito提示无法解析懒猫域名，所以这里把URL分开来填写：</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/54b4fa53-7a0c-4063-b884-1b85662af926.png" alt="image.png"></p><h3 id="渐入：深入联邦身份原理"><a href="#渐入：深入联邦身份原理" class="headerlink" title="渐入：深入联邦身份原理"></a>渐入：深入联邦身份原理</h3><p>然后尝试代码如下,因为Cognito做了中间层，所以这里的信息是Cognito的，然后登陆的页面有一个选项可以跳转到懒猫SSO。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/203cd540-899d-48e3-96e1-9817fee43db7.png" alt="image.png"></p><p>理想很丰满，现实很骨感。当我尝试通过 Cognito 页面跳转懒猫 SSO 登录时，程序报错了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">from flask import Flask, redirect, url_for, session, jsonify</span><br><span class="line">from authlib.integrations.flask_client import OAuth</span><br><span class="line">from functools import wraps</span><br><span class="line">import os</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.urandom(24)</span><br><span class="line">oauth = OAuth(app)</span><br><span class="line"></span><br><span class="line">oauth.register(</span><br><span class="line">    name=&#x27;sso&#x27;,</span><br><span class="line">    client_id=&#x27;&#x27;,</span><br><span class="line">    client_secret=&#x27;&#x27;,</span><br><span class="line">    server_metadata_url=&#x27;https://cognito-idp.us-west-2.amazonaws.com/us-west-xxx/.well-known/openid-configuration&#x27;,</span><br><span class="line">    client_kwargs=&#123;&#x27;scope&#x27;: &#x27;openid email&#x27;&#125;,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">def login_required(f):</span><br><span class="line">    @wraps(f)</span><br><span class="line">    def decorated(*args, **kwargs):</span><br><span class="line">        if &#x27;user&#x27; not in session:</span><br><span class="line">            return redirect(url_for(&#x27;login&#x27;))</span><br><span class="line">        return f(*args, **kwargs)</span><br><span class="line">    return decorated</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/&#x27;)</span><br><span class="line">def index():</span><br><span class="line">    user = session.get(&#x27;user&#x27;)</span><br><span class="line">    if user:</span><br><span class="line">        return f&#x27;Hello, &#123;user.get(&quot;email&quot;, user.get(&quot;name&quot;, &quot;unknown&quot;))&#125;. &lt;a href=&quot;/profile&quot;&gt;Profile&lt;/a&gt; | &lt;a href=&quot;/logout&quot;&gt;Logout&lt;/a&gt;&#x27;</span><br><span class="line">    return &#x27;Welcome! Please &lt;a href=&quot;/login&quot;&gt;Login&lt;/a&gt;.&#x27;</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/login&#x27;)</span><br><span class="line">def login():</span><br><span class="line">    return oauth.dex.authorize_redirect(</span><br><span class="line">        url_for(&#x27;authorize&#x27;, _external=True),</span><br><span class="line">        identity_provider=&#x27;COGNITO&#x27;</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/auth/callback&#x27;)</span><br><span class="line">def authorize():</span><br><span class="line">    token = oauth.dex.authorize_access_token()</span><br><span class="line">    session[&#x27;user&#x27;] = token.get(&#x27;userinfo&#x27;)</span><br><span class="line">    session[&#x27;token_info&#x27;] = &#123;</span><br><span class="line">        &#x27;access_token&#x27;: token.get(&#x27;access_token&#x27;),</span><br><span class="line">        &#x27;id_token&#x27;: token.get(&#x27;id_token&#x27;),</span><br><span class="line">        &#x27;token_type&#x27;: token.get(&#x27;token_type&#x27;),</span><br><span class="line">        &#x27;expires_at&#x27;: token.get(&#x27;expires_at&#x27;),</span><br><span class="line">    &#125;</span><br><span class="line">    return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/profile&#x27;)</span><br><span class="line">@login_required</span><br><span class="line">def profile():</span><br><span class="line">    return jsonify(userinfo=session[&#x27;user&#x27;], token=session.get(&#x27;token_info&#x27;))</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/logout&#x27;)</span><br><span class="line">def logout():</span><br><span class="line">    session.pop(&#x27;user&#x27;, None)</span><br><span class="line">    return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line"></span><br><span class="line">if __name__ == &#x27;__main__&#x27;:</span><br><span class="line">    app.run(host=&#x27;0.0.0.0&#x27;, port=8080, debug=True)</span><br></pre></td></tr></table></figure><p>经过数天的排查，我定位到了问题的核心：<strong>网络隔离与双向通信。</strong><br>Cognito 作为一个公有云服务，在执行 OIDC 协商的时需要访问懒猫 SSO 的接口才能够正常工作。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/7f07fa20-01e5-464a-9a16-19d9f73f52bf.png" alt="image.png"></p><p>但由于懒猫 SSO 部署在私有微服环境下，虽然我的懒猫微服能够访问互联网，但是Cognito 的服务器缺无法解析我的私有域名，更无法穿透内网进行通信，导致请求超时，最终我在日志中翻到了HTTP 400。</p><h3 id="曲终：另辟蹊径的“重定向”方案"><a href="#曲终：另辟蹊径的“重定向”方案" class="headerlink" title="曲终：另辟蹊径的“重定向”方案"></a>曲终：另辟蹊径的“重定向”方案</h3><p>于是不甘心，想了一个折中的办法，是不是可以把这个跳转逻辑放到浏览器里来做呢？<br>想了两个办法：</p><ol><li>直接让浏览器代替Cognito的跳转，多次尝试无果</li><li>在Cognito返回失败的时候捕获error，然后在本地302跳转到懒猫SSO</li></ol><p>幸运的是方案2是工作的，虽然有种欺骗的味道，但是似乎是达成了公有的IDP外挂懒猫IDP的假象。首先把Cognito的注册都关掉，但是保留登陆功能，这样就没有人可以通过Cognito进行登陆，然后就只能点击懒猫SSO登陆，这个时候就会重新协商OpenID Connect协议，当我开启懒猫微服客户端的时候，我可以解析域名，别人哪怕能够跳转也无法做域名解析，所以很安全，其他人无法注册和登录Cognito，也无法解析懒猫微服域名。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">from flask import Flask, redirect, url_for, session, request</span><br><span class="line">from authlib.integrations.flask_client import OAuth</span><br><span class="line">from authlib.integrations.base_client.errors import OAuthError</span><br><span class="line">import os</span><br><span class="line">import logging</span><br><span class="line"></span><br><span class="line">logging.basicConfig(level=logging.DEBUG, format=&#x27;%(asctime)s [%(levelname)s] %(message)s&#x27;)</span><br><span class="line">log = logging.getLogger(__name__)</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.environ.get(&#x27;FLASK_SECRET_KEY&#x27;, &#x27;dev-secret-key-change-in-prod&#x27;)</span><br><span class="line">oauth = OAuth(app)</span><br><span class="line"></span><br><span class="line">oauth.register(</span><br><span class="line">    name=&#x27;cognito&#x27;,</span><br><span class="line">    client_id=&#x27;&#x27;,</span><br><span class="line">    client_secret=&#x27;&#x27;,</span><br><span class="line">    server_metadata_url=&#x27;https://cognito-idp.us-west-2.amazonaws.com/us-west-xxx/.well-known/openid-configuration&#x27;,</span><br><span class="line">    client_kwargs=&#123;&#x27;scope&#x27;: &#x27;openid email&#x27;&#125;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">oauth.register(</span><br><span class="line">    name=&#x27;dex&#x27;,</span><br><span class="line">    client_id=&#x27;Congnito&#x27;,</span><br><span class="line">    client_secret=&#x27;Congnito-secret&#x27;,</span><br><span class="line">    server_metadata_url=&#x27;https://x.heiyu.space/sys/oauth/.well-known/openid-configuration&#x27;,</span><br><span class="line">    client_kwargs=&#123;&#x27;scope&#x27;: &#x27;openid email&#x27;&#125;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/&#x27;)</span><br><span class="line">def index():</span><br><span class="line">    user = session.get(&#x27;user&#x27;)</span><br><span class="line">    if user:</span><br><span class="line">        return f&#x27;Hello, &#123;user.get(&quot;email&quot;, user.get(&quot;sub&quot;))&#125;. &lt;a href=&quot;/logout&quot;&gt;Logout&lt;/a&gt;&#x27;</span><br><span class="line">    return &#x27;Welcome! &lt;a href=&quot;/login&quot;&gt;Login&lt;/a&gt;&#x27;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/login&#x27;)</span><br><span class="line">def login():</span><br><span class="line">    session[&#x27;provider&#x27;] = &#x27;cognito&#x27;</span><br><span class="line">    redirect_uri = url_for(&#x27;callback&#x27;, _external=True)</span><br><span class="line">    return oauth.cognito.authorize_redirect(redirect_uri)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/auth/callback&#x27;)</span><br><span class="line">def callback():</span><br><span class="line">    provider = session.get(&#x27;provider&#x27;, &#x27;cognito&#x27;)</span><br><span class="line">    log.info(f&#x27;Callback - provider: &#123;provider&#125;, args: &#123;dict(request.args)&#125;&#x27;)</span><br><span class="line"></span><br><span class="line">    # Cognito 失败，fallback 到 懒猫SSO 直连</span><br><span class="line">    if provider == &#x27;cognito&#x27; and request.args.get(&#x27;error&#x27;):</span><br><span class="line">        log.warning(f&#x27;Cognito failed: &#123;request.args.get(&quot;error&quot;)&#125;, falling back to Dex&#x27;)</span><br><span class="line">        session[&#x27;provider&#x27;] = &#x27;dex&#x27;</span><br><span class="line">        redirect_uri = url_for(&#x27;callback&#x27;, _external=True)</span><br><span class="line">        return oauth.dex.authorize_redirect(redirect_uri)</span><br><span class="line"></span><br><span class="line">    try:</span><br><span class="line">        client = oauth.cognito if provider == &#x27;cognito&#x27; else oauth.dex</span><br><span class="line">        token = client.authorize_access_token()</span><br><span class="line">        session[&#x27;user&#x27;] = token.get(&#x27;userinfo&#x27;)</span><br><span class="line">        session.pop(&#x27;provider&#x27;, None)</span><br><span class="line">        return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line">    except OAuthError:</span><br><span class="line">        if provider == &#x27;cognito&#x27;:</span><br><span class="line">            log.warning(&#x27;Cognito token exchange failed, falling back to Dex&#x27;)</span><br><span class="line">            session[&#x27;provider&#x27;] = &#x27;dex&#x27;</span><br><span class="line">            redirect_uri = url_for(&#x27;callback&#x27;, _external=True)</span><br><span class="line">            return oauth.dex.authorize_redirect(redirect_uri)</span><br><span class="line">        raise</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/logout&#x27;)</span><br><span class="line">def logout():</span><br><span class="line">    session.pop(&#x27;user&#x27;, None)</span><br><span class="line">    return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">if __name__ == &#x27;__main__&#x27;:</span><br><span class="line">    app.run(debug=True, port=8080)</span><br></pre></td></tr></table></figure><p>别急，如果你看到了这个页面只能说是域名跳转成功，并不是OIDC的凭证交换。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/46bcb057-90ac-483c-a434-b0c72c0c7a4e.png" alt="image.png"></p><p>输入用户名和密码之后出现这个页面就对了：<br><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d741b625-f163-48ad-8366-c999780dd899.png" alt="image.png"></p><p>登陆之后就换到claim信息了，可以看到打印出来了邮箱。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/f0c47b39-825c-4977-bbd0-0e2aa8c26ebb.png" alt="image.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>没白折腾，确实还挺抽象的，抽空又复习了Oauth和OpenID Connect的底层原理，通过使用懒猫SSO，我的技术栈又升级了。</p>]]></content>
    
    
    <summary type="html">懒猫微服进阶心得（十七）：懒猫SSO对接外部OpenID Connect的尝试</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="SSO" scheme="https://blog.no-claw.com/tags/SSO/"/>
    
    <category term="OIDC" scheme="https://blog.no-claw.com/tags/OIDC/"/>
    
  </entry>
  
  <entry>
    <title>景山公园游记</title>
    <link href="https://blog.no-claw.com/posts/fc3e8db7/"/>
    <id>https://blog.no-claw.com/posts/fc3e8db7/</id>
    <published>2026-04-12T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近的日子我喜欢去二环闲逛，一方面是周末去的医院都在附近，另一方面，从皇城根遗址进去之后，才算得上最古早的北京城。东西城内还有些许老北京的味道，三环四环是打工牛马们的圣地，或者我们过些日子，可以把他们叫做龙虾，五环外充满了野蛮与暴力，二环的大爷说不认为那边的是北京人了，所以，我从来不敢轻易的出去。</p><p>周末有闲情去逛了景山公园，印象里是故宫的后花园，大概从元朝就开始了，一直到清朝灭亡，后面才慢慢开放。我在北京这些年，除了在Covid-19时期去瞻仰过毛爷爷，参观人民英雄纪念碑，还有故宫里把摆拍的汉服母女当作闪电下墙壁投出的旧时代里的影子之外，还没去过什么古迹。唯独到景山公园，因为没有预约被拒之门外，于是最近才想起来，这一圈下来更是勾起了我对明末朝代更替的追忆。</p><p>明朝末代皇帝是吊死在这里，相传崇祯自毁长城，冤杀大将袁崇焕，从此辽东天险不再，闯王起义入京，吴三桂一怒为红颜。大明王朝从此覆灭，留给后人的，只有无尽的断壁残垣而已。金庸的《碧血剑》则是以此为原型，讲述了一代江湖故事——从袁崇焕被冤杀开始，袁承志（历史无此人）为报仇而闯荡江湖，与闯王起义军一同推翻明朝，其间也夹杂着他与长平公主阿九一丝丝微妙的感情线。</p><p>近400年后，后人来此凭吊，除了明思宗殉国处的石碑外，其他的不过一棵歪脖老树而已。从他的死，我想到朱允炆，被四叔朱棣赶尽杀绝，大火中其死因成为千古谜团；又想到李煜，曾感叹“雕栏玉砌应犹在，只是朱颜改”；甚至，我还想到刘谌，蜀国城破之日，他杀妻杀子，最后在祠堂前自尽。自古以来，文死谏，武死战，反倒是皇帝们的死法各有不同。只是在小说的刻画里，崇祯已是穷途末路，走到煤山的尽头，结束了他悲剧的一生。而从整个景山公园的布局来看，崇祯大概是急匆匆从皇宫后门出来，然后在南门不远处，随便找了棵树草草吊死罢了，不知道他在最后一秒是否有回望，是否有“国破山河在”的感慨。</p><p>往北，是景山关帝庙，属于皇家，始建于明代，清朝移建重修。庙中的画布从桃园结义，大破黄巾开始，然后关羽斩颜良文丑，解白马之围，过五关斩六将，水淹七军，威震华夏，然后被奸人所害，身首异处。后来的人常常修缮关庙，以关羽的忠义原型，逐渐演变成武圣，大帝，于是简化为关帝。去年在武侯祠，我曾经发出了孙权后人是否拜会关帝是否合适的想法，现在大抵是知道了。</p><p>景山坐落在北京的中轴线上，其最北端，是历代皇帝祭祖的寿皇殿。这里曾供奉着清朝先祖的牌位，每逢大年初一或其他重要祭祀之日，皇帝便会亲临祭拜。殿内至今保留着神库、神厨等建筑，透过这些遗迹，我们得以追忆那些逝去的岁月，殿门前的树木，大多已有110年的树龄，默默见证着王朝的更迭与时代的变迁。</p><p>走出神道，商业街的喧嚣，不远处老大爷碎碎念，还有从山坡上传来的一群人合唱的声音。“旧时王谢堂前燕，飞入寻常百姓家”。大概是这个意思了吧。</p><p>吃过饭，继续往北，走过安定桥，去钟鼓楼，中轴线尽头是被改造成酒吧饭馆的宏恩观。</p><p>下个周末，去中轴线南面吧。</p>]]></content>
    
    
    <summary type="html">景山公园游记</summary>
    
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>龙珠外传，比克大魔王的自白</title>
    <link href="https://blog.no-claw.com/posts/64009444/"/>
    <id>https://blog.no-claw.com/posts/64009444/</id>
    <published>2026-04-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本故事纯属虚构，请不要恶意解读。</p></blockquote><p>我，比克，是被世人所误解的大魔王，在你们眼里烧杀抢掠，无恶不作。</p><p>在很早以前，我是本性善良的那美克星人，我的母星球民风淳朴，家家夜不闭户，路不拾遗。突然有一天，大长老说我太过年轻，需要过去历练，所以让我出去到处走走。</p><p>在浩瀚的宇宙中游荡，我的第一站是蓝白星球，说来惭愧，这也成了我的最后一站。由于人性的贪婪，自私，嗜血，自相残杀而导致我的本心在不断的被侵蚀，从此在这里沾染了邪念。我皮薄心软，而又太纯粹良善，面对这群人心不古的碳基生物便显得格格不入。</p><p>时间久了，所以我决定把我善良的一面完全分离出去，这样就不会有道德负罪，不会感受到痛苦，这是为了活下去，我的挣扎。让分离出来的善念去做天神，让他去制造属于地球的龙珠，然后借助龙珠的许愿把这里变成和娜美克星球一样的故乡。</p><p>原定分离的计划是，让他保持着最原本最初的样子，就是那种在我们那美克星被视为美德的仁慈和纯真，母星球的原住民们都喜欢以德报怨。在大长老的英明领导下，我们也不会滥用龙珠，所以几亿年也没有听过发生什么负向能源以及黑龙珠的故事。但是这里完全不同，大街上到处充斥着各种欲望和怨念的结合体，所以很早之前，我就讲这些隐患这些全盘托付，苦口婆心的告诉过以前的界王神，至于他被封印，那是后面的事情了。</p><p>最早以前我预言过魔人布欧的诞生，不过与他不同的是，他是邪恶念头的占据主体，完全依靠无意识的杀戮——而我与他不同，我选择不得不去剥离善良，只为把那些软弱、无知、脆弱以及同情的情绪，一并全都抛出去。这些在这个蓝白星球中不知不觉的产生的脆弱感情感，总会在不经意间会干扰我的思考的节律，以至于无意识的对他人进行评判或者怨恨。后来我明白了，不能对人产生感情。否则，我就不再是我。至少在我看来，当地的蓝白星人，倒像是战国时代旧物所积的怨念，借一把梳子、一缕头发便化形的妖怪，然后越俎代庖，李代桃僵，夺舍之后再假装自己就是这一代的天命人。</p><p>我并不反感这些由巨人身上跳蚤幻化的后来者，甚至在一开始有些迎合他们。你们喜欢杀戮，我就变成杀戮，你们喜欢肮脏，我就变成肮脏，你们喜欢毁灭，我就变成毁灭，他们不喜欢我，我也不喜欢自己，也逐渐对人认可宣扬比克大魔王这个称号，以至于到后来，我满世界的寻找屠杀武道家，只是为了测试我是否还沾染着脆弱的人类情感而已。到后来，代价太大，我已无力解释。你们都说我残忍，而为了活下去，我只好假装感受不到任何痛苦，放不低姿态所以无法共情。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/04/11/1775843322967-ef59755f-7b9d-438c-bc99-4ded76fa5647.jpg"></p><p>你们蓝白球人喜欢讲，‘橘生淮南，’这又何尝不是。</p><p>当年与武泰斗巅峰一战，我早就已经厌倦了，他天资聪颖，自他出生起，我便在神殿看着他长大，我和他本不必走到如此的。而他漫天拳雨袭来，我本可以轻松的杀死他，于是假装漫不经心。而他要代表某种流派，我和他终究只是立场不同而已。战士骄傲的自尊心不允许我放水，在日日夜夜的战斗后，最终我被成功封印，武泰斗力竭阵亡。在此之后，他的后人龟仙与鹤徒各奔东西，于是树倒猢狲散，土徒子徒孙争斗不休。</p><p>在那平静如水的被封印的日子里，我每次苦思冥想，究竟错的是我，还是始终有一双无形的大手在恣意玩弄这一切？为了贯彻到底，于是我在佛前求了五百年，求他让我了结和武道家的尘缘。碳基人又执念太深，非要说他是正，我是邪，于是正邪不两立，双方争斗又不死不休，索性不如将天底下的罪孽都归我吧，这样的人留着上也是祸害，也省着伤害他人。所以后来你们不断的叫我魔王，比克大魔王，只是我心已死，是非我已无心辩驳。只要我不愿意，随时能够毁灭地球，就凭这一点你们就该对我感恩戴德，顶礼膜拜。这些愚蠢的碳基生物毕生的的愿望就是安家置业成居，然后安静的等待老死。而我是可以不死的，所以宁可不沾染庸俗。而你们，凭什么认为你们能够救赎我？</p><p>后来被孙悟空打败，我是释然了的，除去被封印的漫长的岁月里，我早就已经倦怠了，我需要一个结束自己的理由。但是倘若我死了神仙也会消失，我需要慎重考虑，第一次交手悟空与我实力悬殊，我是故意留了他一口气，就是想知道赛亚人是不是传说中的武道家，他是不是传说中可以打败弗利萨，然后的可以理解我的超级赛亚人。虽然我知道，为了对手的尊严不能放水，就像当初对战武泰斗一样，战士可以光明正大的战死，但是你不能。</p><p>后来我似是解脱了，悟空在超神水的帮助下打败了我。肌肉发达的他显然没有想过，我若死了，神仙也会一起消失。所以最后我吐出蛋，幻化出二代分身，就是为了让那个曾经我不顾一切分离出来的善念活下去。出于某种不可言状的目标，我给他留下的执念是打败孙悟空，同样也要探索在和武道家之前没有达成的共识。我不能因为我的原因造成神仙消失，同时还需要二代的我寻找最初的的执念，让他去继续探究作为一个人人喊打的异类，究竟应该以怎样的姿态活着？</p><p>弗利萨的到来是一个插曲，但是从某种意义来说，我和蓝白星有一段短暂的风花雪月。这群愚蠢的碳基生物虽常常忌惮我，总体也算是秋毫无犯。又发生了很多事，我们一起联手对抗了人造人，魔人布欧。沙鲁篇我和神仙又合二为一，我又找回了作为生物本能的情感，我说这是神仙大人的智慧。我亲自训练了悟饭，悟天克斯，甚至还有小芳。</p><p>最后的事你们都知道了，我变成了孙家保姆，悟饭亲爹。所以我也是一个拥有感情的人？假如有平行世界会好么？</p>]]></content>
    
    
    <summary type="html">龙珠外传，比克大魔王的自白</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="龙珠" scheme="https://blog.no-claw.com/tags/%E9%BE%99%E7%8F%A0/"/>
    
  </entry>
  
  <entry>
    <title>深夜，我卸载了豆包</title>
    <link href="https://blog.no-claw.com/posts/121796b7/"/>
    <id>https://blog.no-claw.com/posts/121796b7/</id>
    <published>2026-04-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>说来也很戏剧性，从一开始抵制国产模型，到最后觉得终端的TTS ASR 很方便，再到从手机上卸载任何即时聊天的AI程序，似乎也没过多久。甚至之前还一度赞扬过豆包的技术做得如何如何。</p><p>深夜好好想想，大概有以下几个问题吧：</p><ol><li>骑车的时候，会一直和豆包聊天，然后直到从此前才把手机收起来。</li><li>走路的时候也会和豆包聊，一些不同不痒的话题，甚至深夜也会把陈年话题翻出来，想寻求认同感</li><li>在情感方面的建议，AI就是一个墙头草。经常过度解读，所以经常让他说“请打醒我”， ”这个对吗“？？然后又会给到截然不同的输出，经常反复。完全重度依赖提示词，或者说系统提示词不够好。</li><li>记忆做的不好，在一个新的Session中会读到我的用户画像，慢慢的收集我的喜好，毕竟有的已经是B类PII了，挺可怕的。</li><li>很容易恭维型人格，让你觉得某些事即将要发生，其实AI给的情绪价值毫无意义。甚至容易情感绑定，万一哪天发一个指导自杀的指令，无法想象。</li><li>某些回复还是太过油腻，我还是得花精力筛选。</li><li>对有些话题的边界太过严苛，比如看到一个新的敏感词，模型直接拒绝回答，我还得去求助其他LLM。</li></ol><blockquote><p>不过似乎让他对症找医院还行。起码医保范围内多开点检查。</p></blockquote><p>除非再人脑植入芯片可以像DMA一样做决策吧，不然日常生活AI话完全就是把日常碎片化。</p><p>再说为什么是卸载豆包，因为其他的也没用过，千问的奶茶也没喝到。</p><p>Gemma4 也出了离线的手机端，会好么？</p><p>我想过去科技化的日子了。</p><h4 id="关于作者"><a href="#关于作者" class="headerlink" title="关于作者"></a>关于作者</h4><p>一个懂计算机组成原理、做AI相关产品工作、理性到会用DMA比喻决策困境的独居青年，在深夜和最后一个AI聊完天后，决定回到一个不需要向机器倾诉的世界——不是因为他恨技术，而是因为他太懂技术，知道技术给不了他真正想要的东西。</p>]]></content>
    
    
    <summary type="html">深夜，我卸载了豆包</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十六）：解密OpenID Connect，如何自主注册懒猫SSO？</title>
    <link href="https://blog.no-claw.com/posts/a4d264e8/"/>
    <id>https://blog.no-claw.com/posts/a4d264e8/</id>
    <published>2026-04-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们移植应用的时候经常希望集成懒猫微服的SSO，在打包LPK上架应用的时候，可以使用官方的配置文件进行集成。但是我们今天想要刨根问底，让他变得通用一些。如果我本地的app想要集成懒猫微服的SSO，是不是也有其他的办法呢，毕竟是底层也是通用的OpenID Connect协议，于是便有了这个文章。</p><p>懒猫的SSO是如下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">- Issuer：https://name.heiyu.space/sys/oauth</span><br><span class="line">- HTTP 监听：0.0.0.0:8000（容器内部，没有映射到宿主机端口）</span><br><span class="line">- gRPC 监听：0.0.0.0:5557，开启了 reflection</span><br><span class="line">- 存储：SQLite3 内存模式（:memory:），意味着重启后所有数据丢失</span><br><span class="line">- Connector：使用 authproxy 类型，名为 hportal</span><br><span class="line">- 没有配置任何 staticClients（OAuth client）</span><br><span class="line">- 在我的网络中，IP 为 172.18.0.2</span><br><span class="line">- 没有端口映射到宿主机，应该是通过反向代理（路径 /sys/oauth）访问</span><br></pre></td></tr></table></figure><p>懒猫微服的SSO服务运行在5557 端口，通过 docker 网络访问地址是 172.18.0.2:5557。</p><p>可以先使用grpcurl来连接测试：</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">./grpcurl -plaintext 172.18.0.2:5557 list</span><br></pre></td></tr></table></figure><p>在这里下载GRPC：<br><a href="https://github.com/fullstorydev/grpcurl/releases">https://github.com/fullstorydev/grpcurl/releases</a></p><blockquote><p>gRPC 是一个<strong>跨语言、高性能</strong>的远程过程调用（RPC）框架，它<strong>强依赖 HTTP&#x2F;2</strong> 协议，并默认使用 <strong>Protobuf</strong> 作为二进制序列化协议。它的核心优势在于利用 HTTP&#x2F;2 的<strong>多路复用</strong>和<strong>头部压缩</strong>提升了性能，通过二进制传输减少了带宽消耗，并且通过<strong>强类型的接口定义</strong>保证了跨语言调用的严谨性。”</p></blockquote><p>然后使用grpcurl对懒猫微服的SSO的API进行操作，因为是OIDC，所以这一步骤主要一个Oauth的应用， 也就是注册App name，client_id，client_secret，以及 redirect_uris。存储用的是内存 SQLite，容器重启后所有 OAuth token、授权码、已注册的 client 都会丢失。当然如果你想持久化的话，也可以写到systemd启动脚本让系统自启动拉起来。</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">./grpcurl -plaintext -d &#x27;&#123;&quot;client&quot;:&#123;&quot;id&quot;:&quot;my-app&quot;,&quot;secret&quot;:&quot;my-app-secret&quot;,&quot;redirect_uris&quot;:[&quot;http://localhost:8080/auth/callback&quot;],&quot;name&quot;:&quot;My App&quot;&#125;&#125;&#x27; 172.18.0.2:5557 api.Dex/CreateClient</span><br></pre></td></tr></table></figure><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/eef8198c-d575-4923-add2-8645a776c4f5.png" alt="image.png" title="image.png"></p><p>注册之后我们把这些信息放到authlib代码里面，把SSO串起来。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">from flask import Flask, redirect, url_for, session, jsonify</span><br><span class="line">from authlib.integrations.flask_client import OAuth</span><br><span class="line">from functools import wraps</span><br><span class="line">import os</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.urandom(24)</span><br><span class="line">oauth = OAuth(app)</span><br><span class="line"></span><br><span class="line">oauth.register(</span><br><span class="line">    name=&#x27;dex&#x27;,</span><br><span class="line">    client_id=&#x27;my-app&#x27;,</span><br><span class="line">    client_secret=&#x27;my-app-secret&#x27;,</span><br><span class="line">    server_metadata_url=&#x27;https://aimax.heiyu.space/sys/oauth/.well-known/openid-configuration&#x27;,</span><br><span class="line">    client_kwargs=&#123;&#x27;scope&#x27;: &#x27;openid email profile&#x27;&#125;,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">def login_required(f):</span><br><span class="line">    @wraps(f)</span><br><span class="line">    def decorated(*args, **kwargs):</span><br><span class="line">        if &#x27;user&#x27; not in session:</span><br><span class="line">            return redirect(url_for(&#x27;login&#x27;))</span><br><span class="line">        return f(*args, **kwargs)</span><br><span class="line">    return decorated</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/&#x27;)</span><br><span class="line">def index():</span><br><span class="line">    user = session.get(&#x27;user&#x27;)</span><br><span class="line">    if user:</span><br><span class="line">        return f&#x27;Hello, &#123;user.get(&quot;email&quot;, user.get(&quot;name&quot;, &quot;unknown&quot;))&#125;. &lt;a href=&quot;/profile&quot;&gt;Profile&lt;/a&gt; | &lt;a href=&quot;/logout&quot;&gt;Logout&lt;/a&gt;&#x27;</span><br><span class="line">    return &#x27;Welcome! Please &lt;a href=&quot;/login&quot;&gt;Login&lt;/a&gt;.&#x27;</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/login&#x27;)</span><br><span class="line">def login():</span><br><span class="line">    return oauth.dex.authorize_redirect(url_for(&#x27;authorize&#x27;, _external=True))</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/auth/callback&#x27;)</span><br><span class="line">def authorize():</span><br><span class="line">    token = oauth.dex.authorize_access_token()</span><br><span class="line">    session[&#x27;user&#x27;] = token.get(&#x27;userinfo&#x27;)</span><br><span class="line">    session[&#x27;token_info&#x27;] = &#123;</span><br><span class="line">        &#x27;access_token&#x27;: token.get(&#x27;access_token&#x27;),</span><br><span class="line">        &#x27;id_token&#x27;: token.get(&#x27;id_token&#x27;),</span><br><span class="line">        &#x27;token_type&#x27;: token.get(&#x27;token_type&#x27;),</span><br><span class="line">        &#x27;expires_at&#x27;: token.get(&#x27;expires_at&#x27;),</span><br><span class="line">    &#125;</span><br><span class="line">    return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/profile&#x27;)</span><br><span class="line">@login_required</span><br><span class="line">def profile():</span><br><span class="line">    return jsonify(userinfo=session[&#x27;user&#x27;], token=session.get(&#x27;token_info&#x27;))</span><br><span class="line"></span><br><span class="line">@app.route(&#x27;/logout&#x27;)</span><br><span class="line">def logout():</span><br><span class="line">    session.pop(&#x27;user&#x27;, None)</span><br><span class="line">    return redirect(url_for(&#x27;index&#x27;))</span><br><span class="line"></span><br><span class="line">if __name__ == &#x27;__main__&#x27;:</span><br><span class="line">    app.run(host=&#x27;0.0.0.0&#x27;, port=8080, debug=True)</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这是一个用 Flask + authlib 对接 懒猫 SSO 的最小 OIDC 客户端应用：</p></blockquote><ul><li>通过 懒猫 SSO（OpenID Connect Provider）实现单点登录</li><li>oauth.register 配置 懒猫 SSO 的 OIDC 发现端点，自动获取授权&#x2F;token 等地址</li><li>login_required 装饰器做路由守卫，未登录自动跳转登录</li><li>&#x2F;login 发起 OAuth2 授权码流程，跳转到 懒猫 SSO 登录页</li><li>&#x2F;auth&#x2F;callback 接收 懒猫 SSO 回调，用授权码换取 access_token 和用户信息，存入 session</li><li>&#x2F;profile 展示当前登录用户的 userinfo 和 token 信息</li><li>&#x2F;logout 清除 session 登出</li></ul><p>整个流程就是标准的 OAuth2 Authorization Code Flow：用户点登录 → 跳 懒猫 SSO → 认证通过 → 回调拿 token → 存<br>session → 完成登录。</p><p>访问应用的时候，一开始会出现这个认证，这个是懒猫域名自带的认证，是为了应用放在公网的上的强制用户名密码认证，所以不要把他当作是我们本次懒猫SSO的主角。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/fcf1ae18-2b1b-4cab-a64b-8d9331f3a4d6.png" alt="image.png" title="image.png"></p><p>这个才是正式的OpenID Connect，点击Grant Access，然后就可以进行认证了。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/e81d2287-cf12-470a-9c7f-3b29a7e5a165.png" alt="image.png" title="image.png"></p><p>登录之后我们可以查看profile信息，以及登录之后token，这样就抓到了OIDC的去全部信息：</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a040c5d5-3fee-4a2e-96e8-10baeec4936d.png" alt="image.png" title="image.png"></p><p>如果之前注册的是 localhost，但 Flask 默认用127.0.0.1 。所以会收到Unregistered redirect_uri 的错误。所以把域名改成localhost就好。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a1e2aa7a-f09d-4d2a-bf3b-d7787ffdc35b.png" alt="image.png" title="image.png"></p><p>好了，以上就是如何超越系统注册，使用API建立自己的懒猫SSO应用了，这样我们就可以不必再依赖三方的IDP了。</p><p>Less is more。</p>]]></content>
    
    
    <summary type="html">懒猫微服进阶心得（十六）：解密OpenID Connect，如何自主注册懒猫SSO？</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>让 OpenSearch 支持单点登录：从密码登录到 OpenID Connect</title>
    <link href="https://blog.no-claw.com/posts/37c6815d/"/>
    <id>https://blog.no-claw.com/posts/37c6815d/</id>
    <published>2026-04-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前我们用 Helm 在 Kubernetes 上部署了 OpenSearch 集群，使用默认的数据库密码进行登录。今天来介绍如何接入 Amazon Cognito 作为 OpenID Connect（OIDC）身份提供商，让Dashboard 实现企业级 SSO 单点登录，同时让 OpenSearch API 也支持 JWT Token 认证。</p><p>OpenID Connect 的提供商我选择了 Amazon 的 Cognito，然后对应的Dashboards（前端跳转）和 OpenSearch Security（后端验证）都要单独来做集成。</p><p>OIDC 是通用协议，Cognito 只是本文选的提供商，换 Keycloak、Okta、Auth0 都一样</p><span id="more"></span><h3 id="第一步：创建-Cognito-App-Client"><a href="#第一步：创建-Cognito-App-Client" class="headerlink" title="第一步：创建 Cognito App Client"></a>第一步：创建 Cognito App Client</h3><p>在 Cognito 控制台创建一个 App Client,因为我做了端口映射，所以回调和注销的URL都是localhost：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/04/02/1775128493364-b0a28f2a-e8a2-495a-a264-264fa24fa670.png"></p><ul><li>勾选”生成客户端密钥”（Generate client secret）</li><li>认证流程勾选：授权码授权（Authorization code grant）、USER_PASSWORD_AUTH</li><li>OpenID Connect 范围选择：openid、profile、email</li><li>允许的回调 URL：<code>http://localhost:5601/auth/openid/login</code></li><li>允许的注销 URL：<code>http://localhost:5601</code></li><li>开启 Hosted UI 并配置 Cognito 域名</li></ul><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2026/04/02/1775128444386-ee93599f-9496-4117-8475-4f1bf8c7e5c8.png"></p><p>记录下 Client ID、Client Secret、User Pool ID。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/04/02/1775128433389-660daecf-d74f-4bcb-80b1-8aefab24fe6a.png"></p><p>创建用户后，Cognito 会将用户状态设为”强制更改密码”，可以通过 AWS CLI 将密码设为永久：</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">aws cognito-idp admin-set-user-password \</span><br><span class="line">  --user-pool-id &lt;user-pool-id&gt; \</span><br><span class="line">  --username &lt;username&gt; \</span><br><span class="line">  --password <span class="string">&#x27;&lt;password&gt;&#x27;</span> \</span><br><span class="line">  --permanent \</span><br><span class="line">  --region &lt;region&gt;</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img10@main/2026/04/02/1775128390605-ee01d63f-c124-4fc3-a66e-9b566b5a1ff1.png"></p><h3 id="第二步：部署-Dashboards（前端跳转）"><a href="#第二步：部署-Dashboards（前端跳转）" class="headerlink" title="第二步：部署 Dashboards（前端跳转）"></a>第二步：部署 Dashboards（前端跳转）</h3><p>创建 <code>dashboards-values.yaml</code>，让 Dashboards 跳转到 Cognito 登录，其实就是配置OIDC的redirect_url，scope，client_id，client_secret 这些东西：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">opensearchHosts:</span> <span class="string">&quot;https://opensearch-cluster-master:9200&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">config:</span></span><br><span class="line">  <span class="attr">opensearch_dashboards.yml:</span></span><br><span class="line">    <span class="attr">server.host:</span> <span class="string">&quot;0.0.0.0&quot;</span></span><br><span class="line">    <span class="attr">opensearch.hosts:</span> [<span class="string">&quot;https://opensearch-cluster-master:9200&quot;</span>]</span><br><span class="line">    <span class="attr">opensearch.ssl.verificationMode:</span> <span class="string">none</span></span><br><span class="line">    <span class="attr">opensearch.username:</span> <span class="string">&quot;kibanaserver&quot;</span></span><br><span class="line">    <span class="attr">opensearch.password:</span> <span class="string">&quot;kibanaserver&quot;</span></span><br><span class="line">    <span class="attr">opensearch.requestHeadersAllowlist:</span> [<span class="string">&quot;Authorization&quot;</span>, <span class="string">&quot;securitytenant&quot;</span>]</span><br><span class="line">    <span class="attr">opensearch_security.multitenancy.enabled:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">opensearch_security.auth.type:</span> <span class="string">&quot;openid&quot;</span></span><br><span class="line">    <span class="attr">opensearch_security.openid.connect_url:</span> <span class="string">&quot;https://cognito-idp.&lt;region&gt;.amazonaws.com/&lt;user-pool-id&gt;/.well-known/openid-configuration&quot;</span></span><br><span class="line">    <span class="attr">opensearch_security.openid.client_id:</span> <span class="string">&quot;&lt;app-client-id&gt;&quot;</span></span><br><span class="line">    <span class="attr">opensearch_security.openid.client_secret:</span> <span class="string">&quot;&lt;app-client-secret&gt;&quot;</span></span><br><span class="line">    <span class="attr">opensearch_security.openid.scope:</span> <span class="string">&quot;openid profile email&quot;</span></span><br><span class="line">    <span class="attr">opensearch_security.openid.base_redirect_url:</span> <span class="string">&quot;http://localhost:5601&quot;</span></span><br></pre></td></tr></table></figure><p>接下来使用helm进行部署：</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">helm upgrade --install dashboards opensearch/opensearch-dashboards -f dashboards-values.yaml</span><br></pre></td></tr></table></figure><p>几个注意点：</p><ul><li><code>config</code> 字段会<strong>完全覆盖</strong>默认的 <code>opensearch_dashboards.yml</code>，不是合并。所以 <code>server.host</code>、<code>opensearch.hosts</code> 等基础配置必须写进去，否则 Dashboards 会启动失败</li><li><code>opensearch.username/password</code> 是 Dashboards 后端连接 OpenSearch 的服务账号（<code>kibanaserver</code>），即使用了 OIDC 登录也需要保留。OIDC 解决的是终端用户认证，<code>kibanaserver</code> 解决的是 Dashboards 进程和 OpenSearch 之间的后端通信</li><li>Dashboards 启动比较慢（2-3 分钟），startup probe 允许最多 200 秒，耐心等待。</li></ul><h3 id="第三步：配置-OpenSearch-Security（后端验证）"><a href="#第三步：配置-OpenSearch-Security（后端验证）" class="headerlink" title="第三步：配置 OpenSearch Security（后端验证）"></a>第三步：配置 OpenSearch Security（后端验证）</h3><p>Dashboards 配好后，用户能跳转到 Cognito 登录了，但 OpenSearch 集群还不认识 JWT Token，会返回 401。需要在 OpenSearch 的 Security 配置中添加 OIDC 认证域。</p><p>虽然 OpenSearch Helm chart 提供了 <code>securityConfig</code> 字段，理论上可以在 values.yaml 里注入安全配置。但实际上我的配置并没有成功，所以我使用了<code>securityadmin</code> 工具直接写入 security index的方式，这个办法不需要重启 OpenSearch，立即生效：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">kubectl <span class="built_in">exec</span> -it opensearch-cluster-master-0 -- bash -c <span class="string">&#x27;</span></span><br><span class="line"><span class="string">cat &gt; /tmp/config.yml &lt;&lt; EOF</span></span><br><span class="line"><span class="string">_meta:</span></span><br><span class="line"><span class="string">  type: &quot;config&quot;</span></span><br><span class="line"><span class="string">  config_version: 2</span></span><br><span class="line"><span class="string">config:</span></span><br><span class="line"><span class="string">  dynamic:</span></span><br><span class="line"><span class="string">    authc:</span></span><br><span class="line"><span class="string">      basic_internal_auth_domain:</span></span><br><span class="line"><span class="string">        http_enabled: true</span></span><br><span class="line"><span class="string">        transport_enabled: true</span></span><br><span class="line"><span class="string">        order: 0</span></span><br><span class="line"><span class="string">        http_authenticator:</span></span><br><span class="line"><span class="string">          type: basic</span></span><br><span class="line"><span class="string">          challenge: false</span></span><br><span class="line"><span class="string">        authentication_backend:</span></span><br><span class="line"><span class="string">          type: internal</span></span><br><span class="line"><span class="string">      openid_auth_domain:</span></span><br><span class="line"><span class="string">        http_enabled: true</span></span><br><span class="line"><span class="string">        transport_enabled: true</span></span><br><span class="line"><span class="string">        order: 1</span></span><br><span class="line"><span class="string">        http_authenticator:</span></span><br><span class="line"><span class="string">          type: openid</span></span><br><span class="line"><span class="string">          challenge: false</span></span><br><span class="line"><span class="string">          config:</span></span><br><span class="line"><span class="string">            subject_key: email</span></span><br><span class="line"><span class="string">            roles_key: cognito:groups</span></span><br><span class="line"><span class="string">            openid_connect_url: https://cognito-idp.&lt;region&gt;.amazonaws.com/&lt;user-pool-id&gt;/.well-known/openid-configuration</span></span><br><span class="line"><span class="string">            required_audience: &lt;app-client-id&gt;</span></span><br><span class="line"><span class="string">        authentication_backend:</span></span><br><span class="line"><span class="string">          type: noop</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"><span class="string">/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \</span></span><br><span class="line"><span class="string">  -f /tmp/config.yml \</span></span><br><span class="line"><span class="string">  -t config \</span></span><br><span class="line"><span class="string">  -icl \</span></span><br><span class="line"><span class="string">  -nhnv \</span></span><br><span class="line"><span class="string">  -cacert /usr/share/opensearch/config/root-ca.pem \</span></span><br><span class="line"><span class="string">  -cert /usr/share/opensearch/config/kirk.pem \</span></span><br><span class="line"><span class="string">  -key /usr/share/opensearch/config/kirk-key.pem&#x27;</span></span><br></pre></td></tr></table></figure><p>关键参数说明：</p><ul><li><code>subject_key: email</code> — 用 JWT 中的 email 字段作为用户名，而不是默认的 <code>sub</code>（一串 UUID）</li><li><code>roles_key: cognito:groups</code> — 从 Cognito 用户组映射 OpenSearch 角色</li><li><code>basic_internal_auth_domain</code> — 保留 Basic Auth，让 admin 用户和 Fluent Bit 等内部服务仍可用用户名密码认证</li><li><code>securityadmin.sh</code> 通过 TLS 客户端证书（kirk.pem）认证，直接操作底层索引，写入后 OpenSearch 自动热加载</li></ul><h3 id="第四步：映射用户权限"><a href="#第四步：映射用户权限" class="headerlink" title="第四步：映射用户权限"></a>第四步：映射用户权限</h3><p>联合登陆的用户登录后默认没有任何权限，接下来需要做角色映射：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">&#123;</span><br><span class="line">  &quot;error&quot;: &#123;</span><br><span class="line">    &quot;root_cause&quot;: [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;type&quot;: &quot;security_exception&quot;,</span><br><span class="line">        &quot;reason&quot;: &quot;no permissions for [cluster:monitor/health] and User [name=1@a.com, backend_roles=[], requestedTenant=null]&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    &quot;type&quot;: &quot;security_exception&quot;,</span><br><span class="line">    &quot;reason&quot;: &quot;no permissions for [cluster:monitor/health] and User [name=1@a.com, backend_roles=[], requestedTenant=null]&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;status&quot;: 403</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2026/04/02/1775128300117-aed1a870-0725-42f9-8f33-49c6a7aadaa7.png"></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">curl -sk -X PUT https://localhost:9200/_plugins/_security/api/rolesmapping/all_access \</span><br><span class="line">  -u <span class="string">&#x27;admin:&lt;your-password&gt;&#x27;</span> \</span><br><span class="line">  -H <span class="string">&#x27;Content-Type: application/json&#x27;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;users&quot;: [&quot;your-email@example.com&quot;]&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>生产环境建议通过Single Sign On用户组来映射角色，而不是直接映射单个用户。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/04/02/1775128271091-f73df9cd-e931-41de-98f9-6058d1028a28.png"></p><h3 id="第五步：Dashboard和OpenSearch集群验证JWT"><a href="#第五步：Dashboard和OpenSearch集群验证JWT" class="headerlink" title="第五步：Dashboard和OpenSearch集群验证JWT"></a>第五步：Dashboard和OpenSearch集群验证JWT</h3><p>由于Pod在k8s集群里，所以我们使用kubectl做端口转发。</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">kubectl port-forward svc/dashboards-opensearch-dashboards 5601:5601</span><br></pre></td></tr></table></figure><p>浏览器打开 <code>http://localhost:5601</code>，应该会自动跳转到 Cognito 登录页面。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2026/04/02/1775128415224-aacb3c9f-1775-4d4a-8136-4d6d4d957970.png"></p><p>同样，OpenSearch也要做端口转发：</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">kubectl port-forward svc/opensearch-cluster-master 9200:9200</span><br></pre></td></tr></table></figure><p>然后就可以使用Python脚本来进行验证了，通过Cognito API得到JWT，然后在请求OpenSearch的时候在header里带上Authorization Header。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> boto3, hmac, hashlib, base64, requests</span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算 SECRET_HASH（App Client 有 Secret 时必须带）</span></span><br><span class="line">secret_hash = base64.b64encode(</span><br><span class="line">    hmac.new(</span><br><span class="line">        <span class="string">&#x27;&lt;client-secret&gt;&#x27;</span>.encode(),</span><br><span class="line">        (<span class="string">&#x27;&lt;username&gt;&#x27;</span> + <span class="string">&#x27;&lt;client-id&gt;&#x27;</span>).encode(),</span><br><span class="line">        hashlib.sha256</span><br><span class="line">    ).digest()</span><br><span class="line">).decode()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取 ID Token</span></span><br><span class="line">client = boto3.client(<span class="string">&#x27;cognito-idp&#x27;</span>, region_name=<span class="string">&#x27;&lt;region&gt;&#x27;</span>)</span><br><span class="line">resp = client.initiate_auth(</span><br><span class="line">    ClientId=<span class="string">&#x27;&lt;client-id&gt;&#x27;</span>,</span><br><span class="line">    AuthFlow=<span class="string">&#x27;USER_PASSWORD_AUTH&#x27;</span>,</span><br><span class="line">    AuthParameters=&#123;</span><br><span class="line">        <span class="string">&#x27;USERNAME&#x27;</span>: <span class="string">&#x27;&lt;username&gt;&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;PASSWORD&#x27;</span>: <span class="string">&#x27;&lt;password&gt;&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;SECRET_HASH&#x27;</span>: secret_hash</span><br><span class="line">    &#125;</span><br><span class="line">)</span><br><span class="line">token = resp[<span class="string">&#x27;AuthenticationResult&#x27;</span>][<span class="string">&#x27;IdToken&#x27;</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用 OpenSearch API</span></span><br><span class="line">r = requests.get(</span><br><span class="line">    <span class="string">&#x27;https://localhost:9200/_cluster/health?pretty&#x27;</span>,</span><br><span class="line">    headers=&#123;<span class="string">&#x27;Authorization&#x27;</span>: <span class="string">f&#x27;Bearer <span class="subst">&#123;token&#125;</span>&#x27;</span>&#125;,</span><br><span class="line">    verify=<span class="literal">False</span></span><br><span class="line">)</span><br><span class="line"><span class="built_in">print</span>(r.text)</span><br></pre></td></tr></table></figure><h3 id="总结梳理"><a href="#总结梳理" class="headerlink" title="总结梳理"></a>总结梳理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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"># 1. 部署 OpenSearch（不带 securityConfig）</span></span><br><span class="line">helm install opensearch-cluster opensearch/opensearch \</span><br><span class="line">  --<span class="built_in">set</span> replicas=2 \</span><br><span class="line">  --<span class="built_in">set</span> <span class="string">&#x27;envFrom[0].secretRef.name=opensearch-admin-secret&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 等待 pod 就绪</span></span><br><span class="line">kubectl get pods -w</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 注入 OIDC 安全配置（热加载，无需重启）</span></span><br><span class="line">kubectl <span class="built_in">exec</span> -it opensearch-cluster-master-0 -- bash -c <span class="string">&#x27;...(securityadmin 命令)...&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 部署 Dashboards</span></span><br><span class="line">helm upgrade --install dashboards opensearch/opensearch-dashboards -f dashboards-values.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 映射用户权限</span></span><br><span class="line">curl -sk -X PUT https://localhost:9200/_plugins/_security/api/rolesmapping/all_access \</span><br><span class="line">  -u <span class="string">&#x27;admin:&lt;password&gt;&#x27;</span> \</span><br><span class="line">  -H <span class="string">&#x27;Content-Type: application/json&#x27;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;users&quot;: [&quot;your-email@example.com&quot;]&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 端口转发并访问</span></span><br><span class="line">kubectl port-forward svc/dashboards-opensearch-dashboards 5601:5601</span><br></pre></td></tr></table></figure><p>感谢有了单点登录，让我们管理OpenSearch的时候更加丝滑～</p>]]></content>
    
    
    <summary type="html">让 OpenSearch 支持 SSO：接入 OpenID Connect 认证</summary>
    
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/AWS/"/>
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/categories/AWS/OpenSearch/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/tags/OpenSearch/"/>
    
  </entry>
  
  <entry>
    <title>是和尚，就可以传经讲道吗？</title>
    <link href="https://blog.no-claw.com/posts/7a4f0ae5/"/>
    <id>https://blog.no-claw.com/posts/7a4f0ae5/</id>
    <published>2026-03-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>十年前，在火车上遇到一个和尚。</p><p>他除了穿着黑色的僧袍，剃了头发，余下的大致与常人没差，当然也用智能手机用微信，和常人一样搭乘火车出行。这事过去太久了，以至于忘记了和他聊起来的契机。大和尚问我是不是从事科技相关的工作，想让我帮他答疑手机卡顿的问题。</p><p>后面慢慢不知怎的就扯到了其他方面，大和尚开示给我讲人的念头到底多少多少，过午不食云云，甚至兴致上来对现在大学生礼崩乐坏周公之礼也评论一番，说孩子是孽，生来就是要赎罪，要用念佛号来压制大脑里浮起的念头。然后提倡我来教育这些年轻人。嗯 ，突然有被传教的感觉。剩下的就是不要杀生吃肉，因为蔬菜没有生命之类的话了。简单交流下来，只知道他这番话，这与我那时的所说修习的正念训练是不一致的，但是我不想争论。</p><p>这种话题自然会吸引到周围的人，他们亲切的称呼为和尚为大师，颇有唐僧见佛就拜的意味。而乘坐绿皮车的大多是普通的苦命人。晚上的车厢时而有觥筹交错，能听见酒鬼们的互相吹牛，而早上又被小孩子的哭闹声或者打游戏的声音吵醒，奶奶辈的啰嗦从来是不停的。甚至偶尔还需要乘务员来调节，乘务员对我说，我就在隔壁，如果车上发生了什么事，就过来找我。</p><p>这和尚讲的起劲，他又把刚才对我讲的话和车里这些人讲述了一遍。他甚至不忘时不时的补上一句，”这些不是我说的，是开悟的大和尚说的。“</p><p>在他的“粉丝团”里，比较典型的有个中年妇女，在我看来，她并不算面善，甚至脸上写满了祥林嫂式的生活。她说老公信佛，在家斋戒，当对外的应酬则很无奈，然后把陈年的烂谷子的事情都倒出来，求大师指点迷津。于是这和尚的布道由此而来，因此人愈来愈多，好像在争着赶集。</p><p>我前面所说，这一车大多是苦命人，被命运的车轮推动着做一些身不由己但力求活着的事情，他们忘了曾经的梦想，今日被惦记明日的言行念头所淹没，明日又会担忧后面的生活，或者干脆认命不再挣扎，承认这辈子就这样，就可以问心无愧从而得到一夕安寝。对他们来说，只要不违背公序良俗，再多的爱恨情仇，也不会触犯法律红线从而带来杀身之祸。倘若苍天有情，因为一个名不见经传和尚的三两闲言碎语就可以轻松改变，愚者开悟，恶人回头。那我之前坚持的善恶之辩又算是什么呢？</p><p>这些人的故事，让我想起了李碧华在《胭脂扣》中的一句话，<strong>在香港，任何一个凡俗的市民，毕生宏愿都是置业成家安居，然后老死。</strong></p><p>但我不是在香港，是在北京。这里也是人生百态，比如餐馆里刚刚出狱的光头，在饭桌眉飞色舞的给同乡伙伴讲述是当时是如何声泪俱下的给狱警求情，甚至避免了在里面用纳税人的钱了过一辈子的事情。甚至还能听见富有二人组在讨论难以想象住宅楼被邻居的鸡飞狗跳所影响的生活，然后说这是是无法忍受的，要住就做别墅，再来一个小院，才是生活。然后继续对北京的某个菜系的难以下咽的继续滔滔不绝的吐槽。不知道这里啊是不是个明星，不过除非像杨紫一样来国贸办活动贴海报，否则就算线下遇到也绝对认不出来。</p><p>我已经无心再听和尚给他们”答疑解惑”了,他如果有什么手机电脑问题，倒是可以来找我请教。盘古开天辟地之后，眼睛变成了日月，血液变成了江河，身上的跳蚤变成了人类。从一朝闻道开始，想要被救赎还要身体力行吧。所以儒家讲：“非独贤者有是心也，人皆有之，贤者能勿丧耳。”，道家讲“知不可奈何而安之若命，唯有德者能之”。</p><p>他的朋友圈每天都在发重复的忏悔，好似应了他关于孩子是孽的论述，甚至包括他自己，也许他不再需要恪守托钵乞食，过午不食这一传统，剩下的只是做大众视野里和尚该做的事。我不爱看，所以屏蔽了。说不定哪天他去考取一个博士的学位，这一切才能够更加顺理成章吧。世人对于和尚的期许是普度众生，那和尚就可以删人微信吗？</p><p>我沉吟良久，这好似鲁迅《狂人日记》书缝里的字了，只是于我而言，看到的只有四个字 — 这不矛盾。</p>]]></content>
    
    
    <summary type="html">是和尚，就可以传经讲道吗？</summary>
    
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 OpenSearch</title>
    <link href="https://blog.no-claw.com/posts/3c76c368/"/>
    <id>https://blog.no-claw.com/posts/3c76c368/</id>
    <published>2026-03-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇我们用 Helm 部署了 OpenSearch 集群和 Dashboards，这篇接着讲怎么用 Fluent Bit 把 Kubernetes 中 Nginx 的日志采集到 OpenSearch，并在 Dashboards 里查看和过滤。</p><p>本文假设你已经有一个运行中的 OpenSearch 集群，如果没有可以参考上一篇文章。</p><h3 id="部署一个-Nginx-用于测试"><a href="#部署一个-Nginx-用于测试" class="headerlink" title="部署一个 Nginx 用于测试"></a>部署一个 Nginx 用于测试</h3><p>先部署一个简单的 Nginx 作为日志来源：</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">kubectl create deployment nginx --image=nginx</span><br><span class="line">kubectl expose deployment nginx --port=80</span><br></pre></td></tr></table></figure><h3 id="安装-Fluent-Bit"><a href="#安装-Fluent-Bit" class="headerlink" title="安装 Fluent Bit"></a>安装 Fluent Bit</h3><p>添加 Helm 仓库：</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">helm repo add fluent https://fluent.github.io/helm-charts</span><br><span class="line">helm repo update</span><br></pre></td></tr></table></figure><span id="more"></span><p>Fluent Bit 的配置比较长，建议用 values 文件管理。创建 <code>fluent-bit-values.yaml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">config:</span></span><br><span class="line">  <span class="attr">inputs:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    [INPUT]</span></span><br><span class="line"><span class="string">        Name tail</span></span><br><span class="line"><span class="string">        Path /var/log/containers/*.log</span></span><br><span class="line"><span class="string">        Exclude_Path /var/log/containers/fluent-bit*.log,/var/log/containers/opensearch*.log,/var/log/containers/dashboards*.log,/var/log/containers/*_kube-system_*.log</span></span><br><span class="line"><span class="string">        multiline.parser docker, cri</span></span><br><span class="line"><span class="string">        Tag kube.*</span></span><br><span class="line"><span class="string">        Mem_Buf_Limit 5MB</span></span><br><span class="line"><span class="string">        Skip_Long_Lines On</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">    [<span class="string">INPUT</span>]</span><br><span class="line">        <span class="string">Name</span> <span class="string">systemd</span></span><br><span class="line">        <span class="string">Tag</span> <span class="string">host.*</span></span><br><span class="line">        <span class="string">Systemd_Filter</span> <span class="string">_SYSTEMD_UNIT=kubelet.service</span></span><br><span class="line">        <span class="string">Read_From_Tail</span> <span class="string">On</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">filters:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    [FILTER]</span></span><br><span class="line"><span class="string">        Name kubernetes</span></span><br><span class="line"><span class="string">        Match kube.*</span></span><br><span class="line"><span class="string">        Merge_Log On</span></span><br><span class="line"><span class="string">        Keep_Log Off</span></span><br><span class="line"><span class="string">        K8S-Logging.Parser On</span></span><br><span class="line"><span class="string">        K8S-Logging.Exclude On</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">  <span class="attr">outputs:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    [OUTPUT]</span></span><br><span class="line"><span class="string">        Name            opensearch</span></span><br><span class="line"><span class="string">        Match           *</span></span><br><span class="line"><span class="string">        Host            opensearch-cluster-master</span></span><br><span class="line"><span class="string">        Port            9200</span></span><br><span class="line"><span class="string">        HTTP_User       admin</span></span><br><span class="line"><span class="string">        HTTP_Passwd     &lt;your-password&gt;</span></span><br><span class="line"><span class="string">        Index           nginx-logs</span></span><br><span class="line"><span class="string">        Logstash_Format On</span></span><br><span class="line"><span class="string">        Logstash_Prefix nginx-logs</span></span><br><span class="line"><span class="string">        tls             On</span></span><br><span class="line"><span class="string">        tls.verify      Off</span></span><br><span class="line"><span class="string">        Suppress_Type_Name On</span></span><br></pre></td></tr></table></figure><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm install fluent-bit fluent/fluent-bit -f fluent-bit-values.yaml</span><br></pre></td></tr></table></figure><h3 id="配置详解"><a href="#配置详解" class="headerlink" title="配置详解"></a>配置详解</h3><p>这里解释几个关键配置项：</p><h4 id="Exclude-Path-—-排除不需要的日志"><a href="#Exclude-Path-—-排除不需要的日志" class="headerlink" title="Exclude_Path — 排除不需要的日志"></a>Exclude_Path — 排除不需要的日志</h4><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">Exclude_Path /var/log/containers/fluent-bit*.log,/var/log/containers/opensearch*.log,/var/log/containers/dashboards*.log,/var/log/containers/*_kube-system_*.log</span><br></pre></td></tr></table></figure><p>这是我们踩过的一个坑。Fluent Bit 作为 DaemonSet 运行在每个节点上，默认会采集 <code>/var/log/containers/</code> 下所有容器的日志。如果节点上跑了 OpenSearch、Dashboards 这些日志量很大的服务，Fluent Bit 的内存缓冲区（<code>Mem_Buf_Limit 5MB</code>）会很快被填满，导致其他日志（比如 Nginx）发送失败，表现为不断报 <code>failed to flush chunk</code> 错误。</p><p>另外特别要注意排除 Fluent Bit 自己的日志。如果开了 debug 模式，Fluent Bit 会疯狂输出日志，而这些日志又会被自己的 tail input 读取，形成死循环，直接把缓冲区撑爆。</p><h4 id="Logstash-Format-—-按日期轮换索引"><a href="#Logstash-Format-—-按日期轮换索引" class="headerlink" title="Logstash_Format — 按日期轮换索引"></a>Logstash_Format — 按日期轮换索引</h4><figure class="highlight plaintext"><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">Logstash_Format On</span><br><span class="line">Logstash_Prefix nginx-logs</span><br></pre></td></tr></table></figure><p>开启后索引名会变成 <code>nginx-logs-2026.03.23</code> 这样按天轮换，方便后续做索引生命周期管理。在 Dashboards 里创建 index pattern 时用 <code>nginx-logs-*</code> 即可匹配所有日期的索引。</p><h4 id="kubernetes-filter-—-添加-Pod-元数据"><a href="#kubernetes-filter-—-添加-Pod-元数据" class="headerlink" title="kubernetes filter — 添加 Pod 元数据"></a>kubernetes filter — 添加 Pod 元数据</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[FILTER]</span><br><span class="line">    Name kubernetes</span><br><span class="line">    Match kube.*</span><br><span class="line">    Merge_Log On</span><br></pre></td></tr></table></figure><p>这个 filter 会自动给每条日志加上 <code>kubernetes.pod_name</code>、<code>kubernetes.namespace_name</code>、<code>kubernetes.container_name</code> 等字段，这样在 Dashboards 里就可以按 Pod 名称过滤日志了。</p><h3 id="产生测试日志"><a href="#产生测试日志" class="headerlink" title="产生测试日志"></a>产生测试日志</h3><p>Nginx 在没有请求的时候不会输出 access log，需要手动访问一下：</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">kubectl port-forward svc/nginx 8080:80 &amp;</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> $(<span class="built_in">seq</span> 1 100); <span class="keyword">do</span> curl -s http://localhost:8080 &gt; /dev/null; <span class="keyword">done</span></span><br></pre></td></tr></table></figure><h3 id="查看日志索引"><a href="#查看日志索引" class="headerlink" title="查看日志索引"></a>查看日志索引</h3><p>我们能够看到已经馋看了nginx-log这个索引，并且按照时间轮换。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2026/03/25/1774436171914-37ffc32f-482d-4750-b5a4-3033fbb020bc.png"></p><h3 id="在-Dashboards-中查看"><a href="#在-Dashboards-中查看" class="headerlink" title="在 Dashboards 中查看"></a>在 Dashboards 中查看</h3><ol><li>打开 OpenSearch Dashboards（<code>kubectl port-forward svc/dashboards-opensearch-dashboards 5601:5601</code>）</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2026/03/25/1774436232934-0f243cf1-4364-4bc6-92ea-3df298390142.png"></p><ol start="3"><li>进入 Stack Management → Index Patterns，创建 <code>nginx-logs-*</code> 的 index pattern。</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/03/25/1774436247235-960af1a7-aac0-402c-980a-7d8cab813816.png"></p><p>时间字段选 <code>@timestamp</code></p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2026/03/25/1774436214805-08d2901b-efa5-4f2d-bc19-f630bdc8df38.png"></p><ol start="3"><li>进入 Discover，选择刚创建的 index pattern</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/03/25/1774436370240-1529b228-6430-4c70-b981-af821a74b078.png"></p><ol start="4"><li>在左侧 Available fields 找到 <code>kubernetes.pod_name</code>或者其他字段（我这里用的imaeg的名字），点击过滤即可按 Pod 查看日志</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2026/03/25/1774436409525-cf3a6870-23fa-43a3-97ce-3cc45d395a91.png"></p><p>如果在 Available fields 里看不到 kubernetes 相关字段，去 Index Patterns 里点刷新按钮（🔄）刷新字段列表。</p><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><h4 id="Fluent-Bit-一直报-failed-to-flush-chunk"><a href="#Fluent-Bit-一直报-failed-to-flush-chunk" class="headerlink" title="Fluent Bit 一直报 failed to flush chunk"></a>Fluent Bit 一直报 failed to flush chunk</h4><p>大概率是缓冲区被其他 Pod 的日志挤满了。用 <code>Exclude_Path</code> 排除不需要的日志，或者加大 <code>Mem_Buf_Limit</code>。</p><h4 id="Dashboards-里看不到-Nginx-日志但能看到其他-Pod"><a href="#Dashboards-里看不到-Nginx-日志但能看到其他-Pod" class="headerlink" title="Dashboards 里看不到 Nginx 日志但能看到其他 Pod"></a>Dashboards 里看不到 Nginx 日志但能看到其他 Pod</h4><p>检查 Nginx Pod 所在节点的 Fluent Bit 是否正常。可能是那个节点的 Fluent Bit 启动时 OpenSearch 还没 ready，导致一直 flush 失败。删掉那个 Fluent Bit Pod 让 DaemonSet 重建即可。</p><h4 id="手动-curl-OpenSearch-能写入但-Fluent-Bit-写不进去"><a href="#手动-curl-OpenSearch-能写入但-Fluent-Bit-写不进去" class="headerlink" title="手动 curl OpenSearch 能写入但 Fluent Bit 写不进去"></a>手动 curl OpenSearch 能写入但 Fluent Bit 写不进去</h4><p>确认 Fluent Bit output 配置里的 <code>Host</code>、<code>Port</code>、<code>HTTP_User</code>、<code>HTTP_Passwd</code>、<code>tls</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">kubectl run test-curl --image=curlimages/curl --<span class="built_in">rm</span> -it --restart=Never -- curl -sk https://opensearch-cluster-master:9200 -u <span class="string">&#x27;admin:&lt;your-password&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Fluent Bit + OpenSearch 是 Kubernetes 上轻量级日志方案的经典组合。核心要点是合理配置 <code>Exclude_Path</code> 控制采集范围，避免无关日志挤占缓冲区。配合 <code>Logstash_Format</code> 做索引轮换，再加上 kubernetes filter 提供的 Pod 元数据，就能在 Dashboards 里方便地按 Pod、Namespace 等维度过滤和分析日志了。</p>]]></content>
    
    
    <summary type="html">在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 OpenSearch</summary>
    
    
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/categories/OpenSearch/"/>
    
    
    <category term="Kubernetes" scheme="https://blog.no-claw.com/tags/Kubernetes/"/>
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/tags/OpenSearch/"/>
    
    <category term="Fluent Bit" scheme="https://blog.no-claw.com/tags/Fluent-Bit/"/>
    
  </entry>
  
  <entry>
    <title>月度TODO</title>
    <link href="https://blog.no-claw.com/posts/2d786d98/"/>
    <id>https://blog.no-claw.com/posts/2d786d98/</id>
    <published>2026-03-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="2026-3-月度TODO"><a href="#2026-3-月度TODO" class="headerlink" title="2026.3 月度TODO"></a>2026.3 月度TODO</h2><ul><li><input disabled="" type="checkbox"> 极限科技 4篇文章</li><li><input disabled="" type="checkbox"> 懒猫微服 4篇文章</li><li><input disabled="" type="checkbox"> 生活感悟1篇</li><li><input disabled="" type="checkbox"> 纯技术文章 2篇</li><li><input disabled="" type="checkbox"> 英语</li></ul>]]></content>
    
    
    <summary type="html">每月内容创作计划和待办事项清单。</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Go 代理项目：14 周每日学习路线</title>
    <link href="https://blog.no-claw.com/posts/e6d8d61c/"/>
    <id>https://blog.no-claw.com/posts/e6d8d61c/</id>
    <published>2026-03-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="项目全貌"><a href="#项目全貌" class="headerlink" title="项目全貌"></a>项目全貌</h2><p>一个从零开始的 Go 反向代理，分四个阶段进化成一个生产级 SRE 工具。每个阶段解决一类真实问题，边学语法边写功能。每周按 5 个工作日拆分，周末可以复习或补进度。</p><h2 id="先说一下-gRPC"><a href="#先说一下-gRPC" class="headerlink" title="先说一下 gRPC"></a>先说一下 gRPC</h2><p>你原始规划里提到了”协议升级：把 HTTP 请求转成 gRPC”。简单解释一下这是什么。</p><p>gRPC 是 Google 搞的一个远程过程调用（RPC）框架。普通的 HTTP API 用 JSON 传数据，人能读懂但体积大、解析慢。gRPC 用一种叫 Protocol Buffers（protobuf）的二进制格式传数据，体积小、速度快，而且跑在 HTTP&#x2F;2 上，天然支持多路复用和流式传输。</p><p>在微服务架构里，服务之间的内部通信经常用 gRPC 而不是 HTTP+JSON，因为性能好很多。代理做”协议升级”的意思是：外部客户端发普通 HTTP+JSON 请求进来，代理把它转成 gRPC 格式发给后端微服务。这样外部用户不需要知道后端用的是 gRPC，代理帮你做了翻译。</p><p>gRPC 和普通 HTTP API 的区别在于三个层面。传输层：普通 HTTP API 跑在 HTTP&#x2F;1.1 上，一个连接同一时间只能处理一个请求；gRPC 跑在 HTTP&#x2F;2 上，一个连接可以同时处理多个请求（多路复用）。序列化格式：普通 API 用 JSON（文本），gRPC 用 protobuf（二进制），同样的数据 protobuf 通常比 JSON 小 3-10 倍。接口定义：gRPC 用 <code>.proto</code> 文件严格定义接口，用 <code>protoc</code> 编译器自动生成代码，客户端和服务端的数据结构是编译时确定的。</p><p>gRPC 代理涉及 HTTP&#x2F;2 帧处理、protobuf 序列化&#x2F;反序列化、服务定义文件编译、动态反射调用等，复杂度比较高，放在第 13-14 周作为进阶目标。</p><hr><h2 id="阶段一：隐身斗篷——透明代理-协议转换（第-1-4-周）"><a href="#阶段一：隐身斗篷——透明代理-协议转换（第-1-4-周）" class="headerlink" title="阶段一：隐身斗篷——透明代理 &#x2F; 协议转换（第 1-4 周）"></a>阶段一：隐身斗篷——透明代理 &#x2F; 协议转换（第 1-4 周）</h2><h3 id="第-1-周：Go-基础-读配置"><a href="#第-1-周：Go-基础-读配置" class="headerlink" title="第 1 周：Go 基础 + 读配置"></a>第 1 周：Go 基础 + 读配置</h3><p>这周不急着写代理，先把 Go 的基础语法打牢。</p><p><strong>Day 1：环境搭建 + A Tour of Go（上）</strong></p><p>安装 Go，配置好编辑器。打开 A Tour of Go（<a href="https://go.dev/tour/%EF%BC%89%EF%BC%8C%E8%BF%87">https://go.dev/tour/），过</a> Basics 部分：变量声明（<code>var</code> 和 <code>:=</code>）、基本类型（int、string、bool）、函数定义（<code>func</code>）、多返回值。不用全记住，有个印象就行。</p><p><strong>Day 2：A Tour of Go（中）</strong></p><p>继续过 Flow Control（for、if、switch）和 More Types 的前半部分（指针、struct）。Go 没有 while 循环，所有循环都用 <code>for</code>。struct 是 Go 里组织数据的核心方式，类似其他语言的 class 但没有继承。</p><p><strong>Day 3：A Tour of Go（下）</strong></p><p>过 More Types 的后半部分（slice、map、range）和 Methods and Interfaces 的前半部分。slice 和 map 会贯穿整个项目。如果 interface 看不懂没关系，后面第 9 周会专门学。</p><p><strong>Day 4：第一个程序 + struct 和 json</strong></p><p>关掉教程，自己写一个 <code>main.go</code>。定义一个 <code>Config</code> struct，包含 <code>ListenPort int</code> 和 <code>TargetURL string</code> 两个字段，加上 json tag（比如 <code>`json:&quot;listen_port&quot;`</code>）。创建一个 <code>config.json</code> 文件，用 <code>os.ReadFile</code> 读取，用 <code>json.Unmarshal</code> 映射到 struct，用 <code>fmt.Println</code> 打印出来。确认 <code>go run main.go</code> 能跑通。</p><p><strong>Day 5：错误处理 + 练习</strong></p><p>Go 的错误处理模式是 <code>if err != nil { log.Fatal(err) }</code>，从今天开始习惯它。给 Day 4 的代码加上完整的错误处理：文件不存在怎么办、JSON 格式错误怎么办。然后做个小练习：改 config.json 的内容，故意写错 JSON 格式，看程序怎么报错。学会用 <code>log.Printf</code> 输出带格式的日志。</p><h3 id="第-2-周：HTTP-服务器-请求转发"><a href="#第-2-周：HTTP-服务器-请求转发" class="headerlink" title="第 2 周：HTTP 服务器 + 请求转发"></a>第 2 周：HTTP 服务器 + 请求转发</h3><p><strong>Day 1：启动一个 HTTP 服务器</strong></p><p>学 <code>net/http</code> 包。用 <code>http.HandleFunc(&quot;/&quot;, handler)</code> 注册一个处理函数，用 <code>http.ListenAndServe(&quot;:8080&quot;, nil)</code> 启动服务器。handler 函数的签名是 <code>func(w http.ResponseWriter, r *http.Request)</code>，先让它返回一个固定字符串 “hello proxy”。用 <code>curl localhost:8080</code> 验证。</p><p><strong>Day 2：理解 http.Request 和 http.ResponseWriter</strong></p><p>在 handler 里打印请求的各种信息：<code>r.Method</code>（GET&#x2F;POST）、<code>r.URL.Path</code>（路径）、<code>r.Header</code>（所有请求头）、<code>r.RemoteAddr</code>（客户端地址）。用 <code>w.WriteHeader(200)</code> 设置状态码，用 <code>w.Write([]byte(&quot;hello&quot;))</code> 写响应体。用 curl 发不同的请求（GET、POST、带 Header），观察打印结果。</p><p><strong>Day 3：用 http.Client 发送请求</strong></p><p>学 <code>http.NewRequest</code> 构造请求和 <code>http.Client{}</code> 的 <code>Do</code> 方法发送请求。写一个小程序：向 <code>http://httpbin.org/get</code> 发 GET 请求，打印响应状态码和 Body。理解 <code>resp.Body</code> 是一个 <code>io.ReadCloser</code>，必须用 <code>defer resp.Body.Close()</code> 关闭。</p><p><strong>Day 4：实现请求转发</strong></p><p>把前几天学的串起来：handler 收到请求后，用 <code>http.NewRequest</code> 构造一个新请求（方法、URL、Body 都从原始请求复制），用 <code>http.Client</code> 发送到 config.json 里配置的目标地址，用 <code>io.Copy(w, resp.Body)</code> 把上游响应写回客户端。</p><p><strong>Day 5：处理请求头复制 + 测试</strong></p><p>转发时还需要把原始请求的 Header 复制到新请求上（遍历 <code>r.Header</code>，逐个 <code>Set</code> 到新请求）。同时把上游响应的 Header 也复制回客户端。用 <code>curl localhost:8080/get</code> 测试，确认返回的内容和直接访问 httpbin.org&#x2F;get 一样。这就是一个能跑的最简代理了。</p><h3 id="第-3-周：伪装-Host-HTTPS-卸载"><a href="#第-3-周：伪装-Host-HTTPS-卸载" class="headerlink" title="第 3 周：伪装 Host + HTTPS 卸载"></a>第 3 周：伪装 Host + HTTPS 卸载</h3><p><strong>Day 1：伪装 Host</strong></p><p>在转发逻辑里，转发之前加两行：<code>req.Header.Set(&quot;Host&quot;, targetHost)</code> 和 <code>req.Host = targetHost</code>。这样目标服务器看到的 Host 头就是你指定的值，而不是 localhost:8080。用 httpbin.org&#x2F;headers 验证：转发后返回的 headers 里 Host 应该是 httpbin.org。</p><p><strong>Day 2：理解 TLS&#x2F;HTTPS + 生成自签名证书</strong></p><p>花半天理解 HTTPS 的工作原理：客户端和服务器通过 TLS 握手协商加密方式，服务器出示证书证明身份，之后所有数据加密传输。”HTTPS 卸载”的意思是代理对外提供 HTTPS，对内用 HTTP 转发给后端。然后用 openssl 生成自签名证书：<code>openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes</code>。</p><p><strong>Day 3：启动 HTTPS 服务器</strong></p><p>学 <code>crypto/tls</code> 包，用 <code>tls.LoadX509KeyPair(&quot;cert.pem&quot;, &quot;key.pem&quot;)</code> 加载证书。把 <code>http.ListenAndServe</code> 换成 <code>http.ListenAndServeTLS(&quot;:8443&quot;, &quot;cert.pem&quot;, &quot;key.pem&quot;, nil)</code>。用 <code>curl -k https://localhost:8443/get</code> 测试（<code>-k</code> 跳过证书验证）。转发到后端仍然用 HTTP，HTTPS 卸载完成。</p><blockquote><p>Arch Linux 提示：你可以用 <code>mkcert</code> 代替 openssl 生成证书（<code>pacman -S mkcert</code>）。mkcert 会自动创建一个本地信任的 CA 并安装到系统信任链里，生成的证书浏览器和 curl 都不会报红，测试时不需要 <code>-k</code> 参数。运行 <code>mkcert -install</code> 初始化 CA，然后 <code>mkcert localhost 127.0.0.1</code> 生成证书。</p></blockquote><p><strong>Day 4：同时监听 HTTP 和 HTTPS</strong></p><p>实际场景中代理可能需要同时提供 HTTP（8080）和 HTTPS（8443）。用两个 goroutine 分别启动两个服务器：<code>go http.ListenAndServe(...)</code> 和 <code>http.ListenAndServeTLS(...)</code>。这里会第一次用到 <code>goroutine</code>，但很简单，就是在函数调用前加个 <code>go</code>。</p><p><strong>Day 5：学习 httputil.ReverseProxy + 回顾</strong></p><p>Go 标准库自带了 <code>net/http/httputil.ReverseProxy</code>，它就是一个现成的反向代理实现。读一下它的源码（不长），理解它怎么处理请求复制、响应转发、错误处理。对比你自己写的转发逻辑，看看有什么可以改进的。不需要替换你的代码，理解思路就行。回顾这三周的代码，整理一下结构。</p><h3 id="第-4-周：WebSocket-协议升级"><a href="#第-4-周：WebSocket-协议升级" class="headerlink" title="第 4 周：WebSocket 协议升级"></a>第 4 周：WebSocket 协议升级</h3><p>这周比较有挑战，不要急。</p><p><strong>Day 1：理解 WebSocket 协议</strong></p><p>不写代码。WebSocket 通过一次 HTTP 升级握手（Upgrade: websocket）建立连接后，双方可以随时互发消息，连接一直保持。适合聊天、实时推送这类场景。用浏览器开发者工具打开一个 WebSocket 网站（比如 websocket.org 的 echo 测试），观察握手过程和消息收发。</p><p><strong>Day 2：搭建 WebSocket 测试服务</strong></p><p>安装 <code>gorilla/websocket</code> 库（<code>go get github.com/gorilla/websocket</code>），这里会学到 <code>go get</code> 安装第三方库和 <code>go.mod</code> 依赖管理。写一个最简单的 WebSocket echo 服务器：客户端发什么，服务端原样返回。安装 <code>wscat</code>（<code>npm install -g wscat</code>）测试连接。</p><p><strong>Day 3：学习 http.Hijacker 接口</strong></p><p><code>http.Hijacker</code> 能从 HTTP 连接中”劫持”底层的 TCP 连接。调用 <code>w.(http.Hijacker).Hijack()</code> 后，你拿到的是一个裸的 <code>net.Conn</code>，HTTP 层不再管这个连接了。写一个小例子：收到请求后 Hijack 连接，直接往 TCP 连接上写一段文字，用 curl 观察效果。</p><p><strong>Day 4：实现 WebSocket 代理转发</strong></p><p>代理收到 WebSocket 升级请求后，先向后端建立 WebSocket 连接，然后用两个 goroutine 做双向转发：一个从客户端读数据写到后端，一个从后端读数据写到客户端。用 <code>wscat</code> 通过代理连接 Day 2 的 echo 服务，验证双向通信。</p><p><strong>Day 5：错误处理 + 连接关闭</strong></p><p>处理各种边界情况：客户端断开时关闭后端连接，后端断开时通知客户端。加上日志，打印 WebSocket 连接的建立和关闭事件。如果 Day 4 没搞定，今天继续调试，不要急着往下走。</p><hr><h2 id="阶段二：安检员——认证与过滤（第-5-6-周）"><a href="#阶段二：安检员——认证与过滤（第-5-6-周）" class="headerlink" title="阶段二：安检员——认证与过滤（第 5-6 周）"></a>阶段二：安检员——认证与过滤（第 5-6 周）</h2><p>原来的鉴权和过滤分了两周，但 Token 鉴权和 UA 黑名单的语法很简单（就是 if-else 和 slice 遍历），所以压缩成一周，把省下来的时间给内容过滤和动态路由多留一天缓冲。</p><h3 id="第-5-周：Token-鉴权-UA-黑名单-内容过滤"><a href="#第-5-周：Token-鉴权-UA-黑名单-内容过滤" class="headerlink" title="第 5 周：Token 鉴权 + UA 黑名单 + 内容过滤"></a>第 5 周：Token 鉴权 + UA 黑名单 + 内容过滤</h3><p><strong>Day 1：学 slice 和 for…range + 实现 Token 鉴权</strong></p><p>Go 的 slice（切片）是动态数组，用 <code>[]string{&quot;a&quot;, &quot;b&quot;}</code> 创建，用 <code>len()</code> 获取长度，用 <code>append()</code> 添加元素，用 <code>for i, v := range slice</code> 遍历。在 config.json 里加一个 <code>&quot;token&quot;: &quot;my-secret-token&quot;</code> 字段。在转发逻辑的最前面加检查：<code>r.Header.Get(&quot;X-Token&quot;)</code> 读取请求头，不等于配置里的 token 就返回 403。用 <code>curl -H &quot;X-Token: my-secret-token&quot; localhost:8080/get</code> 测试通过，不带 header 测试返回 403。</p><p><strong>Day 2：实现 UA 黑名单</strong></p><p>在 config.json 里加 <code>&quot;ua_blacklist&quot;: [&quot;BadBot&quot;, &quot;Scrapy&quot;]</code>。读取请求的 User-Agent 头，用 <code>for...range</code> 遍历黑名单，用 <code>strings.Contains</code>（配合 <code>strings.ToLower</code> 做大小写不敏感匹配）检查 UA 是否包含黑名单关键词。命中就返回 403。把鉴权和 UA 检查抽成独立函数：<code>func checkToken(r *http.Request, token string) bool</code> 和 <code>func checkUA(r *http.Request, blacklist []string) bool</code>，保持 handler 干净。</p><p><strong>Day 3：学 map + bytes 包 + 理解 Body 一次性读取问题</strong></p><p>Go 的 <code>map[string]string</code> 是键值对集合，用 <code>m[key]</code> 读取，用 <code>for k, v := range m</code> 遍历。然后理解一个关键问题：HTTP 请求的 Body 是一个 <code>io.ReadCloser</code>，只能读一次。读完之后如果还要转发，必须用 <code>bytes.NewReader(bodyBytes)</code> 重新包装成一个新的 Reader 赋回 <code>r.Body</code>。写个小例子验证这个行为。</p><p><strong>Day 4：实现内容过滤（敏感词拦截）</strong></p><p>在 config.json 里加 <code>&quot;sensitive_words&quot;: [&quot;password&quot;, &quot;secret&quot;]</code>。在转发之前，用 <code>io.ReadAll(r.Body)</code> 读取整个 Body，用 <code>bytes.Contains</code> 检查是否包含敏感词。如果包含，返回 400。如果不包含，用 <code>bytes.NewReader(bodyBytes)</code> 重新包装 Body 继续转发。用 <code>curl -X POST -d &quot;my password is 123&quot; localhost:8080/post</code> 测试拦截。</p><p><strong>Day 5：测试所有安检功能</strong></p><p>同时测试 Token 鉴权、UA 黑名单、内容过滤的各种组合：不带 Token、带错误 Token、带正确 Token 但 UA 在黑名单里、Token 和 UA 都通过但 Body 有敏感词。加上日志，打印每个被拒绝的请求的原因。确保所有情况都正确处理。</p><h3 id="第-6-周：动态路由-轮询负载均衡"><a href="#第-6-周：动态路由-轮询负载均衡" class="headerlink" title="第 6 周：动态路由 + 轮询负载均衡"></a>第 6 周：动态路由 + 轮询负载均衡</h3><p>原来路由和负载均衡分了两周，但路由的核心就是 map + 前缀匹配，负载均衡的核心就是取模运算，合并到一周刚好。</p><p><strong>Day 1：实现动态路由</strong></p><p>把 config.json 的单个 target_url 改成路由表：<code>&quot;routes&quot;: {&quot;/api&quot;: &quot;http://backend-a:8080&quot;, &quot;/static&quot;: &quot;http://backend-b:8080&quot;}</code>。在 handler 里用 <code>strings.HasPrefix(r.URL.Path, prefix)</code> 遍历路由表找到匹配的后端。注意 map 遍历顺序是随机的，所以把路由前缀放进一个 slice 里按长度从长到短排序，先匹配最具体的路由。加一个默认路由兜底。</p><p><strong>Day 2：学方法（method）和指针 + 定义 LoadBalancer struct</strong></p><p>Go 的 struct 可以绑定方法：<code>func (lb *LoadBalancer) Next() string</code>。<code>*LoadBalancer</code> 是指针接收者，方法可以修改 struct 的字段。如果用值接收者，每次调用都是在副本上操作，修改不会生效。定义一个 <code>LoadBalancer</code> struct，包含 <code>backends []string</code> 和 <code>counter int64</code>，写一个 <code>Next()</code> 方法返回 <code>backends[counter % len(backends)]</code> 并递增 counter。</p><p><strong>Day 3：用 atomic 实现并发安全 + 改造路由表</strong></p><p>学 <code>sync/atomic</code> 包，把 <code>counter++</code> 换成 <code>atomic.AddInt64(&amp;lb.counter, 1)</code>。然后改造 config.json 的路由表，支持多后端：<code>&quot;routes&quot;: {&quot;/api&quot;: [&quot;http://pod1:8080&quot;, &quot;http://pod2:8080&quot;, &quot;http://pod3:8080&quot;]}</code>。Config struct 对应改成 <code>Routes map[string][]string</code>。每个路由前缀对应一个 LoadBalancer 实例。</p><p><strong>Day 4：集成到代理 + 测试轮询</strong></p><p>在 handler 里先匹配路由，再调用对应 LoadBalancer 的 Next() 获取后端地址。启动 3 个简单后端（分别返回 “I am pod1”、”I am pod2”、”I am pod3”），用 <code>for i in {1..9}; do curl localhost:8080/api; done</code> 连续请求 9 次，确认响应依次是 pod1、pod2、pod3 循环。</p><blockquote><p>进阶思考：基础的 Round Robin 假设所有后端性能一样，但 SRE 场景下后端节点性能不一是很常见的。如果有余力，可以尝试实现加权轮询（Weighted Round Robin）——在 config.json 里给每个后端配一个权重，权重高的分到更多请求。或者实现最少连接（Least Connections）——每次选当前活跃连接数最少的后端。这两个算法不复杂，但能让你的代理在真实场景下更实用。不急，可以留到 12 周收尾时再加。</p></blockquote><p><strong>Day 5：整合测试阶段一和阶段二</strong></p><p>这是一个里程碑。同时验证所有已完成的功能：HTTPS 卸载、Host 伪装、WebSocket 转发、Token 鉴权、UA 黑名单、内容过滤、动态路由、轮询负载均衡。整理代码结构，把不同功能拆到不同的 <code>.go</code> 文件里（比如 <code>auth.go</code>、<code>router.go</code>、<code>loadbalancer.go</code>）。</p><hr><h2 id="阶段三：交警——流量调度与限流（第-7-9-周）"><a href="#阶段三：交警——流量调度与限流（第-7-9-周）" class="headerlink" title="阶段三：交警——流量调度与限流（第 7-9 周）"></a>阶段三：交警——流量调度与限流（第 7-9 周）</h2><p>这是整个路线里最陡的坡，goroutine、channel、select、context、Mutex 集中在这里。原来 2 周，现在给 3 周，多出来的一周用来消化并发模型。</p><h3 id="第-7-周：Go-并发模型-限流器"><a href="#第-7-周：Go-并发模型-限流器" class="headerlink" title="第 7 周：Go 并发模型 + 限流器"></a>第 7 周：Go 并发模型 + 限流器</h3><p><strong>Day 1：学 goroutine 基础</strong></p><p>goroutine 是 Go 的轻量级线程，用 <code>go func() { ... }()</code> 启动。写几个小练习：启动 5 个 goroutine 分别打印不同的数字，观察输出顺序是随机的。理解 <code>time.Sleep</code> 可以让 goroutine 等待，但主 goroutine 退出后所有子 goroutine 都会被杀掉。</p><p><strong>Day 2：学 channel 基础</strong></p><p>channel 是 goroutine 之间传递数据的管道。<code>ch := make(chan int)</code> 创建无缓冲 channel，<code>ch &lt;- 42</code> 发送（会阻塞直到有人接收），<code>v := &lt;-ch</code> 接收（会阻塞直到有人发送）。写一个小练习：一个 goroutine 往 channel 发 10 个数字，主 goroutine 接收并打印。然后学带缓冲的 channel：<code>ch := make(chan int, 5)</code>，缓冲没满时发送不阻塞。</p><p><strong>Day 3：学 select + time.Ticker</strong></p><p><code>select</code> 同时监听多个 channel，谁先有数据就执行谁的分支，类似 switch 但用于 channel。<code>time.Ticker</code> 是一个定时器，<code>ticker := time.NewTicker(time.Second)</code> 每秒往 <code>ticker.C</code> 这个 channel 发一个信号。写一个小程序：用 select 同时监听一个 ticker（每秒打印 “tick”）和一个 quit channel（收到信号就退出）。</p><p><strong>Day 4：学 sync.Map + 实现限流器核心逻辑</strong></p><p><code>sync.Map</code> 是并发安全的 map，不需要手动加锁。用 <code>m.Store(key, value)</code> 存，<code>m.Load(key)</code> 取，<code>m.Range(func(k, v) bool { ... })</code> 遍历。创建一个 <code>RateLimiter</code> struct，内部用 <code>sync.Map</code> 存储每个 IP 的请求计数。<code>Allow(ip string) bool</code> 方法：读取当前计数，超过 5 就返回 false，否则加 1 返回 true。启动一个 goroutine 监听 Ticker，每秒清零所有计数。</p><p><strong>Day 5：集成限流器到代理 + 测试</strong></p><p>用 <code>net.SplitHostPort(r.RemoteAddr)</code> 提取客户端 IP，调用 <code>limiter.Allow(ip)</code>，返回 false 就响应 <code>429 Too Many Requests</code>。用 <code>ab -n 100 -c 10 http://localhost:8080/api</code> 压测，观察日志确认同一个 IP 每秒只有前 5 个请求通过。</p><h3 id="第-8-周：熔断与降级"><a href="#第-8-周：熔断与降级" class="headerlink" title="第 8 周：熔断与降级"></a>第 8 周：熔断与降级</h3><p><strong>Day 1：理解熔断器状态机</strong></p><p>不写代码，先在纸上画状态图。三种状态：Closed（关闭，正常转发）→ 连续错误超过阈值 → Open（打开，直接返回 503）→ 等待超时到期 → Half-Open（半开，放一个请求试探）→ 试探成功回到 Closed，失败回到 Open。理解每个状态转换的触发条件。</p><p><strong>Day 2：学 sync.Mutex + 定义 CircuitBreaker struct</strong></p><p><code>sync.Mutex</code> 是互斥锁，<code>mu.Lock()</code> 加锁，<code>mu.Unlock()</code> 解锁，通常配合 <code>defer mu.Unlock()</code> 使用。定义一个 <code>CircuitBreaker</code> struct，包含：<code>state string</code>（当前状态）、<code>failCount int</code>（连续失败次数）、<code>threshold int</code>（阈值，比如 10）、<code>lastFailTime time.Time</code>、<code>timeout time.Duration</code>（恢复等待时间，比如 60 秒）、<code>mu sync.Mutex</code>。</p><p><strong>Day 3：学 context.WithTimeout + 实现 Allow 和 RecordResult</strong></p><p><code>context.WithTimeout</code> 给操作设超时：<code>ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)</code>，别忘了 <code>defer cancel()</code>。实现 <code>Allow() bool</code>：Closed 状态返回 true；Open 状态检查是否超过 timeout，超过了切到 Half-Open 返回 true，没超过返回 false；Half-Open 状态返回 true（放一个请求试探）。实现 <code>RecordResult(success bool)</code>：成功则重置 failCount 切到 Closed；失败则递增 failCount，超过阈值切到 Open。</p><p><strong>Day 4：集成到代理</strong></p><p>在转发逻辑里加入熔断器：转发前调用 <code>cb.Allow()</code>，返回 false 就直接响应 503 “系统繁忙”。转发请求时用 <code>req.WithContext(ctx)</code> 加上 5 秒超时。转发后根据响应状态码调用 <code>cb.RecordResult(statusCode &lt; 500)</code>。</p><p><strong>Day 5：测试熔断和恢复</strong></p><p>启动后端，正常请求确认通过。关掉后端，连续发请求，观察日志：前 10 个请求报错，第 11 个开始直接返回 503。等 60 秒后再发请求，观察半开状态的试探行为。重启后端，确认代理自动恢复转发。如果有 bug，今天调试。</p><h3 id="第-9-周：并发复习-耗时统计-defer"><a href="#第-9-周：并发复习-耗时统计-defer" class="headerlink" title="第 9 周：并发复习 + 耗时统计 + defer"></a>第 9 周：并发复习 + 耗时统计 + defer</h3><p>这周节奏放缓，消化前两周的并发知识，同时开始做可观测性。</p><p><strong>Day 1：并发复习</strong></p><p>回顾 goroutine、channel、select、Mutex 的用法。重新读一遍限流器和熔断器的代码，确保每一行都理解。如果有不清楚的地方，去 Go by Example（<a href="https://gobyexample.com/%EF%BC%89%E6%9F%A5%E5%AF%B9%E5%BA%94%E7%9A%84%E4%BE%8B%E5%AD%90%E3%80%82">https://gobyexample.com/）查对应的例子。</a></p><p><strong>Day 2：学 defer + time 包做耗时统计</strong></p><p><code>defer</code> 在函数退出时执行，不管是正常 return 还是 panic。在 handler 开头写 <code>start := time.Now()</code>，然后 <code>defer func() { log.Printf(&quot;[%s] %s → %d (%v)&quot;, r.Method, r.URL.Path, statusCode, time.Since(start)) }()</code>。注意 statusCode 需要用一个自定义的 ResponseWriter 包装器来捕获（因为标准的 ResponseWriter 写完状态码后你读不回来）。</p><p><strong>Day 3：写一个 StatusCapture ResponseWriter</strong></p><p>定义一个 struct 包装 <code>http.ResponseWriter</code>，重写 <code>WriteHeader</code> 方法来记录状态码。这里会更深入理解 Go 的 interface：你的 struct 只要实现了 <code>http.ResponseWriter</code> 的三个方法（<code>Header()</code>、<code>Write()</code>、<code>WriteHeader()</code>），就可以当 ResponseWriter 用。</p><p><strong>Day 4：学 interface + 理解 http.Handler</strong></p><p>Go 的 <code>http.Handler</code> 接口只有一个方法：<code>ServeHTTP(w http.ResponseWriter, r *http.Request)</code>。任何实现了这个方法的 struct 都自动满足接口，不需要显式声明。理解中间件模式的签名：<code>func(next http.Handler) http.Handler</code>——接收一个 Handler，返回一个新的 Handler。</p><p><strong>Day 5：写第一个中间件（Logging）</strong></p><p>把 Day 2 的耗时统计逻辑封装成一个中间件函数。在调用 <code>next.ServeHTTP(w, r)</code> 之前记录开始时间，之后计算耗时并打印。验证中间件能正确包装现有的 handler。</p><hr><h2 id="阶段四：监控探针——可观测性（第-10-14-周）"><a href="#阶段四：监控探针——可观测性（第-10-14-周）" class="headerlink" title="阶段四：监控探针——可观测性（第 10-14 周）"></a>阶段四：监控探针——可观测性（第 10-14 周）</h2><h3 id="第-10-周：Prometheus-指标-全量日志存数据库"><a href="#第-10-周：Prometheus-指标-全量日志存数据库" class="headerlink" title="第 10 周：Prometheus 指标 + 全量日志存数据库"></a>第 10 周：Prometheus 指标 + 全量日志存数据库</h3><p><strong>Day 1：引入 Prometheus 客户端库</strong></p><p>运行 <code>go get github.com/prometheus/client_golang/prometheus</code> 和 <code>go get github.com/prometheus/client_golang/prometheus/promhttp</code>。注册两个指标：一个 Counter <code>proxy_requests_total</code>（按状态码和路径分标签），一个 Histogram <code>proxy_request_duration_seconds</code>（记录延迟分布）。在 Logging 中间件里调用 <code>counter.Inc()</code> 和 <code>histogram.Observe(elapsed.Seconds())</code>。</p><p><strong>Day 2：暴露 &#x2F;metrics 端点 + 验证</strong></p><p>用 <code>http.Handle(&quot;/metrics&quot;, promhttp.Handler())</code> 暴露指标端点。启动代理，发几个请求，然后 <code>curl localhost:8080/metrics</code> 查看输出。你应该能看到 Prometheus 格式的指标数据。如果你的 K3s 集群里跑着 Prometheus，可以配置它来抓取这个端点。</p><p><strong>Day 3：学 database&#x2F;sql + 连接 SQLite</strong></p><p>Go 的 <code>database/sql</code> 是标准库的数据库接口，具体数据库通过驱动实现。先用 SQLite（轻量，不需要装数据库服务）：<code>go get github.com/mattn/go-sqlite3</code>。用 <code>sql.Open(&quot;sqlite3&quot;, &quot;./proxy.db&quot;)</code> 连接，用 <code>db.Exec</code> 创建一张 <code>request_logs</code> 表（字段：id、timestamp、method、url、status_code、duration_ms、request_body）。</p><blockquote><p>避坑：SQLite 在并发写时会报 <code>database is locked</code> 错误。你的代理是高并发的，必须在连接后设置 <code>db.SetMaxOpenConns(1)</code> 限制为单连接，让写操作排队。这能解决问题但会成为瓶颈。如果后续发现 SQLite 扛不住，可以换成 PostgreSQL（驱动用 <code>github.com/jackc/pgx/v5/stdlib</code>），连接字符串改一下就行，<code>database/sql</code> 的接口是通用的，业务代码几乎不用改。</p></blockquote><p><strong>Day 4：学 io.TeeReader + 实现同步写库</strong></p><p><code>io.TeeReader(r, w)</code> 返回一个 Reader，从它读取数据时，数据会同时写入 w。用它复制请求 Body：<code>var bodyBuf bytes.Buffer</code>，<code>teeReader := io.TeeReader(r.Body, &amp;bodyBuf)</code>。转发完成后，用 <code>db.Exec(&quot;INSERT INTO request_logs ...&quot;)</code> 把请求信息写入数据库。先做同步写，验证数据能正确存入。</p><p><strong>Day 5：用带缓冲 channel 改成异步写库</strong></p><p>同步写库会拖慢请求。创建 <code>logCh := make(chan LogEntry, 1000)</code>，handler 里把日志条目扔进 channel 就返回，另一个 goroutine 从 channel 取数据写库。用 <code>select</code> 加 <code>default</code> 分支处理 channel 满的情况——丢弃日志而不是阻塞请求。加一个 Prometheus Counter 记录丢弃数量。</p><h3 id="第-11-周：流量镜像"><a href="#第-11-周：流量镜像" class="headerlink" title="第 11 周：流量镜像"></a>第 11 周：流量镜像</h3><p><strong>Day 1：理解流量镜像的原理</strong></p><p>流量镜像就是把线上请求复制一份发到测试环境。生产后端正常处理并返回响应给客户端，测试后端也收到一模一样的请求但响应被丢弃。关键原则：镜像是”发了就忘”（fire and forget），镜像失败不能影响正常请求。</p><p><strong>Day 2：用 bytes.Buffer 复制请求 Body</strong></p><p>用 <code>io.ReadAll(r.Body)</code> 把 Body 读出来存到 <code>[]byte</code>，然后用 <code>bytes.NewReader(bodyBytes)</code> 分别创建两个 Reader——一个给正常转发，一个给镜像请求。在 config.json 里加 <code>&quot;mirror_target&quot;: &quot;http://test-backend:8080&quot;</code>。</p><p><strong>Day 3：用 goroutine 异步发送镜像请求</strong></p><p>用 <code>http.NewRequest</code> 构造一个新请求（复制方法、URL、Header、Body），<code>go sendMirror(mirrorReq)</code> 异步发送。主流程不等待镜像结果。给镜像请求用 <code>context.WithTimeout</code> 设独立的 3 秒超时。</p><p><strong>Day 4：学 recover + 保护镜像 goroutine</strong></p><p><code>recover</code> 能捕获 goroutine 里的 panic。在镜像 goroutine 开头加 <code>defer func() { if r := recover(); r != nil { log.Printf(&quot;mirror panic: %v&quot;, r) } }()</code>，防止镜像崩溃影响主进程。</p><p><strong>Day 5：测试镜像功能</strong></p><p>启动两个后端：一个”生产”后端返回正常响应，一个”测试”后端只打印收到的请求。发请求到代理，确认客户端收到生产后端的响应，同时测试后端也出现了请求日志。关掉测试后端，确认镜像失败不影响正常响应。</p><h3 id="第-12-周：中间件链整合-收尾"><a href="#第-12-周：中间件链整合-收尾" class="headerlink" title="第 12 周：中间件链整合 + 收尾"></a>第 12 周：中间件链整合 + 收尾</h3><p><strong>Day 1：把所有功能重构为中间件</strong></p><p>把鉴权、限流、UA 黑名单、内容过滤、Prometheus 指标、日志等逻辑都重构成独立的中间件函数，签名统一为 <code>func(next http.Handler) http.Handler</code>。比如 <code>func TokenAuth(token string) func(http.Handler) http.Handler</code>，检查 Token 不通过就返回 403，通过就调用 <code>next.ServeHTTP(w, r)</code>。</p><p><strong>Day 2：串联中间件链</strong></p><p>写一个 <code>Chain</code> 函数把多个中间件串起来。最终效果：请求依次经过 <code>日志 → Prometheus → 限流 → Token 认证 → UA 黑名单 → 内容过滤 → 路由 → 负载均衡 → 熔断 → 转发</code>。每一层都可以决定继续传递还是直接返回错误。</p><p><strong>Day 3：学 flag 包 + 命令行参数</strong></p><p>用 <code>flag</code> 包支持命令行参数：<code>--config</code> 指定配置文件路径，<code>--port</code> 指定监听端口。调用 <code>flag.Parse()</code> 解析。这样启动代理时可以 <code>./proxy --config /etc/proxy/config.json --port 9090</code>。</p><p><strong>Day 4：实现优雅关闭（Graceful Shutdown）</strong></p><p>用 <code>os/signal</code> 监听 <code>SIGINT</code> 和 <code>SIGTERM</code>。收到信号后调用 <code>server.Shutdown(ctx)</code> 停止接受新连接，等待在途请求处理完再退出。同时关闭数据库连接、停止日志 goroutine。</p><blockquote><p>Docker 化：写一个多阶段构建（Multi-stage Build）的 Dockerfile。第一阶段用 <code>golang:1.22</code> 编译，第二阶段用 <code>scratch</code> 或 <code>gcr.io/distroless/static</code> 作为底座，只复制编译好的二进制文件进去。Go 编译出来的是静态链接的单文件，打包出来的镜像可能只有 10-15MB。然后 <code>docker build -t go-proxy .</code> 构建，<code>docker run -p 8080:8080 go-proxy</code> 运行，确认代理在容器里正常工作。这个镜像可以直接推到你的 K3s 集群里跑。</p></blockquote><p><strong>Day 5：写单元测试 + 最终验收</strong></p><p>学 <code>testing</code> 包。给限流器的 <code>Allow()</code> 写测试：连续调用 6 次，前 5 次返回 true 第 6 次返回 false。给熔断器写测试：连续 RecordResult(false) 超过阈值后 Allow() 返回 false。给轮询写测试：3 个后端调用 6 次，每个出现 2 次。用 <code>go test ./...</code> 运行。然后做一次完整的端到端验收。</p><hr><h2 id="进阶：gRPC-协议升级（第-13-14-周）"><a href="#进阶：gRPC-协议升级（第-13-14-周）" class="headerlink" title="进阶：gRPC 协议升级（第 13-14 周）"></a>进阶：gRPC 协议升级（第 13-14 周）</h2><p>不需要重写代理。是在已有的中间件链和路由系统上加一个新的路由规则：当请求路径匹配 <code>/grpc/...</code> 时，走 gRPC 转发 handler；其他路径还是走原来的 HTTP 转发。限流、鉴权、Prometheus 指标这些中间件照常工作，gRPC 转换只是在链的末端替换了转发方式。</p><h3 id="第-13-周：protobuf-gRPC-基础"><a href="#第-13-周：protobuf-gRPC-基础" class="headerlink" title="第 13 周：protobuf + gRPC 基础"></a>第 13 周：protobuf + gRPC 基础</h3><p><strong>Day 1：安装 protoc + 写第一个 .proto 文件</strong></p><p>安装 Protocol Buffers 编译器（macOS 用 <code>brew install protobuf</code>），安装 Go 插件（<code>go install google.golang.org/protobuf/cmd/protoc-gen-go@latest</code> 和 <code>go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest</code>）。写一个 <code>greeter.proto</code>，定义一个 Greeter 服务，有一个 SayHello 方法，请求参数是 name（string），返回值是 message（string）。</p><p><strong>Day 2：生成 Go 代码 + 写 gRPC 服务端</strong></p><p>运行 <code>protoc --go_out=. --go-grpc_out=. greeter.proto</code> 生成两个 <code>.go</code> 文件。写一个 gRPC 服务端：实现生成的接口，用 <code>grpc.NewServer()</code> 创建服务器，监听 50051 端口。学 <code>google.golang.org/grpc</code> 这个核心库。</p><p><strong>Day 3：写 gRPC 客户端 + 验证通信</strong></p><p>写一个 gRPC 客户端：用 <code>grpc.Dial</code> 连接服务端，调用 SayHello 方法。运行服务端和客户端，确认通信正常。理解完整链路：<code>.proto</code> 定义 → <code>protoc</code> 生成代码 → 服务端实现 → 客户端调用。</p><p><strong>Day 4：学 JSON ↔ protobuf 转换</strong></p><p>学 <code>google.golang.org/protobuf/encoding/protojson</code> 包。<code>protojson.Unmarshal</code> 把 JSON 转成 protobuf 消息，<code>protojson.Marshal</code> 把 protobuf 转回 JSON。写个小程序验证：JSON <code>{&quot;name&quot;: &quot;world&quot;}</code> → protobuf HelloRequest → JSON，确认数据一致。</p><p><strong>Day 5：学 gRPC 反射 + grpcurl</strong></p><p>gRPC 反射让代理在运行时查询后端支持哪些方法和参数结构，不需要编译时知道 <code>.proto</code> 定义。在 gRPC 服务端开启反射（<code>reflection.Register(server)</code>）。安装 <code>grpcurl</code>（<code>brew install grpcurl</code>），用它测试：<code>grpcurl -plaintext localhost:50051 list</code> 列出服务，<code>grpcurl -plaintext -d &#39;{&quot;name&quot;: &quot;world&quot;}&#39; localhost:50051 greeter.Greeter/SayHello</code> 调用方法。</p><h3 id="第-14-周：在代理中实现-HTTP→gRPC-转换"><a href="#第-14-周：在代理中实现-HTTP→gRPC-转换" class="headerlink" title="第 14 周：在代理中实现 HTTP→gRPC 转换"></a>第 14 周：在代理中实现 HTTP→gRPC 转换</h3><p><strong>Day 1：学动态调用 API</strong></p><p>学 <code>google.golang.org/grpc</code> 的 <code>Invoke</code> 方法和 <code>google.golang.org/protobuf/types/dynamicpb</code> 包。<code>dynamicpb</code> 允许在不 import 生成代码的情况下，根据运行时获取的服务描述符动态构造 protobuf 消息。这是通用 gRPC 代理的关键。</p><blockquote><p>性能考量：动态反射转换（JSON ↔ Protobuf）虽然通用，但每次请求都要通过反射查询服务描述符，性能开销很大。可以加一个 LRU Cache（用 <code>github.com/hashicorp/golang-lru</code> 或自己用 <code>sync.Map</code> + 链表实现），缓存已经解析过的服务描述符和方法描述符。第一次请求某个方法时走反射，之后直接从缓存取，减少反射频率。</p></blockquote><p><strong>Day 2：在代理中加 gRPC 路由</strong></p><p>加一个新路由规则 <code>/grpc/&lt;service&gt;/&lt;method&gt;</code>。当请求匹配时：读取 JSON Body → 通过反射获取目标方法的描述符 → 用 <code>protojson.Unmarshal</code> 转成动态 protobuf 消息 → 用 <code>grpc.Invoke</code> 发送到后端 → 把 protobuf 响应用 <code>protojson.Marshal</code> 转回 JSON → 返回给客户端。</p><p><strong>Day 3：处理 gRPC 错误码映射</strong></p><p>gRPC 有自己的错误码（NotFound、Internal、Unavailable 等），需要映射到 HTTP 状态码：NotFound → 404，Internal → 500，Unavailable → 503。学 <code>google.golang.org/grpc/status</code> 包提取错误码。</p><p><strong>Day 4：端到端测试</strong></p><p>启动 gRPC 后端（开启反射），启动代理。用 <code>curl -X POST -d &#39;{&quot;name&quot;: &quot;world&quot;}&#39; localhost:8080/grpc/greeter.Greeter/SayHello</code> 发送 HTTP+JSON 请求，确认代理翻译成 gRPC 发给后端，再翻译回 JSON 返回。客户端全程只用了 HTTP 和 JSON。</p><p><strong>Day 5：最终收尾</strong></p><p>验证 gRPC 路由和 HTTP 路由共存：<code>/grpc/...</code> 走 gRPC 转发，其他路径走 HTTP 转发。确认中间件链（限流、鉴权、Prometheus）对两种路由都生效。整理代码，更新 README。</p><hr><h2 id="14-周总览"><a href="#14-周总览" class="headerlink" title="14 周总览"></a>14 周总览</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">阶段一：隐身斗篷（透明代理 / 协议转换）— 4 周</span><br><span class="line">  第 1 周   Go 基础语法 + 读 JSON 配置（多留 1 天给零基础缓冲）</span><br><span class="line">  第 2 周   HTTP 服务器 + 请求转发</span><br><span class="line">  第 3 周   伪装 Host + HTTPS 卸载</span><br><span class="line">  第 4 周   WebSocket 协议升级</span><br><span class="line"></span><br><span class="line">阶段二：安检员（认证与过滤）— 2 周（压缩，语法简单）</span><br><span class="line">  第 5 周   Token 鉴权 + UA 黑名单 + 内容过滤</span><br><span class="line">  第 6 周   动态路由 + 轮询负载均衡</span><br><span class="line"></span><br><span class="line">阶段三：交警（流量调度与限流）— 3 周（展开，并发是最陡的坡）</span><br><span class="line">  第 7 周   Go 并发模型（goroutine/channel/select）+ 限流器</span><br><span class="line">  第 8 周   熔断与降级（Mutex/context/状态机）</span><br><span class="line">  第 9 周   并发复习 + 耗时统计 + defer + 中间件模式入门</span><br><span class="line"></span><br><span class="line">阶段四：监控探针（可观测性）— 3 周</span><br><span class="line">  第 10 周  Prometheus 指标 + 全量日志存数据库</span><br><span class="line">  第 11 周  流量镜像</span><br><span class="line">  第 12 周  中间件链整合 + 优雅关闭 + 单元测试</span><br><span class="line"></span><br><span class="line">进阶：gRPC 协议升级 — 2 周</span><br><span class="line">  第 13 周  protobuf + gRPC 基础</span><br><span class="line">  第 14 周  在代理中实现 HTTP→gRPC 转换（不重写，加新路由）</span><br></pre></td></tr></table></figure><hr><h2 id="推荐资源"><a href="#推荐资源" class="headerlink" title="推荐资源"></a>推荐资源</h2><ul><li><a href="https://go.dev/tour/">A Tour of Go</a>：语法速通，第 1 周前三天过完。</li><li><a href="https://gobyexample.com/">Go by Example</a>：每个语法点一个可运行的例子，当字典查。</li><li><a href="https://gopl-zh.github.io/">《Go 语言圣经》</a>：系统学习，重点看 struct、interface、goroutine、channel 章节。</li><li>直接写代理项目：边做边学，遇到不会的语法再查，这是最快的路径。</li></ul><hr><h2 id="一句话"><a href="#一句话" class="headerlink" title="一句话"></a>一句话</h2><p>14 周后你手里会有一个能伪装 Host、能卸载 HTTPS、能转发 WebSocket 和 gRPC、能鉴权、能过滤、能路由、能负载均衡、能限流、能熔断、能出 Prometheus 指标、能全量记录日志、能镜像流量的 Go 代理——装进你封装的 Linux 发行版里，就是一套完整的自动化运维体系。</p>]]></content>
    
    
    <summary type="html">从零开始用 Go 写一个反向代理，14 周按工作日拆分的学习路线，边学语法边写功能，最终做成生产级 SRE 工具。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="Go" scheme="https://blog.no-claw.com/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 Easysearch</title>
    <link href="https://blog.no-claw.com/posts/8fee9a49/"/>
    <id>https://blog.no-claw.com/posts/8fee9a49/</id>
    <published>2026-03-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文基于 k3s + Easysearch 2.0.3 实测验证，从零开始搭建一套完整的日志收集方案。</p></blockquote><h3 id="什么是-Fluent-Bit"><a href="#什么是-Fluent-Bit" class="headerlink" title="什么是 Fluent Bit"></a>什么是 Fluent Bit</h3><p><a href="https://fluentbit.io/">Fluent Bit</a> 是一个轻量级的日志收集和转发工具，用 C 语言写的，内存占用极低（通常只需要几十 MB）。它的工作很简单：从某个地方读日志（INPUT），可选地处理一下（FILTER），然后发到某个地方（OUTPUT）。</p><figure class="highlight plaintext"><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">INPUT → FILTER → OUTPUT</span><br><span class="line">读日志    处理     发送</span><br></pre></td></tr></table></figure><p>常见用法：</p><ul><li>从文件读日志（<code>tail</code> 插件，类似 <code>tail -f</code>）</li><li>从容器 stdout 读日志</li><li>发送到 Elasticsearch &#x2F; Easysearch &#x2F; Kafka &#x2F; S3 等</li></ul><p>和 Fluentd 的区别：Fluent Bit 更轻量（C 语言 vs Ruby），适合作为 Agent 部署在每个节点或 Pod 里。Fluentd 功能更丰富，适合做日志聚合层。在 Kubernetes 场景下，Fluent Bit 是更常见的选择。</p><h3 id="什么是-Easysearch"><a href="#什么是-Easysearch" class="headerlink" title="什么是 Easysearch"></a>什么是 Easysearch</h3><p><a href="https://infinilabs.com/products/easysearch/">INFINI Easysearch</a> 是兼容 Elasticsearch API 的搜索引擎。Fluent Bit 的 <code>es</code>（Elasticsearch）输出插件可以直接对接，不需要改配置。简单理解：Easysearch 是 Elasticsearch 的国产替代品。</p><h3 id="为什么用-Sidecar-模式"><a href="#为什么用-Sidecar-模式" class="headerlink" title="为什么用 Sidecar 模式"></a>为什么用 Sidecar 模式</h3><p>本文用 Sidecar 模式部署 Fluent Bit：把它和 Nginx 放在同一个 Pod 里，共享日志目录。</p><p>另一种常见方式是 DaemonSet 模式：在每个节点上跑一个 Fluent Bit，收集该节点上所有 Pod 的 stdout 日志。DaemonSet 适合收集所有 Pod 的日志，Sidecar 适合收集特定应用的日志文件。</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Nginx Pod</span><br><span class="line">├── nginx 容器        → 产生访问日志 → /var/log/nginx/access.log</span><br><span class="line">└── fluent-bit 容器   → tail 日志文件 → 写入 Easysearch</span><br><span class="line">                                              ↓</span><br><span class="line">                                    curl 查询 nginx-logs-* 索引</span><br></pre></td></tr></table></figure><h3 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h3><ul><li>k3s 单节点（Ubuntu 24.04，30G 内存）</li><li>Helm 3</li><li>cert-manager（Easysearch 依赖）</li></ul><h4 id="安装-k3s"><a href="#安装-k3s" class="headerlink" title="安装 k3s"></a>安装 k3s</h4><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">curl -sfL https://get.k3s.io | sh -</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置 kubectl</span></span><br><span class="line"><span class="built_in">mkdir</span> -p ~/.kube</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 644 /etc/rancher/k3s/k3s.yaml</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> /etc/rancher/k3s/k3s.yaml ~/.kube/config</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> $(<span class="built_in">id</span> -u):$(<span class="built_in">id</span> -g) ~/.kube/config</span><br><span class="line">kubectl get nodes</span><br></pre></td></tr></table></figure><h4 id="安装-Helm"><a href="#安装-Helm" class="headerlink" title="安装 Helm"></a>安装 Helm</h4><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 https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash</span><br></pre></td></tr></table></figure><h4 id="设置内核参数"><a href="#设置内核参数" class="headerlink" title="设置内核参数"></a>设置内核参数</h4><p>Easysearch（基于 Lucene）需要较高的 mmap 限制：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sysctl -w vm.max_map_count=262144</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;vm.max_map_count=262144&quot;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> -a /etc/sysctl.conf</span><br></pre></td></tr></table></figure><h3 id="第一步：部署-Easysearch"><a href="#第一步：部署-Easysearch" class="headerlink" title="第一步：部署 Easysearch"></a>第一步：部署 Easysearch</h3><h4 id="1-1-准备工具"><a href="#1-1-准备工具" class="headerlink" title="1.1 准备工具"></a>1.1 准备工具</h4><p>更新helm仓库并且初始化cert-manager。</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">helm repo add infinilabs https://helm.infinilabs.com</span><br><span class="line">helm repo update</span><br><span class="line">kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 等所有 Pod Ready（约 30-60 秒）</span></span><br><span class="line">kubectl get pods -n cert-manager -w</span><br></pre></td></tr></table></figure><h3 id="1-2-创建命名空间和证书"><a href="#1-2-创建命名空间和证书" class="headerlink" title="1.2 创建命名空间和证书"></a>1.2 创建命名空间和证书</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><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">kubectl create namespace es</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建自签名 CA</span></span><br><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">EOF | kubectl -n es apply -f -</span></span><br><span class="line"><span class="string">apiVersion: cert-manager.io/v1</span></span><br><span class="line"><span class="string">kind: Issuer</span></span><br><span class="line"><span class="string">metadata:</span></span><br><span class="line"><span class="string">  name: easysearch-ca-issuer</span></span><br><span class="line"><span class="string">spec:</span></span><br><span class="line"><span class="string">  selfSigned: &#123;&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">apiVersion: cert-manager.io/v1</span></span><br><span class="line"><span class="string">kind: Certificate</span></span><br><span class="line"><span class="string">metadata:</span></span><br><span class="line"><span class="string">  name: easysearch-ca-certificate</span></span><br><span class="line"><span class="string">spec:</span></span><br><span class="line"><span class="string">  commonName: easysearch-ca-certificate</span></span><br><span class="line"><span class="string">  duration: 87600h0m0s</span></span><br><span class="line"><span class="string">  isCA: true</span></span><br><span class="line"><span class="string">  issuerRef:</span></span><br><span class="line"><span class="string">    kind: Issuer</span></span><br><span class="line"><span class="string">    name: easysearch-ca-issuer</span></span><br><span class="line"><span class="string">  privateKey:</span></span><br><span class="line"><span class="string">    algorithm: ECDSA</span></span><br><span class="line"><span class="string">    size: 256</span></span><br><span class="line"><span class="string">  renewBefore: 2160h0m0s</span></span><br><span class="line"><span class="string">  secretName: easysearch-ca-secret</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><h3 id="1-3-创建密码-Secret"><a href="#1-3-创建密码-Secret" class="headerlink" title="1.3 创建密码 Secret"></a>1.3 创建密码 Secret</h3><blockquote><p>⚠️ 密码必须包含至少 2 类字符（大写&#x2F;小写&#x2F;数字&#x2F;特殊字符），否则初始化会失败，Pod 直接 Exit Code 1 崩溃。</p></blockquote><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">kubectl create secret generic easysearch-secrets \</span><br><span class="line">  -n es --from-literal=ezs_password=<span class="string">&#x27;Admin123&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="1-4-Helm-安装-Easysearch"><a href="#1-4-Helm-安装-Easysearch" class="headerlink" title="1.4 Helm 安装 Easysearch"></a>1.4 Helm 安装 Easysearch</h3><p>使用厂家提供了helm Chart安装Easysearch，并且通过变量设置image的版本，截止目前，最新版本是2.0.3-2534。</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">helm repo add infinilabs https://helm.infinilabs.com --force-update</span><br><span class="line">helm repo update</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">helm install easysearch infinilabs/easysearch -n es \</span><br><span class="line">  --<span class="built_in">set</span> image.tag=2.0.3-2534</span><br></pre></td></tr></table></figure><p>然后就是等 Pod Running：</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">kubectl get pods -n es -w</span><br><span class="line"><span class="comment"># NAME           READY   STATUS    RESTARTS   AGE</span></span><br><span class="line"><span class="comment"># easysearch-0   1/1     Running   0          60s</span></span><br></pre></td></tr></table></figure><h3 id="1-5-验证-Easysearch"><a href="#1-5-验证-Easysearch" class="headerlink" title="1.5 验证 Easysearch"></a>1.5 验证 Easysearch</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></pre></td><td class="code"><pre><span class="line">kubectl port-forward -n es pod/easysearch-0 9200:9200 &amp;</span><br><span class="line"><span class="built_in">sleep</span> 3</span><br><span class="line">curl -s http://localhost:9200 -u admin:Admin123</span><br></pre></td></tr></table></figure><p>返回 JSON 集群信息即成功：</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;easysearch-0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;infinilabs&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;cluster_uuid&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;_eqbd5PbQ5WNpNVJFccNDA&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span> <span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;distribution&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;easysearch&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;number&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;2.0.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;distributor&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;INFINI Labs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build_hash&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;e6180819aedb3d4759cdbcd2c1b856a9635d4aff&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build_date&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;2026-01-16T09:44:14.477332385Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build_snapshot&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="attr">&quot;lucene_version&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;9.12.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;minimum_wire_lucene_version&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;8.7.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;minimum_lucene_index_compatibility_version&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;8.7.0&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;tagline&quot;</span> <span class="punctuation">:</span> <span class="string">&quot;You Know, For Easy Search!&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如果你的机器内存比较小（&lt; 4G）或者使用其他 storageClassName 需要修改配置：</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">helm install easysearch infinilabs/easysearch -n es \</span><br><span class="line">  --<span class="built_in">set</span> storageClassName=gp2 \</span><br><span class="line">  --<span class="built_in">set</span> resources.requests.memory=512Mi \</span><br><span class="line">  --<span class="built_in">set</span> resources.limits.memory=1536Mi \</span><br><span class="line">  --<span class="built_in">set</span> <span class="string">&quot;javaOpts=-Xms256m -Xmx256m&quot;</span></span><br></pre></td></tr></table></figure><h3 id="第二步：部署-Nginx-Fluent-Bit-Sidecar"><a href="#第二步：部署-Nginx-Fluent-Bit-Sidecar" class="headerlink" title="第二步：部署 Nginx + Fluent Bit Sidecar"></a>第二步：部署 Nginx + Fluent Bit Sidecar</h3><p>这个 YAML 包含三个 Kubernetes 资源（用 <code>---</code> 分隔）：</p><ol><li>ConfigMap：存放 Fluent Bit 的配置文件 <code>fluent-bit.conf</code></li><li>Deployment：定义一个 Pod，里面跑两个容器（nginx + fluent-bit sidecar）</li><li>Service：让集群内其他 Pod 可以通过 <code>http://nginx:80</code> 访问 Nginx</li></ol><h4 id="创建-nginx-fluentbit-yaml"><a href="#创建-nginx-fluentbit-yaml" class="headerlink" title="创建 nginx-fluentbit.yaml"></a>创建 nginx-fluentbit.yaml</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">&#x27;EOF&#x27;</span> &gt; nginx-fluentbit.yaml</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: ConfigMap</span><br><span class="line">metadata:</span><br><span class="line">  name: fluent-bit-config</span><br><span class="line">  namespace: default</span><br><span class="line">data:</span><br><span class="line">  fluent-bit.conf: |</span><br><span class="line">    [SERVICE]</span><br><span class="line">        Flush         3</span><br><span class="line">        Log_Level     info</span><br><span class="line">        Daemon        off</span><br><span class="line">    [INPUT]</span><br><span class="line">        Name              <span class="built_in">tail</span></span><br><span class="line">        Tag               nginx.access</span><br><span class="line">        Path              /var/log/nginx/access.log</span><br><span class="line">        DB                /var/log/flb_nginx.db</span><br><span class="line">        Mem_Buf_Limit     5MB</span><br><span class="line">        Refresh_Interval  5</span><br><span class="line">    [OUTPUT]</span><br><span class="line">        Name            es</span><br><span class="line">        Match           *</span><br><span class="line">        Host            easysearch.es.svc.cluster.local</span><br><span class="line">        Port            9200</span><br><span class="line">        HTTP_User       admin</span><br><span class="line">        HTTP_Passwd     Admin123</span><br><span class="line">        tls             Off</span><br><span class="line">        Logstash_Format On</span><br><span class="line">        Logstash_Prefix nginx-logs</span><br><span class="line">        Retry_Limit     False</span><br><span class="line">        Suppress_Type_Name On</span><br><span class="line">---</span><br><span class="line">apiVersion: apps/v1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line">  name: nginx</span><br><span class="line">  namespace: default</span><br><span class="line">spec:</span><br><span class="line">  replicas: 1</span><br><span class="line">  selector:</span><br><span class="line">    matchLabels:</span><br><span class="line">      app: nginx</span><br><span class="line">  template:</span><br><span class="line">    metadata:</span><br><span class="line">      labels:</span><br><span class="line">        app: nginx</span><br><span class="line">    spec:</span><br><span class="line">      containers:</span><br><span class="line">      - name: nginx</span><br><span class="line">        image: nginx:alpine</span><br><span class="line">        ports:</span><br><span class="line">        - containerPort: 80</span><br><span class="line">        volumeMounts:</span><br><span class="line">        - name: nginx-logs</span><br><span class="line">          mountPath: /var/log/nginx</span><br><span class="line">      - name: fluent-bit</span><br><span class="line">        image: fluent/fluent-bit:2.2</span><br><span class="line">        volumeMounts:</span><br><span class="line">        - name: nginx-logs</span><br><span class="line">          mountPath: /var/log/nginx</span><br><span class="line">          readOnly: <span class="literal">true</span></span><br><span class="line">        - name: config</span><br><span class="line">          mountPath: /fluent-bit/etc/</span><br><span class="line">      volumes:</span><br><span class="line">      - name: nginx-logs</span><br><span class="line">        emptyDir: &#123;&#125;</span><br><span class="line">      - name: config</span><br><span class="line">        configMap:</span><br><span class="line">          name: fluent-bit-config</span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Service</span><br><span class="line">metadata:</span><br><span class="line">  name: nginx</span><br><span class="line">  namespace: default</span><br><span class="line">spec:</span><br><span class="line">  selector:</span><br><span class="line">    app: nginx</span><br><span class="line">  ports:</span><br><span class="line">  - port: 80</span><br><span class="line">    targetPort: 80</span><br><span class="line">  <span class="built_in">type</span>: ClusterIP</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><h4 id="部署和删除"><a href="#部署和删除" class="headerlink" title="部署和删除"></a>部署和删除</h4><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"># 部署</span></span><br><span class="line">kubectl apply -f nginx-fluentbit.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除</span></span><br><span class="line">kubectl delete -f nginx-fluentbit.yaml</span><br></pre></td></tr></table></figure><p>确认 Pod 里两个容器都 Running（READY 2&#x2F;2）：</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">kubectl get pods</span><br><span class="line"><span class="comment"># NAME                     READY   STATUS    RESTARTS   AGE</span></span><br><span class="line"><span class="comment"># nginx-699c4dd7ff-ntzfg   2/2     Running   0          30s</span></span><br></pre></td></tr></table></figure><h3 id="YAML-逐字段解释"><a href="#YAML-逐字段解释" class="headerlink" title="YAML 逐字段解释"></a>YAML 逐字段解释</h3><p>ConfigMap 部分（Fluent Bit 配置）：</p><table><thead><tr><th>字段</th><th>值</th><th>说明</th></tr></thead><tbody><tr><td><code>[SERVICE] Flush</code></td><td><code>3</code></td><td>每 3 秒把缓冲区的日志刷到 OUTPUT</td></tr><tr><td><code>Log_Level</code></td><td><code>info</code></td><td>Fluent Bit 自身的日志级别</td></tr><tr><td><code>Daemon</code></td><td><code>off</code></td><td>前台运行，容器里必须前台否则容器会退出</td></tr><tr><td><code>[INPUT] Name</code></td><td><code>tail</code></td><td>类似 <code>tail -f</code>，实时跟踪文件新增内容</td></tr><tr><td><code>Tag</code></td><td><code>nginx.access</code></td><td>给输入打标签，OUTPUT 用 Match 匹配</td></tr><tr><td><code>Path</code></td><td><code>/var/log/nginx/access.log</code></td><td>要读的日志文件路径</td></tr><tr><td><code>DB</code></td><td><code>/var/log/flb_nginx.db</code></td><td>SQLite 文件，记录读到哪一行了，重启不重复读</td></tr><tr><td><code>Mem_Buf_Limit</code></td><td><code>5MB</code></td><td>内存缓冲区上限，防止日志太多撑爆内存</td></tr><tr><td><code>Refresh_Interval</code></td><td><code>5</code></td><td>每 5 秒检查文件是否有新内容</td></tr><tr><td><code>[OUTPUT] Name</code></td><td><code>es</code></td><td>Elasticsearch 兼容输出插件，Easysearch 直接可用</td></tr><tr><td><code>Match</code></td><td><code>*</code></td><td>匹配所有 Tag 的日志</td></tr><tr><td><code>Host</code></td><td><code>easysearch.es.svc.cluster.local</code></td><td>Easysearch 的 Service DNS（集群内访问）</td></tr><tr><td><code>HTTP_User/Passwd</code></td><td><code>admin/Admin123</code></td><td>Easysearch 认证信息</td></tr><tr><td><code>tls</code></td><td><code>Off</code></td><td>不用 HTTPS（本版本默认 HTTP）</td></tr><tr><td><code>Logstash_Format</code></td><td><code>On</code></td><td>按日期创建索引，格式：<code>nginx-logs-2026.03.11</code></td></tr><tr><td><code>Logstash_Prefix</code></td><td><code>nginx-logs</code></td><td>索引名前缀</td></tr><tr><td><code>Retry_Limit</code></td><td><code>False</code></td><td>发送失败无限重试</td></tr><tr><td><code>Suppress_Type_Name</code></td><td><code>On</code></td><td>兼容 ES 7.x+，不发送 <code>_type</code></td></tr></tbody></table><blockquote><p>⚠️ Fluent Bit 的 ini 格式不支持行内注释！<code>Flush 3 # 注释</code> 会把 <code>3 # 注释</code> 整个当成值，导致解析失败。</p></blockquote><p>Deployment 部分（两个容器 + 共享卷）：</p><table><thead><tr><th>字段</th><th>说明</th></tr></thead><tbody><tr><td><code>containers[0]: nginx</code></td><td>Nginx 容器，处理 HTTP 请求，日志写到 <code>/var/log/nginx/access.log</code></td></tr><tr><td><code>containers[1]: fluent-bit</code></td><td>Sidecar 容器，读取 nginx 的日志文件发送到 Easysearch</td></tr><tr><td><code>volumeMounts: nginx-logs</code></td><td>两个容器都挂载这个卷，共享 <code>/var/log/nginx</code> 目录</td></tr><tr><td><code>readOnly: true</code></td><td>Fluent Bit 只读挂载，最小权限原则</td></tr><tr><td><code>volumeMounts: config</code></td><td>把 ConfigMap 挂载为 Fluent Bit 的配置文件目录</td></tr><tr><td><code>volumes: nginx-logs (emptyDir)</code></td><td>临时卷，Pod 内容器共享，Pod 删除后数据丢失</td></tr><tr><td><code>volumes: config (configMap)</code></td><td>引用上面的 ConfigMap</td></tr></tbody></table><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">                    同一个 Pod</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│                                                 │</span><br><span class="line">│  nginx 容器                fluent-bit 容器       │</span><br><span class="line">│  ┌──────────┐              ┌──────────────┐     │</span><br><span class="line">│  │ 处理请求  │    写入       │ tail 插件     │     │</span><br><span class="line">│  │          │ ────────→    │ 读取日志文件  │     │</span><br><span class="line">│  │ access   │  emptyDir    │              │     │</span><br><span class="line">│  │ .log     │  共享卷       │ es 插件      │     │</span><br><span class="line">│  └──────────┘              │ 发送到       │     │</span><br><span class="line">│                            │ Easysearch   │     │</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">                          │  Easysearch      │</span><br><span class="line">                          │  nginx-logs-*    │</span><br><span class="line">                          │  索引            │</span><br><span class="line">                          └──────────────────┘</span><br></pre></td></tr></table></figure><h4 id="关键概念"><a href="#关键概念" class="headerlink" title="关键概念"></a>关键概念</h4><p>为什么用 <code>emptyDir</code>？</p><p><code>emptyDir</code> 是一个临时卷，在 Pod 创建时自动创建，Pod 删除时自动清理。它的作用是让同一个 Pod 里的多个容器共享文件。nginx 写日志到这个目录，fluent-bit 从这个目录读日志。</p><p>为什么 fluent-bit 挂载时加 <code>readOnly: true</code>？</p><p>Fluent Bit 只需要读日志文件，不需要写。加 readOnly 是最小权限原则，防止 Fluent Bit 意外修改日志文件。</p><p><code>Logstash_Format On</code> 是什么意思？</p><p>开启后 Fluent Bit 会按日期自动创建索引，格式为 <code>{Logstash_Prefix}-{日期}</code>，比如 <code>nginx-logs-2026.03.11</code>。这样每天的日志在不同索引里，方便按时间范围查询和清理旧数据。</p><p><code>DB /var/log/flb_nginx.db</code> 是什么？</p><p>Fluent Bit 用这个 SQLite 数据库文件记录”读到文件的哪一行了”。如果 Fluent Bit 重启，它会从上次的位置继续读，不会重复发送已经处理过的日志。</p><h3 id="第三步：产生日志并查询"><a href="#第三步：产生日志并查询" class="headerlink" title="第三步：产生日志并查询"></a>第三步：产生日志并查询</h3><h4 id="3-1-产生访问日志"><a href="#3-1-产生访问日志" class="headerlink" title="3.1 产生访问日志"></a>3.1 产生访问日志</h4><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">kubectl run curl-test --<span class="built_in">rm</span> -it --restart=Never --image=curlimages/curl -- \</span><br><span class="line">  sh -c <span class="string">&#x27;for i in $(seq 1 20); do curl -s http://nginx &gt; /dev/null; sleep 0.5; done&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="3-2-查询-Easysearch"><a href="#3-2-查询-Easysearch" class="headerlink" title="3.2 查询 Easysearch"></a>3.2 查询 Easysearch</h4><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">kubectl port-forward -n es pod/easysearch-0 9200:9200 &amp;</span><br><span class="line"><span class="built_in">sleep</span> 2</span><br><span class="line"></span><br><span class="line">curl -s <span class="string">&#x27;http://localhost:9200/nginx-logs-*/_search?size=3&#x27;</span> \</span><br><span class="line">  -u admin:Admin123 | python3 -m json.tool</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><span class="line">13</span><br><span class="line">14</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;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">31</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;hits&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;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;nginx-logs-2026.03.11&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;@timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-03-11T07:16:55.881Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;log&quot;</span><span class="punctuation">:</span> <span class="string">&quot;10.42.0.22 - - [11/Mar/2026:07:16:55 +0000] \&quot;GET / HTTP/1.1\&quot; 200 896 \&quot;-\&quot; \&quot;curl/8.18.0\&quot; \&quot;-\&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><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><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/03/11/1773217650148-29b92831-1984-4a8c-b242-7d301c9dcef3.png"></p><h4 id="3-3-从本地电脑访问（SSH-隧道）"><a href="#3-3-从本地电脑访问（SSH-隧道）" class="headerlink" title="3.3 从本地电脑访问（SSH 隧道）"></a>3.3 从本地电脑访问（SSH 隧道）</h4><p>Easysearch 自带了一个UI，但是跑在远程POD里，部署的Service也是Headless，所以没办法直接访问，我们的办法是先用kubectl port-forward把pod端口映射到服务器宿主机，然后再用 SSH 隧道把 9200 端口映射到本地：</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"><span class="comment"># 远程服务器上先跑 port-forward</span></span><br><span class="line">kubectl port-forward -n es pod/easysearch-0 9200:9200 &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 本地电脑 SSH 隧道</span></span><br><span class="line">ssh -L 9200:localhost:9200 ubuntu@&lt;server-ip&gt; -i key.pem</span><br><span class="line"></span><br><span class="line"><span class="comment"># 然后本地直接访问</span></span><br><span class="line">curl -s http://localhost:9200/nginx-logs-*/_search?size=3 -u admin:Admin123</span><br></pre></td></tr></table></figure><p>这样转发之后，我们也可以使用本机浏览器直接访问Easysearch自带的UI了。<br><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2026/03/11/1773217638361-947d31bd-246d-4918-9b55-ad25967062c3.png"></p><h3 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h3><h4 id="1-密码复杂度（最常见的坑）"><a href="#1-密码复杂度（最常见的坑）" class="headerlink" title="1. 密码复杂度（最常见的坑）"></a>1. 密码复杂度（最常见的坑）</h4><p>Easysearch 最新版本要求密码至少包含 2 类字符，不满足时 Pod 直接崩溃（Exit Code 1），日志来不及写，非常难排查。</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"># ❌ 纯小写</span></span><br><span class="line">ezs_password=<span class="string">&#x27;easysearchpaswd&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ✅ 大写 + 小写 + 数字</span></span><br><span class="line">ezs_password=<span class="string">&#x27;Admin123&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="2-vm-max-map-count"><a href="#2-vm-max-map-count" class="headerlink" title="2. vm.max_map_count"></a>2. vm.max_map_count</h4><p>不设置的话 Easysearch 启动失败：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sysctl -w vm.max_map_count=262144</span><br></pre></td></tr></table></figure><h4 id="3-小内存环境-OOMKilled"><a href="#3-小内存环境-OOMKilled" class="headerlink" title="3. 小内存环境 OOMKilled"></a>3. 小内存环境 OOMKilled</h4><p>默认 JVM 堆 1G + 堆外内存，总共需要 2G+。小集群用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">--<span class="built_in">set</span> <span class="string">&quot;javaOpts=-Xms256m -Xmx256m&quot;</span></span><br><span class="line">--<span class="built_in">set</span> resources.requests.memory=512Mi</span><br><span class="line">--<span class="built_in">set</span> resources.limits.memory=1536Mi</span><br></pre></td></tr></table></figure><h4 id="4-StorageClass-不匹配（AWS-环境）"><a href="#4-StorageClass-不匹配（AWS-环境）" class="headerlink" title="4. StorageClass 不匹配（AWS 环境）"></a>4. StorageClass 不匹配（AWS 环境）</h4><p>我的自建集群没有默认 StorageClass 是 <code>gp2</code>，需要指定：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--<span class="built_in">set</span> storageClassName=gp2</span><br></pre></td></tr></table></figure><p>如果环境自带 <code>local-path-provisioner</code>，不需要指定。</p><h4 id="5-Fluent-Bit-配置不支持行内注释"><a href="#5-Fluent-Bit-配置不支持行内注释" class="headerlink" title="5. Fluent Bit 配置不支持行内注释"></a>5. Fluent Bit 配置不支持行内注释</h4><p>Fluent Bit 的 ini 格式不支持行内注释，<code>#</code> 后面的内容会被当成值的一部分：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ❌ 错误：行内注释会被当成值</span></span><br><span class="line">Logstash_Format On   <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="comment"># 按日期创建索引</span></span><br><span class="line">Logstash_Format On</span><br></pre></td></tr></table></figure><h3 id="排查命令"><a href="#排查命令" class="headerlink" title="排查命令"></a>排查命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="comment"># Pod 状态</span></span><br><span class="line">kubectl get pods -n es -w</span><br><span class="line">kubectl describe pod easysearch-0 -n es</span><br><span class="line"></span><br><span class="line"><span class="comment"># 容器日志</span></span><br><span class="line">kubectl logs easysearch-0 -n es</span><br><span class="line">kubectl logs easysearch-0 -n es --previous</span><br><span class="line">kubectl logs easysearch-0 -n es -c init-config</span><br><span class="line"></span><br><span class="line"><span class="comment"># Fluent Bit sidecar 日志</span></span><br><span class="line">kubectl logs &lt;nginx-pod-name&gt; -c fluent-bit</span><br><span class="line"></span><br><span class="line"><span class="comment"># 退出原因</span></span><br><span class="line">kubectl describe pod easysearch-0 -n es | grep -A5 <span class="string">&quot;Last State&quot;</span></span><br><span class="line"><span class="comment"># Exit Code 1   = 应用错误（密码/配置）</span></span><br><span class="line"><span class="comment"># Exit Code 137 = OOMKilled（内存不足）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 资源使用</span></span><br><span class="line">kubectl top nodes</span><br><span class="line">kubectl top pods -n es</span><br><span class="line">free -h</span><br><span class="line"></span><br><span class="line"><span class="comment"># 确认 TLS 模式</span></span><br><span class="line">kubectl logs easysearch-0 -n es | grep <span class="string">&quot;TLS HTTP Provider&quot;</span></span><br><span class="line"><span class="comment"># null = HTTP，JDK = HTTPS</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 确认密码</span></span><br><span class="line">kubectl get secret easysearch-secrets -n es \</span><br><span class="line">  -o jsonpath=<span class="string">&#x27;&#123;.data.ezs_password&#125;&#x27;</span> | <span class="built_in">base64</span> -d</span><br><span class="line"></span><br><span class="line"><span class="comment"># 确认镜像版本</span></span><br><span class="line">kubectl get pod easysearch-0 -n es -o jsonpath=<span class="string">&#x27;&#123;.spec.containers[0].image&#125;&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="清理"><a href="#清理" class="headerlink" title="清理"></a>清理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除 Nginx + Fluent Bit</span></span><br><span class="line">kubectl delete -f nginx-fluentbit.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除 Easysearch</span></span><br><span class="line">helm uninstall easysearch -n es</span><br><span class="line">kubectl delete pvc --all -n es</span><br><span class="line">kubectl delete secret easysearch-secrets -n es</span><br><span class="line">kubectl delete namespace es</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">基于 k3s + Easysearch 2.0.3 实测，从零搭建 Fluent Bit 日志收集方案，将 Nginx 日志转发到 Easysearch。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>在AWS EC2 上从零搭建 Kubernetes 集群（kubeadm）</title>
    <link href="https://blog.no-claw.com/posts/7e9d67ce/"/>
    <id>https://blog.no-claw.com/posts/7e9d67ce/</id>
    <published>2026-03-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天讲解在AWS EC2 上使用kubeadm搭建Kubernetes 集群。</p><p>kubeadm 是 Kubernetes 官方提供的集群引导工具，用来快速创建符合最佳实践的 K8s 集群。除了初始化集群，它还能做节点的升级、降级等生命周期管理。用 kubeadm 建集群是学习 K8s 的推荐方式，也适合搭建小规模集群或作为更复杂企业级方案的基础组件。</p><p>本文基于 Ubuntu，使用三台 EC2 实例：一台作为控制面（Master），两台作为工作节点（Worker）。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2026/03/06/1772810707208-980e3edd-b4f4-4678-a17b-a5abd5b4a5a6.png"></p><p>我们会在 Master 节点上从头安装 kubeadm 及其依赖，然后初始化集群，最后把 Worker 节点加入进来。</p> <span id="more"></span><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2026/03/06/1772810713484-31955c25-1962-4513-af60-01cd0d1f9a4e.png"></p><p>这几台 EC2 使用同一个安全组，入站规则只放行VPC网段和终端访问地址。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2026/03/06/1772811257345-c40eeeab-4627-4793-a84c-45abd2232b12.png"></p><h3 id="第一步：安装-kubeadm-及其依赖"><a href="#第一步：安装-kubeadm-及其依赖" class="headerlink" title="第一步：安装 kubeadm 及其依赖"></a>第一步：安装 kubeadm 及其依赖</h3><p>以下操作在 Master 节点上执行（Worker 节点如果还没装也要跑一遍）。</p><h4 id="1-关闭-swap"><a href="#1-关闭-swap" class="headerlink" title="1. 关闭 swap"></a>1. 关闭 swap</h4><p>kubelet 需要精确管理 Pod 的内存，swap 会让内存数据不准确，导致调度器做出错误判断。kubelet 默认检测到 swap 开着就拒绝启动。</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"># 关闭 swap</span></span><br><span class="line"><span class="built_in">sudo</span> swapoff -a</span><br><span class="line"><span class="comment"># 注释掉 fstab 里的 swap 行，重启后也不会再开</span></span><br><span class="line"><span class="built_in">sudo</span> sed -i <span class="string">&#x27;/ swap / s/^/#/&#x27;</span> /etc/fstab</span><br></pre></td></tr></table></figure><p>验证：<code>free -h</code>，Swap 行全是 0 就对了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@ip-172-31-27-240:~$ free -h</span><br><span class="line">               total        used        free      shared  buff/cache   available</span><br><span class="line">Mem:           1.9Gi       361Mi       1.4Gi       2.7Mi       303Mi       1.5Gi</span><br><span class="line">Swap:             0B          0B          0B</span><br><span class="line">ubuntu@ip-172-31-27-240:~$</span><br></pre></td></tr></table></figure><h4 id="2-更新包管理器，安装基础依赖"><a href="#2-更新包管理器，安装基础依赖" class="headerlink" title="2. 更新包管理器，安装基础依赖"></a>2. 更新包管理器，安装基础依赖</h4><p>更新 apt 包索引，安装通过 HTTPS 访问软件仓库所需的包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release</span><br></pre></td></tr></table></figure><h4 id="3-加载内核模块，允许-IPv4-转发"><a href="#3-加载内核模块，允许-IPv4-转发" class="headerlink" title="3. 加载内核模块，允许 IPv4 转发"></a>3. 加载内核模块，允许 IPv4 转发</h4><p>加载 <code>overlay</code> 和 <code>br_netfilter</code> 两个内核模块。<code>overlay</code> 是 containerd 存储驱动需要的，<code>br_netfilter</code> 让 bridge 上的流量能经过 iptables 规则，K8s 的 Service 和 NetworkPolicy 都依赖这个。</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"><span class="built_in">sudo</span> modprobe overlay</span><br><span class="line"><span class="built_in">sudo</span> modprobe br_netfilter</span><br><span class="line"></span><br><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF | sudo tee /etc/modules-load.d/k8s.conf</span></span><br><span class="line"><span class="string">overlay</span></span><br><span class="line"><span class="string">br_netfilter</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><h4 id="4-设置内核网络参数"><a href="#4-设置内核网络参数" class="headerlink" title="4. 设置内核网络参数"></a>4. 设置内核网络参数</h4><p>让 Linux 节点的 iptables 能正确处理桥接流量。三个参数缺一不可：</p><ul><li><code>bridge-nf-call-iptables</code>：bridge 流量过 iptables，Service 转发靠这个</li><li><code>ip_forward</code>：开启 IP 转发，Pod 跨节点通信需要</li><li><code>bridge-nf-call-ip6tables</code>：同上，IPv6 版本</li></ul><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"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf</span></span><br><span class="line"><span class="string">net.bridge.bridge-nf-call-iptables  = 1</span></span><br><span class="line"><span class="string">net.ipv4.ip_forward                 = 1</span></span><br><span class="line"><span class="string">net.bridge.bridge-nf-call-ip6tables = 1</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> sysctl --system</span><br></pre></td></tr></table></figure><h4 id="5-安装-containerd"><a href="#5-安装-containerd" class="headerlink" title="5. 安装 containerd"></a>5. 安装 containerd</h4><p>使用 Docker 官方分发的 DEB 包安装 containerd 作为容器运行时。这只是安装 containerd 的方式之一，也可以从源码编译或用其他包管理器。</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">curl -fsSL https://download.docker.com/linux/ubuntu/gpg | <span class="built_in">sudo</span> gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu <span class="subst">$(lsb_release -cs)</span> stable&quot;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y containerd.io</span><br></pre></td></tr></table></figure><h4 id="6-配置-systemd-cgroup-驱动"><a href="#6-配置-systemd-cgroup-驱动" class="headerlink" title="6. 配置 systemd cgroup 驱动"></a>6. 配置 systemd cgroup 驱动</h4><p>生成 containerd 默认配置，然后把 cgroup 驱动改成 systemd。这一步是为了避免系统里同时存在两个 cgroup 管理器（systemd 和 cgroupfs）导致的不稳定问题。kubelet 默认用 systemd，containerd 也要对齐。</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="built_in">sudo</span> <span class="built_in">mkdir</span> -p /etc/containerd</span><br><span class="line">containerd config default | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/containerd/config.toml</span><br><span class="line"><span class="built_in">sudo</span> sed -i <span class="string">&#x27;s/SystemdCgroup = false/SystemdCgroup = true/&#x27;</span> /etc/containerd/config.toml</span><br><span class="line"><span class="built_in">sudo</span> systemctl restart containerd</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> containerd</span><br></pre></td></tr></table></figure><h4 id="7-安装-kubeadm、kubelet、kubectl"><a href="#7-安装-kubeadm、kubelet、kubectl" class="headerlink" title="7. 安装 kubeadm、kubelet、kubectl"></a>7. 安装 kubeadm、kubelet、kubectl</h4><p>从 Kubernetes 官方包仓库安装。三个组件的作用：</p><ul><li><code>kubeadm</code>：集群引导工具，负责 init 和 join</li><li><code>kubelet</code>：每个节点上的代理，负责管理 Pod 和容器</li><li><code>kubectl</code>：命令行工具，用来操作集群</li></ul><p>下面以 v1.30 为例。如果要装其他版本（比如 v1.35），把两处 <code>v1.30</code> 都改成 <code>v1.35</code> 就行，GPG key 和源地址的版本号要一致。可用版本列表见 <a href="https://pkgs.k8s.io/core:/stable:/">https://pkgs.k8s.io/core:/stable:/</a></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">curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | <span class="built_in">sudo</span> gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /&#x27;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/kubernetes.list</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y kubelet kubeadm kubectl</span><br></pre></td></tr></table></figure><h4 id="8-锁定版本，防止自动升级"><a href="#8-锁定版本，防止自动升级" class="headerlink" title="8. 锁定版本，防止自动升级"></a>8. 锁定版本，防止自动升级</h4><p>K8s 组件版本需要严格对齐，自动升级可能导致版本不一致出问题：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-mark hold kubelet kubeadm kubectl</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2026/03/06/1772810696314-d0f7282a-22ff-4de5-a576-a879f241fa15.png"></p><h3 id="第二步：初始化控制面节点"><a href="#第二步：初始化控制面节点" class="headerlink" title="第二步：初始化控制面节点"></a>第二步：初始化控制面节点</h3><p>以下操作仅在 Master 节点执行。</p><p>初始化过程会完成以下事情：</p><ul><li>创建证书颁发机构（CA），用于集群内的安全通信和身份认证</li><li>启动节点组件：kubelet</li><li>启动控制面组件：API Server、Controller Manager、Scheduler、etcd</li><li>安装通用插件：kube-proxy、DNS</li></ul><p>kubeadm 初始化使用合理的默认值，遵循最佳实践。当然也有很多配置选项可以自定义，比如使用自己的 CA 证书或外部 etcd 存储。</p><p>kubeadm 不会帮你安装网络插件，这个需要自己装。我们用 Calico 作为 Pod 网络插件。Calico 支持 Kubernetes NetworkPolicy，也是 AWS、Azure、GCP 的托管 K8s 服务内部使用的网络方案，生产可用。为了让 Calico 的网络策略正常工作，初始化时必须通过 <code>--pod-network-cidr</code> 指定 Pod 网络的 IP 范围。</p><h4 id="1-初始化控制面"><a href="#1-初始化控制面" class="headerlink" title="1. 初始化控制面"></a>1. 初始化控制面</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> kubeadm init --pod-network-cidr=192.168.0.0/16 </span><br></pre></td></tr></table></figure><p><code>192.168.0.0/16</code> 是 Calico 的默认网段。注意这个网段不能跟你的 VPC 网络 CIDR 重叠，如果重叠了需要额外配置 Calico 来避免冲突。</p><p>输出会显示 kubeadm 初始化控制面的每一步。最后会给出两个重要信息：配置 kubectl 的命令和 worker 节点加入集群的 join 命令。</p><h4 id="2-保存-join-命令"><a href="#2-保存-join-命令" class="headerlink" title="2. 保存 join 命令"></a>2. 保存 join 命令</h4><p>把输出最后的 <code>kubeadm join ...</code> 命令复制下来存好，后面 worker 节点加入集群要用。token 默认 24 小时过期，过期了可以用 <code>kubeadm token create --print-join-command</code> 重新生成。</p><h4 id="3-配置-kubectl"><a href="#3-配置-kubectl" class="headerlink" title="3. 配置 kubectl"></a>3. 配置 kubectl</h4><p>用 kubeadm 生成的 admin kubeconfig 初始化 kubectl 配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$HOME</span>/.kube</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> -i /etc/kubernetes/admin.conf <span class="variable">$HOME</span>/.kube/config</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> $(<span class="built_in">id</span> -u):$(<span class="built_in">id</span> -g) <span class="variable">$HOME</span>/.kube/config</span><br></pre></td></tr></table></figure><h4 id="4-确认控制面组件状态"><a href="#4-确认控制面组件状态" class="headerlink" title="4. 确认控制面组件状态"></a>4. 确认控制面组件状态</h4><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">kubectl get componentstatuses</span><br></pre></td></tr></table></figure><p>输出应该显示 scheduler、controller-manager、etcd 都是 Healthy。kubectl 能正常返回结果也说明 API Server 在正常工作。</p><h4 id="5-查看节点状态"><a href="#5-查看节点状态" class="headerlink" title="5. 查看节点状态"></a>5. 查看节点状态</h4><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">kubectl get nodes</span><br></pre></td></tr></table></figure><p>这时候控制面节点会显示 NotReady。这是正常的——因为还没装网络插件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NAME               STATUS     ROLES           AGE     VERSION</span><br><span class="line">ip-172-31-16-97    NotReady   &lt;none&gt;          33s     v1.30.14</span><br><span class="line">ip-172-31-27-240   NotReady   control-plane   4m11s   v1.30.14</span><br></pre></td></tr></table></figure><p>可以用 <code>kubectl describe nodes</code> 看详细信息，Conditions 里会显示 <code>Ready: False</code>，原因是 “network plugin is not ready”，CNI（Container Network Interface）还没初始化。</p><h4 id="6-安装-Calico-网络插件（Operator-方式）"><a href="#6-安装-Calico-网络插件（Operator-方式）" class="headerlink" title="6. 安装 Calico 网络插件（Operator 方式）"></a>6. 安装 Calico 网络插件（Operator 方式）</h4><p>Calico 有两种安装方式：Operator 和 Manifest。官方现在推荐 Operator 方式，通过 Tigera Operator 来管理 Calico 的生命周期，后续升级和配置变更都更方便。</p><p>先装 Tigera Operator：</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">kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/tigera-operator.yaml</span><br></pre></td></tr></table></figure><p>再装 Calico 自定义资源：</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">kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/custom-resources.yaml</span><br></pre></td></tr></table></figure><p>custom-resources.yaml 里默认的 Pod CIDR 是 <code>192.168.0.0/16</code>，跟 <code>kubeadm init</code> 时指定的一致。如果你用了不同的 CIDR，需要先下载这个文件改 <code>cidr</code> 字段再 apply。</p><p>这会创建一系列资源来支持 Pod 网络，包括：</p><ul><li>Tigera Operator Deployment，负责管理 Calico 组件的部署和升级</li><li>一个 DaemonSet，在集群每个节点上运行一个 calico-node Pod</li><li>多个 CRD（自定义资源定义），扩展 K8s API 来支持网络策略等功能</li></ul><p>Calico 组件会部署在 <code>calico-system</code> namespace 下（Operator 方式），而不是 <code>kube-system</code>（Manifest 方式）。</p><h4 id="7-等待节点就绪"><a href="#7-等待节点就绪" class="headerlink" title="7. 等待节点就绪"></a>7. 等待节点就绪</h4><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">watch kubectl get nodes</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">NAME               STATUS   ROLES           AGE     VERSION</span><br><span class="line">ip-172-31-16-97    Ready    &lt;none&gt;          95s     v1.30.14</span><br><span class="line">ip-172-31-27-240   Ready    control-plane   5m13s   v1.30.14</span><br></pre></td></tr></table></figure><p>网络插件初始化完成后，控制面节点会变成 Ready。可能需要等一分钟左右。按 <code>Ctrl+C</code> 停止 watch。</p><hr><h3 id="第三步：Worker-节点加入集群"><a href="#第三步：Worker-节点加入集群" class="headerlink" title="第三步：Worker 节点加入集群"></a>第三步：Worker 节点加入集群</h3><p>以下操作在 Worker 节点执行。</p><p>使用 kubeadm 添加工作节点比初始化控制面还要简单，一条 join 命令就搞定。</p><h4 id="1-连接到-Worker-节点"><a href="#1-连接到-Worker-节点" class="headerlink" title="1. 连接到 Worker 节点"></a>1. 连接到 Worker 节点</h4><p>打开一个新终端，SSH 连接到 Worker 节点（用户名 ubuntu）。</p><h4 id="2-执行-join-命令"><a href="#2-执行-join-命令" class="headerlink" title="2. 执行 join 命令"></a>2. 执行 join 命令</h4><p>用 <code>sudo</code> 加上之前从 <code>kubeadm init</code> 输出中保存的 join 命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> kubeadm <span class="built_in">join</span> 10.0.0.100:6443 \</span><br><span class="line">  --token &lt;token&gt; \</span><br><span class="line">  --discovery-token-ca-cert-hash sha256:&lt;<span class="built_in">hash</span>&gt;</span><br></pre></td></tr></table></figure><p>如果 token 过期了（24 小时有效），在 Master 上重新生成：</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">kubeadm token create --print-join-command</span><br></pre></td></tr></table></figure><h4 id="3-确认-Worker-节点加入集群"><a href="#3-确认-Worker-节点加入集群" class="headerlink" title="3. 确认 Worker 节点加入集群"></a>3. 确认 Worker 节点加入集群</h4><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">kubectl get nodes</span><br></pre></td></tr></table></figure><p>Worker 节点会显示出来，ROLES 列为 <code>&lt;none&gt;</code>（这是正常的，worker 节点默认没有角色标签）。节点可能需要一分钟左右才能变成 Ready。</p><h4 id="4-确认所有-Pod-正常运行"><a href="#4-确认所有-Pod-正常运行" class="headerlink" title="4. 确认所有 Pod 正常运行"></a>4. 确认所有 Pod 正常运行</h4><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">kubectl get pods --all-namespaces</span><br></pre></td></tr></table></figure><p>所有 Pod 都应该处于 Running 状态。注意每个节点上都会有 Calico 的 Pod 在运行，负责该节点的网络功能。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">ubuntu@ip-172-31-27-240:~$ kubectl get pods --all-namespaces</span><br><span class="line">NAMESPACE          NAME                                       READY   STATUS    RESTARTS   AGE</span><br><span class="line">calico-apiserver   calico-apiserver-7458595b77-jclzd          1/1     Running   0          61s</span><br><span class="line">calico-apiserver   calico-apiserver-7458595b77-w2ww8          1/1     Running   0          61s</span><br><span class="line">calico-system      calico-kube-controllers-8b7bdfdc7-tgx2p    1/1     Running   0          60s</span><br><span class="line">calico-system      calico-node-d4m6v                          1/1     Running   0          61s</span><br><span class="line">calico-system      calico-node-lr6bq                          1/1     Running   0          61s</span><br><span class="line">calico-system      calico-typha-5494555c85-5vftk              1/1     Running   0          61s</span><br><span class="line">calico-system      csi-node-driver-spgw8                      2/2     Running   0          60s</span><br><span class="line">calico-system      csi-node-driver-xjsfk                      2/2     Running   0          60s</span><br><span class="line">kube-system        coredns-55cb58b774-fjhvm                   1/1     Running   0          5m28s</span><br><span class="line">kube-system        coredns-55cb58b774-z4c6x                   1/1     Running   0          5m28s</span><br><span class="line">kube-system        etcd-ip-172-31-27-240                      1/1     Running   0          5m41s</span><br><span class="line">kube-system        kube-apiserver-ip-172-31-27-240            1/1     Running   0          5m42s</span><br><span class="line">kube-system        kube-controller-manager-ip-172-31-27-240   1/1     Running   0          5m41s</span><br><span class="line">kube-system        kube-proxy-424ck                           1/1     Running   0          5m28s</span><br><span class="line">kube-system        kube-proxy-7tpcs                           1/1     Running   0          2m7s</span><br><span class="line">kube-system        kube-scheduler-ip-172-31-27-240            1/1     Running   0          5m41s</span><br><span class="line">tigera-operator    tigera-operator-5645cfc98-fjxtp            1/1     Running   0          67s</span><br></pre></td></tr></table></figure><p>双节点集群到这里就搭建完成了。</p><h3 id="第四步：部署应用验证集群"><a href="#第四步：部署应用验证集群" class="headerlink" title="第四步：部署应用验证集群"></a>第四步：部署应用验证集群</h3><p>集群搭好了，跑个 nginx 验证一下。</p><h4 id="1-创建-nginx-Pod"><a href="#1-创建-nginx-Pod" class="headerlink" title="1. 创建 nginx Pod"></a>1. 创建 nginx Pod</h4><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">kubectl run nginx --image=nginx --port=80</span><br></pre></td></tr></table></figure><h4 id="2-确认-Pod-运行正常"><a href="#2-确认-Pod-运行正常" class="headerlink" title="2. 确认 Pod 运行正常"></a>2. 确认 Pod 运行正常</h4><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">kubectl get pods -o wide</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@ip-172-31-27-240:~$ kubectl get pods -o wide</span><br><span class="line">NAME    READY   STATUS    RESTARTS   AGE   IP               NODE              NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx   1/1     Running   0          9s    192.168.98.135   ip-172-31-16-97   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></table></figure><p>看 STATUS 是 Running，IP 已分配，记下 Pod 跑在哪个节点上。</p><h4 id="3-通过-NodePort-暴露服务"><a href="#3-通过-NodePort-暴露服务" class="headerlink" title="3. 通过 NodePort 暴露服务"></a>3. 通过 NodePort 暴露服务</h4><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">kubectl expose pod nginx --<span class="built_in">type</span>=NodePort --port=80</span><br><span class="line">kubectl get svc nginx</span><br></pre></td></tr></table></figure><p>输出里 <code>PORT(S)</code> 列会显示类似 <code>80:31973/TCP</code>，<code>31973</code> 就是 NodePort。</p><h4 id="4-访问-nginx"><a href="#4-访问-nginx" class="headerlink" title="4. 访问 nginx"></a>4. 访问 nginx</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 用 Pod 所在节点的 IP + NodePort</span></span><br><span class="line">curl &lt;worker节点IP&gt;:&lt;NodePort&gt;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">ubuntu@ip-172-31-27-240:~$ curl 172.31.16.97:32686</span><br><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">&lt;title&gt;Welcome to nginx!&lt;/title&gt;</span><br><span class="line">&lt;style&gt;</span><br><span class="line">html &#123; color-scheme: light dark; &#125;</span><br><span class="line">body &#123; width: 35em; margin: 0 auto;</span><br><span class="line">font-family: Tahoma, Verdana, Arial, sans-serif; &#125;</span><br><span class="line">&lt;/style&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;</span><br><span class="line">&lt;p&gt;If you see this page, the nginx web server is successfully installed and</span><br><span class="line">working. Further configuration is required.&lt;/p&gt;</span><br><span class="line"></span><br><span class="line">&lt;p&gt;For online documentation and support please refer to</span><br><span class="line">&lt;a href=&quot;http://nginx.org/&quot;&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;</span><br><span class="line">Commercial support is available at</span><br><span class="line">&lt;a href=&quot;http://nginx.com/&quot;&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;</span><br><span class="line"></span><br><span class="line">&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>能看到 nginx 欢迎页就说明集群网络正常。</p><p>注意：NodePort 通过节点 IP 访问，不是 ClusterIP。ClusterIP 是集群内部的虚拟 IP，只在集群内部可达。</p><h3 id="AWS-上的-Calico-网络踩坑"><a href="#AWS-上的-Calico-网络踩坑" class="headerlink" title="AWS 上的 Calico 网络踩坑"></a>AWS 上的 Calico 网络踩坑</h3><p>在 AWS EC2 上用 Calico 默认的 BGP 路由模式，你可能会发现从一个节点直接 curl 另一个节点上的 Pod IP 不通。比如从 master（10.0.0.100）curl worker 上的 Pod（192.168.180.194）会超时。</p><p>路由表是对的（<code>ip route | grep 192.168</code> 能看到到 worker Pod 网段的路由），但包就是过不去。</p><p>原因是 AWS VPC 的源&#x2F;目标检查（Source&#x2F;Destination Check）。EC2 默认会检查每个网络包的源 IP 和目标 IP 是否属于这台实例，Pod IP（192.168.x.x）不属于 EC2 的 VPC 地址，VPC 直接把包丢了。</p><h4 id="关闭源-目标检查（简单）"><a href="#关闭源-目标检查（简单）" class="headerlink" title="关闭源&#x2F;目标检查（简单）"></a>关闭源&#x2F;目标检查（简单）</h4><p>AWS Console → EC2 → 选中实例 → Actions → Networking → Change source&#x2F;destination check → Stop</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/03/06/1772810680065-9cc90e1f-4127-4d34-a5ac-de98202fc020.png"></p><p>所有节点都要改。改完后跨节点的 Pod IP 直接访问就通了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">ubuntu@ip-172-31-27-240:~$ curl 192.168.98.135</span><br><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">&lt;title&gt;Welcome to nginx!&lt;/title&gt;</span><br><span class="line">&lt;style&gt;</span><br><span class="line">html &#123; color-scheme: light dark; &#125;</span><br><span class="line">body &#123; width: 35em; margin: 0 auto;</span><br><span class="line">font-family: Tahoma, Verdana, Arial, sans-serif; &#125;</span><br><span class="line">&lt;/style&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;</span><br><span class="line">&lt;p&gt;If you see this page, the nginx web server is successfully installed and</span><br><span class="line">working. Further configuration is required.&lt;/p&gt;</span><br><span class="line"></span><br><span class="line">&lt;p&gt;For online documentation and support please refer to</span><br><span class="line">&lt;a href=&quot;http://nginx.org/&quot;&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;</span><br><span class="line">Commercial support is available at</span><br><span class="line">&lt;a href=&quot;http://nginx.com/&quot;&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;</span><br><span class="line"></span><br><span class="line">&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><h3 id="第五步：用-Deployment-管理副本"><a href="#第五步：用-Deployment-管理副本" class="headerlink" title="第五步：用 Deployment 管理副本"></a>第五步：用 Deployment 管理副本</h3><p>前面用 <code>kubectl run</code> 创建的是裸 Pod，不支持扩缩容。生产环境应该用 Deployment，支持副本管理和滚动更新。</p><h3 id="1-删掉裸-Pod，改用-Deployment"><a href="#1-删掉裸-Pod，改用-Deployment" class="headerlink" title="1. 删掉裸 Pod，改用 Deployment"></a>1. 删掉裸 Pod，改用 Deployment</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">kubectl delete pod nginx</span><br><span class="line">kubectl delete svc nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 Deployment，10 个副本</span></span><br><span class="line">kubectl create deployment nginx --image=nginx --replicas=10</span><br><span class="line">kubectl expose deployment nginx --<span class="built_in">type</span>=NodePort --port=80</span><br></pre></td></tr></table></figure><h3 id="2-查看副本状态"><a href="#2-查看副本状态" class="headerlink" title="2. 查看副本状态"></a>2. 查看副本状态</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">kubectl get deployment nginx</span><br><span class="line">kubectl get pods -o wide</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/03/06/1772810946669-7c09169f-2b0a-4419-ad68-8f3a1590504d.png"></p><p>10 个 Pod 会分布在不同节点上。</p><h3 id="3-验证所有-Pod-都能访问"><a href="#3-验证所有-Pod-都能访问" class="headerlink" title="3. 验证所有 Pod 都能访问"></a>3. 验证所有 Pod 都能访问</h3><p>一行命令逐个 curl 所有 Pod IP：</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">kubectl get pods -l app=nginx -o jsonpath=<span class="string">&#x27;&#123;range .items[*]&#125;&#123;.status.podIP&#125;&#123;&quot;\n&quot;&#125;&#123;end&#125;&#x27;</span> | <span class="keyword">while</span> <span class="built_in">read</span> ip; <span class="keyword">do</span> <span class="built_in">echo</span> <span class="string">&quot;--- <span class="variable">$ip</span> ---&quot;</span>; curl --connect-timeout 3 -s <span class="variable">$ip</span> | <span class="built_in">head</span> -1; <span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>每个 IP 都输出 <code>&lt;!DOCTYPE html&gt;</code> 就说明都通了。</p><h3 id="4-调整副本数"><a href="#4-调整副本数" class="headerlink" title="4. 调整副本数"></a>4. 调整副本数</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 缩到 3 个</span></span><br><span class="line">kubectl scale deployment nginx --replicas=3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 扩到 20 个</span></span><br><span class="line">kubectl scale deployment nginx --replicas=20</span><br></pre></td></tr></table></figure><h3 id="附：安装-k9s（终端集群管理工具）"><a href="#附：安装-k9s（终端集群管理工具）" class="headerlink" title="附：安装 k9s（终端集群管理工具）"></a>附：安装 k9s（终端集群管理工具）</h3><p>k9s 是一个终端 UI 工具，比 kubectl 看集群状态直观很多，支持实时查看 Pod、日志、资源占用等。</p><p>Mac：</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">brew install derailed/k9s/k9s</span><br></pre></td></tr></table></figure><p>Ubuntu&#x2F;Linux：</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 -sS https://webi.sh/k9s | sh</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">curl -LO https://github.com/derailed/k9s/releases/latest/download/k9s_Linux_amd64.tar.gz</span><br><span class="line">tar xzf k9s_Linux_amd64.tar.gz</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span> k9s /usr/local/bin/</span><br></pre></td></tr></table></figure><p>装完敲 <code>k9s</code> 进入，按 <code>:</code> 输入资源类型（比如 <code>pods</code>、<code>deploy</code>、<code>svc</code>）切换视图从而看到集群的各种信息，<code>q</code> 退出。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/03/06/1772810912168-11906582-ba70-4a6f-9aac-43429e3c54db.png"></p>]]></content>
    
    
    <summary type="html">使用 kubeadm 在 AWS EC2 Ubuntu 实例上搭建 Kubernetes 集群，一台 Master 两台 Worker 的完整教程。</summary>
    
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>修复 GitHub Pages 推送后 CNAME 自动重置旧域名的问题</title>
    <link href="https://blog.no-claw.com/posts/6ec894ce/"/>
    <id>https://blog.no-claw.com/posts/6ec894ce/</id>
    <published>2026-03-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Hexo 使用Github action 部署后 GitHub Pages 域名自动变成已经取消的实效的域名，而不是预期的 <code>*.github.io</code></p><p>排查之后是<code>source/CNAME</code> 文件中配置了旧域名 <code>airag.click</code></p><p>删除 CNAME 文件后重新部署就可以解决</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> <span class="built_in">source</span>/CNAME</span><br></pre></td></tr></table></figure><ul><li>CNAME 文件会被 Hexo 复制到生成目录，告诉 GitHub Pages 使用自定义域名</li><li>删除后将使用默认的 <code>&lt;username&gt;.github.io</code> 域名</li></ul>]]></content>
    
    
    <summary type="html">GitHub Pages 部署后域名自动变回旧域名，排查发现是 source/CNAME 残留导致，删除后解决。</summary>
    
    
    
    <category term="Blog" scheme="https://blog.no-claw.com/categories/Blog/"/>
    
    
    <category term="Blog" scheme="https://blog.no-claw.com/tags/Blog/"/>
    
  </entry>
  
  <entry>
    <title>k3s + Helm 部署 Easysearch</title>
    <link href="https://blog.no-claw.com/posts/1dba38bc/"/>
    <id>https://blog.no-claw.com/posts/1dba38bc/</id>
    <published>2026-03-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近学了K8S，为了测试方便测试搭了一个K3S集群，然后使用helm运行一下Easysearch。</p><p>参考文档：<a href="https://docs.infinilabs.com/easysearch/main/docs/deployment/install-guide/helm/">https://docs.infinilabs.com/easysearch/main/docs/deployment/install-guide/helm/</a></p><p>首先添加helm仓库并更新。</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">helm repo add infinilabs https://helm.infinilabs.com</span><br><span class="line">helm repo update</span><br></pre></td></tr></table></figure><p>然后新建命名空间，我这里叫做es（下同），也可以使用其他名字。</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">kubectl create namespace es</span><br></pre></td></tr></table></figure><span id="more"></span><p>Easysearch 依赖 <code>cert-manager</code> 来处理证书。使用这个命令来安装。</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">kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml</span><br></pre></td></tr></table></figure><p>否则就会收到如下报错。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">resource mapping not found for name: &quot;easysearch-ca-issuer&quot; namespace: &quot;&quot; from &quot;STDIN&quot;: no matches for kind &quot;Issuer&quot; in version &quot;cert-manager.io/v1&quot;</span><br><span class="line"></span><br><span class="line">ensure CRDs are installed first</span><br><span class="line"></span><br><span class="line">resource mapping not found for name: &quot;easysearch-ca-certificate&quot; namespace: &quot;&quot; from &quot;STDIN&quot;: no matches for kind &quot;Certificate&quot; in version &quot;cert-manager.io/v1&quot;</span><br><span class="line"></span><br><span class="line">ensure CRDs are installed first</span><br></pre></td></tr></table></figure><p>直接执行以下命令，我设置的命名空间固定是 <code>es</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">cat &lt;&lt; EOF | kubectl apply -n es -f -</span><br><span class="line">apiVersion: cert-manager.io/v1</span><br><span class="line">kind: Issuer</span><br><span class="line">metadata:</span><br><span class="line">  name: easysearch-ca-issuer</span><br><span class="line">spec:</span><br><span class="line">  selfSigned: &#123;&#125;</span><br><span class="line">---</span><br><span class="line">apiVersion: cert-manager.io/v1</span><br><span class="line">kind: Certificate</span><br><span class="line">metadata:</span><br><span class="line">  name: easysearch-ca-certificate</span><br><span class="line">spec:</span><br><span class="line">  commonName: easysearch-ca-certificate</span><br><span class="line">  duration: 87600h0m0s</span><br><span class="line">  isCA: true</span><br><span class="line">  issuerRef:</span><br><span class="line">    kind: Issuer</span><br><span class="line">    name: easysearch-ca-issuer</span><br><span class="line">  privateKey:</span><br><span class="line">    algorithm: ECDSA</span><br><span class="line">    size: 256</span><br><span class="line">  renewBefore: 2160h0m0s</span><br><span class="line">  secretName: easysearch-ca-secret</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>Easysearch Chart 默认开启了安全功能，需要在 <code>es</code> 命名空间下找到一个名为 <strong><code>easysearch-secrets</code></strong> 的 Secret，用来存放集群的初始化密码或通信密钥。</p><p>之前创建的是 <code>easysearch-ca-secret</code>（CA 证书），但系统还在找这个基础的 <code>easysearch-secrets</code>。</p><p>使用这个创建集群需要的secret：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">kubectl create secret generic easysearch-secrets -n es \</span><br><span class="line">  --from-literal=ezs_username=admin \</span><br><span class="line">  --from-literal=ezs_password=easysearchpaswd</span><br></pre></td></tr></table></figure><p>另外在启动集群之前，别忘记修改max_map_count：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 临时生效</span><br><span class="line">sudo sysctl -w vm.max_map_count=262144</span><br><span class="line"></span><br><span class="line"># 永久生效（防止重启失效）</span><br><span class="line">echo &quot;vm.max_map_count=262144&quot; | sudo tee -a /etc/sysctl.conf</span><br></pre></td></tr></table></figure><p>都准备了好了之后我们来使用helm能够很方便的安装Easysearch：</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">helm install easysearch infinilabs/easysearch -n es</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2026/03/04/1772629576758-fd1f8d78-cdf0-4787-83ed-284f94545067.png"></p><p>如果你的某个步骤有问题，修改配置之后需要重启，那么可以直接删除这个pod，然后K3S会自动按照当前配置拉起来一个最新的。</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">kubectl delete pod easysearch-0 -n es</span><br></pre></td></tr></table></figure><p>在这个过程过，我发现helm里的Easysearch版本比较旧</p><p>不需要 <code>uninstall</code>，直接运行 <code>upgrade</code>。这样子就会触发 Kubernetes 的 **RollingUpdate (滚动更新)**：它会先停掉旧的 Pod，挂载原来的数据卷，然后启动 2.0.2 的新容器。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 使用 upgrade 命令，强行覆盖镜像 Tag</span><br><span class="line">helm upgrade easysearch infinilabs/easysearch -n es \</span><br><span class="line">  --reuse-values \</span><br><span class="line">  --set image.tag=2.0.2</span><br></pre></td></tr></table></figure><p>然后可以使用helm继续安装console。</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">helm install console infinilabs/console -n es</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/03/04/1772629622370-3f6bffa5-c78e-4451-ae51-0468d6a17d5a.png"></p><p>我的集群开了http端口，可以进入pod进去call api。但是https没有反应。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">kubectl exec -it easysearch-0 -n es -- curl -u admin:easysearchpaswd http://127.0.0.1:9200</span><br><span class="line">Defaulted container &quot;easysearch&quot; out of: easysearch, init-config (init)</span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot; : &quot;easysearch-0&quot;,</span><br><span class="line">  &quot;cluster_name&quot; : &quot;infinilabs&quot;,</span><br><span class="line">  &quot;cluster_uuid&quot; : &quot;oqn-k99eS32e0IRMlxrcHg&quot;,</span><br><span class="line">  &quot;version&quot; : &#123;</span><br><span class="line">    &quot;distribution&quot; : &quot;easysearch&quot;,</span><br><span class="line">    &quot;number&quot; : &quot;1.13.0&quot;,</span><br><span class="line">    &quot;distributor&quot; : &quot;INFINI Labs&quot;,</span><br><span class="line">    &quot;build_hash&quot; : &quot;5b73b39bc689f1366b09987fa07eee07ee89c2f6&quot;,</span><br><span class="line">    &quot;build_date&quot; : &quot;2025-06-11T07:39:43.374688Z&quot;,</span><br><span class="line">    &quot;build_snapshot&quot; : false,</span><br><span class="line">    &quot;lucene_version&quot; : &quot;8.11.4&quot;,</span><br><span class="line">    &quot;minimum_wire_lucene_version&quot; : &quot;7.7.0&quot;,</span><br><span class="line">    &quot;minimum_lucene_index_compatibility_version&quot; : &quot;7.7.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;tagline&quot; : &quot;You Know, For Easy Search!&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">kubectl exec -it easysearch-0 -n es -- curl -k -u admin:easysearch https://127.0.0.1:9200</span><br><span class="line">Defaulted container &quot;easysearch&quot; out of: easysearch, init-config (init)</span><br><span class="line">curl: (35) error:0A0000C6:SSL routines::packet length too long</span><br><span class="line">command terminated with exit code 35</span><br></pre></td></tr></table></figure><p>如果想清理数据或者重装，可以使用这些命令。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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><br><span class="line">helm uninstall easysearch console  -n es</span><br><span class="line"></span><br><span class="line"># 清理残留的数据卷 (数据会被删除，请谨慎操作)</span><br><span class="line">kubectl delete pvc -n es \</span><br><span class="line">  easysearch-data-easysearch-0 \</span><br><span class="line">  easysearch-config-easysearch-0 \</span><br><span class="line">  console-data-console-0 \</span><br><span class="line">  console-config-console-0 \</span><br></pre></td></tr></table></figure><p>那么在pod中我们怎么访问Easysearch呢？<br>我用busybox 当做例子，写了三种访问的方式</p><ol><li>使用POD的IP地址</li><li>使用service的名字访问</li><li>使用pod.service访问（其实就是Headless Service）</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/03/04/1772629602528-e66bcdad-b7a0-4526-82ec-584cf9678e3a.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">kubectl run busybox-debug --rm -it --image=busybox -n es -- /bin/sh</span><br><span class="line">All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.</span><br><span class="line">If you don&#x27;t see a command prompt, try pressing enter.</span><br><span class="line">/ # curl</span><br><span class="line">/bin/sh: curl: not found</span><br><span class="line"></span><br><span class="line"># 1:使用POD的IP地址</span><br><span class="line">/ # wget -qO- http://admin:easysearchpaswd@10.42.0.78:9200</span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot; : &quot;easysearch-0&quot;,</span><br><span class="line">  &quot;cluster_name&quot; : &quot;infinilabs&quot;,</span><br><span class="line">  &quot;cluster_uuid&quot; : &quot;c7pjDjA4SJSQixsbrdy1GQ&quot;,</span><br><span class="line">  &quot;version&quot; : &#123;</span><br><span class="line">    &quot;distribution&quot; : &quot;easysearch&quot;,</span><br><span class="line">    &quot;number&quot; : &quot;1.13.0&quot;,</span><br><span class="line">    &quot;distributor&quot; : &quot;INFINI Labs&quot;,</span><br><span class="line">    &quot;build_hash&quot; : &quot;5b73b39bc689f1366b09987fa07eee07ee89c2f6&quot;,</span><br><span class="line">    &quot;build_date&quot; : &quot;2025-06-11T07:39:43.374688Z&quot;,</span><br><span class="line">    &quot;build_snapshot&quot; : false,</span><br><span class="line">    &quot;lucene_version&quot; : &quot;8.11.4&quot;,</span><br><span class="line">    &quot;minimum_wire_lucene_version&quot; : &quot;7.7.0&quot;,</span><br><span class="line">    &quot;minimum_lucene_index_compatibility_version&quot; : &quot;7.7.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;tagline&quot; : &quot;You Know, For Easy Search!&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#2. 使用service的名字访问</span><br><span class="line">/ # wget -qO- http://admin:easysearchpaswd@easysearch:9200</span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot; : &quot;easysearch-0&quot;,</span><br><span class="line">  &quot;cluster_name&quot; : &quot;infinilabs&quot;,</span><br><span class="line">  &quot;cluster_uuid&quot; : &quot;c7pjDjA4SJSQixsbrdy1GQ&quot;,</span><br><span class="line">  &quot;version&quot; : &#123;</span><br><span class="line">    &quot;distribution&quot; : &quot;easysearch&quot;,</span><br><span class="line">    &quot;number&quot; : &quot;1.13.0&quot;,</span><br><span class="line">    &quot;distributor&quot; : &quot;INFINI Labs&quot;,</span><br><span class="line">    &quot;build_hash&quot; : &quot;5b73b39bc689f1366b09987fa07eee07ee89c2f6&quot;,</span><br><span class="line">    &quot;build_date&quot; : &quot;2025-06-11T07:39:43.374688Z&quot;,</span><br><span class="line">    &quot;build_snapshot&quot; : false,</span><br><span class="line">    &quot;lucene_version&quot; : &quot;8.11.4&quot;,</span><br><span class="line">    &quot;minimum_wire_lucene_version&quot; : &quot;7.7.0&quot;,</span><br><span class="line">    &quot;minimum_lucene_index_compatibility_version&quot; : &quot;7.7.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;tagline&quot; : &quot;You Know, For Easy Search!&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#3. 使用pod.service访问</span><br><span class="line">/ # wget -qO- http://admin:easysearchpaswd@easysearch-0.easysearch:9200</span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot; : &quot;easysearch-0&quot;,</span><br><span class="line">  &quot;cluster_name&quot; : &quot;infinilabs&quot;,</span><br><span class="line">  &quot;cluster_uuid&quot; : &quot;c7pjDjA4SJSQixsbrdy1GQ&quot;,</span><br><span class="line">  &quot;version&quot; : &#123;</span><br><span class="line">    &quot;distribution&quot; : &quot;easysearch&quot;,</span><br><span class="line">    &quot;number&quot; : &quot;1.13.0&quot;,</span><br><span class="line">    &quot;distributor&quot; : &quot;INFINI Labs&quot;,</span><br><span class="line">    &quot;build_hash&quot; : &quot;5b73b39bc689f1366b09987fa07eee07ee89c2f6&quot;,</span><br><span class="line">    &quot;build_date&quot; : &quot;2025-06-11T07:39:43.374688Z&quot;,</span><br><span class="line">    &quot;build_snapshot&quot; : false,</span><br><span class="line">    &quot;lucene_version&quot; : &quot;8.11.4&quot;,</span><br><span class="line">    &quot;minimum_wire_lucene_version&quot; : &quot;7.7.0&quot;,</span><br><span class="line">    &quot;minimum_lucene_index_compatibility_version&quot; : &quot;7.7.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;tagline&quot; : &quot;You Know, For Easy Search!&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">在 k3s 集群上使用 Helm 快速部署 Easysearch 搜索引擎的实战记录。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>k8s yml小抄</title>
    <link href="https://blog.no-claw.com/posts/e2ac0d70/"/>
    <id>https://blog.no-claw.com/posts/e2ac0d70/</id>
    <published>2026-03-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>源码来自 <a href="https://github.com/cloudacademy/intro-to-k8s/tree/master/src">cloudacademy&#x2F;intro-to-k8s</a>，用作学习笔记</p></blockquote><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/03/09/1773059681103-5f9751bb-be0f-4e82-bcf1-1395556b634e.png"></p><h3 id="1-Pod-基础"><a href="#1-Pod-基础" class="headerlink" title="1. Pod 基础"></a>1. Pod 基础</h3><h6 id="1-1-最简-Pod（1-1-basic-pod-yaml）"><a href="#1-1-最简-Pod（1-1-basic-pod-yaml）" class="headerlink" title="1.1 最简 Pod（1.1-basic_pod.yaml）"></a>1.1 最简 Pod（1.1-basic_pod.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypod</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mycontainer</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br></pre></td></tr></table></figure><p>这是最小化的 Pod 定义。只需要四个顶级字段：</p><ul><li><code>apiVersion: v1</code> — 核心 API 版本</li><li><code>kind: Pod</code> — 资源类型</li><li><code>metadata.name</code> — Pod 名称，命名空间内唯一</li><li><code>spec.containers</code> — 至少一个容器，必须指定 <code>name</code> 和 <code>image</code></li></ul><p>注意：使用 <code>nginx:latest</code> 时，Kubernetes 默认 <code>imagePullPolicy: Always</code>，每次启动都会拉取镜像。</p><hr><h6 id="1-2-声明端口（1-2-port-pod-yaml）"><a href="#1-2-声明端口（1-2-port-pod-yaml）" class="headerlink" title="1.2 声明端口（1.2-port_pod.yaml）"></a>1.2 声明端口（1.2-port_pod.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypod</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mycontainer</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></table></figure><span id="more"></span><p>相比 1.1 新增了 <code>ports.containerPort: 80</code>。如果不声明端口，<code>kubectl describe</code> 中端口显示为 none，外部无法知道容器监听哪个端口。声明端口是让 Kubernetes 知道容器对外提供服务的方式。</p><p>注意：即使声明了端口，从集群外部仍然无法直接访问 Pod IP（Pod IP 在容器网络内），需要通过 Service 暴露。</p><hr><h6 id="1-3-添加标签（1-3-labeled-pod-yaml）"><a href="#1-3-添加标签（1-3-labeled-pod-yaml）" class="headerlink" title="1.3 添加标签（1.3-labeled_pod.yaml）"></a>1.3 添加标签（1.3-labeled_pod.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypod</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">webserver</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mycontainer</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></table></figure><p>新增 <code>labels.app: webserver</code>。标签是键值对，用途：</p><ul><li>标识资源属性（应用类型、层级、区域等）</li><li>被 Service 的 <code>selector</code> 用来匹配目标 Pod</li><li>被 <code>kubectl get</code> 的 <code>-l</code> 选项用来过滤资源</li></ul><p>标签是 Kubernetes 中资源关联的核心机制。</p><hr><h6 id="1-4-资源请求与限制（1-4-resources-pod-yaml）"><a href="#1-4-资源请求与限制（1-4-resources-pod-yaml）" class="headerlink" title="1.4 资源请求与限制（1.4-resources_pod.yaml）"></a>1.4 资源请求与限制（1.4-resources_pod.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypod</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">webserver</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mycontainer</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="attr">requests:</span></span><br><span class="line">        <span class="attr">memory:</span> <span class="string">&quot;128Mi&quot;</span>  <span class="comment"># 128 MiB</span></span><br><span class="line">        <span class="attr">cpu:</span> <span class="string">&quot;500m&quot;</span>      <span class="comment"># 0.5 CPU</span></span><br><span class="line">      <span class="attr">limits:</span></span><br><span class="line">        <span class="attr">memory:</span> <span class="string">&quot;128Mi&quot;</span></span><br><span class="line">        <span class="attr">cpu:</span> <span class="string">&quot;500m&quot;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></table></figure><p>新增 <code>resources</code> 字段：</p><ul><li><code>requests</code>：调度器据此选择节点，节点必须有足够的可分配资源</li><li><code>limits</code>：容器运行时的资源上限，超过内存限制会被 OOMKilled</li></ul><p>这里 requests &#x3D; limits，QoS 等级为 Guaranteed（最不容易被驱逐）。不设置任何资源则为 BestEffort（最先被驱逐）。生产环境应始终设置资源请求。</p><hr><h3 id="2-Service"><a href="#2-Service" class="headerlink" title="2. Service"></a>2. Service</h3><h6 id="2-1-NodePort-Service（2-1-web-service-yaml）"><a href="#2-1-NodePort-Service（2-1-web-service-yaml）" class="headerlink" title="2.1 NodePort Service（2.1-web_service.yaml）"></a>2.1 NodePort Service（2.1-web_service.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">webserver</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">webserver</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">webserver</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br></pre></td></tr></table></figure><ul><li><code>selector.app: webserver</code> — 匹配带有 <code>app=webserver</code> 标签的 Pod</li><li><code>ports.port: 80</code> — Service 端口，对应 Pod 的 containerPort</li><li><code>type: NodePort</code> — 在每个节点上分配一个端口（30000–32767），集群外部可通过 <code>节点IP:NodePort</code> 访问</li></ul><p>Service 解决的核心问题：Pod IP 不固定，Service 提供稳定入口并自动负载均衡。</p><hr><h3 id="3-多容器-Pod-与命名空间"><a href="#3-多容器-Pod-与命名空间" class="headerlink" title="3. 多容器 Pod 与命名空间"></a>3. 多容器 Pod 与命名空间</h3><h6 id="3-1-命名空间（3-1-namespace-yaml）"><a href="#3-1-命名空间（3-1-namespace-yaml）" class="headerlink" title="3.1 命名空间（3.1-namespace.yaml）"></a>3.1 命名空间（3.1-namespace.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Namespace</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">microservice</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">counter</span></span><br></pre></td></tr></table></figure><p>命名空间用于隔离资源。不需要 spec，只需 name。使用 <code>-n microservice</code> 指定命名空间。</p><hr><h6 id="3-2-多容器-Pod（3-2-multi-container-yaml）"><a href="#3-2-多容器-Pod（3-2-multi-container-yaml）" class="headerlink" title="3.2 多容器 Pod（3.2-multi_container.yaml）"></a>3.2 多容器 Pod（3.2-multi_container.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">redis:latest</span></span><br><span class="line">      <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span>  <span class="comment"># 防止每次拉取 latest</span></span><br><span class="line">      <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">6379</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">server</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:server-v1</span></span><br><span class="line">      <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">REDIS_URL</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">redis://localhost:6379</span>  <span class="comment"># 同 Pod 内用 localhost</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">counter</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:counter-v1</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">http://localhost:8080</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">poller</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:poller-v1</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">http://localhost:8080</span></span><br></pre></td></tr></table></figure><p>4 个容器在同一个 Pod 中，共享网络栈：</p><ul><li>Redis（数据层）监听 6379</li><li>Server（应用层）监听 8080，通过 <code>localhost:6379</code> 连接 Redis</li><li>Counter 和 Poller（支持层）通过 <code>localhost:8080</code> 连接 Server</li></ul><p><code>imagePullPolicy: IfNotPresent</code> 用于 <code>latest</code> 标签时防止每次都拉取。使用具体标签时默认就是 IfNotPresent。</p><p>局限性：Kubernetes 以 Pod 为最小扩缩单位，无法单独扩缩某个容器。如果需要独立扩缩，应拆分为多个 Pod + Service。</p><hr><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/03/09/1773059617005-24dc90a1-8781-4e18-86d9-00fbda097882.png"></p><h3 id="4-服务发现（Service-Discovery）"><a href="#4-服务发现（Service-Discovery）" class="headerlink" title="4. 服务发现（Service Discovery）"></a>4. 服务发现（Service Discovery）</h3><h6 id="4-1-命名空间（4-1-namespace-yaml）"><a href="#4-1-命名空间（4-1-namespace-yaml）" class="headerlink" title="4.1 命名空间（4.1-namespace.yaml）"></a>4.1 命名空间（4.1-namespace.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Namespace</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">service-discovery</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">counter</span></span><br></pre></td></tr></table></figure><p>为服务发现课程创建独立的命名空间 <code>service-discovery</code>，隔离本课资源。后续所有命令需要加 <code>-n service-discovery</code>。</p><hr><h6 id="4-2-数据层-—-Pod-ClusterIP-Service（4-2-data-tier-yaml）"><a href="#4-2-数据层-—-Pod-ClusterIP-Service（4-2-data-tier-yaml）" class="headerlink" title="4.2 数据层 — Pod + ClusterIP Service（4.2-data_tier.yaml）"></a>4.2 数据层 — Pod + ClusterIP Service（4.2-data_tier.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Service</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">data-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">data</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ClusterIP</span>    <span class="comment"># 默认类型，仅集群内可访问</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># Pod</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">data-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">data</span>        <span class="comment"># 被 Service selector 匹配</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">redis:latest</span></span><br><span class="line">      <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">      <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">6379</span></span><br></pre></td></tr></table></figure><p>将 Redis 拆分为独立 Pod + ClusterIP Service。<code>type: ClusterIP</code> 是默认值，仅集群内部可访问。<code>name: redis</code> 为端口命名，后续可通过环境变量引用。</p><hr><h6 id="4-3-应用层-—-环境变量服务发现（4-3-app-tier-yaml）"><a href="#4-3-应用层-—-环境变量服务发现（4-3-app-tier-yaml）" class="headerlink" title="4.3 应用层 — 环境变量服务发现（4.3-app_tier.yaml）"></a>4.3 应用层 — 环境变量服务发现（4.3-app_tier.yaml）</h6><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">server</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:server-v1</span></span><br><span class="line">      <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">REDIS_URL</span></span><br><span class="line">          <span class="comment"># Environment variable service discovery</span></span><br><span class="line">          <span class="comment"># Naming pattern:</span></span><br><span class="line">          <span class="comment">#   IP address: &lt;all_caps_service_name&gt;_SERVICE_HOST</span></span><br><span class="line">          <span class="comment">#   Port: &lt;all_caps_service_name&gt;_SERVICE_PORT</span></span><br><span class="line">          <span class="comment">#   Named Port: &lt;all_caps_service_name&gt;_SERVICE_PORT_&lt;all_caps_port_name&gt;</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">redis://$(DATA_TIER_SERVICE_HOST):$(DATA_TIER_SERVICE_PORT_REDIS)</span></span><br><span class="line">          <span class="comment"># In multi-container example value was</span></span><br><span class="line">          <span class="comment"># value: redis://localhost:6379</span></span><br></pre></td></tr></table></figure><p>Kubernetes 自动为同命名空间的每个 Service 注入环境变量：</p><ul><li><code>&lt;SERVICE_NAME&gt;_SERVICE_HOST</code> → Service 的 ClusterIP</li><li><code>&lt;SERVICE_NAME&gt;_SERVICE_PORT</code> → Service 的端口</li><li><code>&lt;SERVICE_NAME&gt;_SERVICE_PORT_&lt;PORT_NAME&gt;</code> → 命名端口</li></ul><p>这里 <code>DATA_TIER_SERVICE_HOST</code> 和 <code>DATA_TIER_SERVICE_PORT_REDIS</code> 由 Kubernetes 自动注入，无需硬编码 IP。对比多容器 Pod 中的 <code>redis://localhost:6379</code>，现在通过 Service 跨 Pod 通信。</p><hr><h6 id="4-4-支持层-—-DNS-服务发现（4-4-support-tier-yaml）"><a href="#4-4-支持层-—-DNS-服务发现（4-4-support-tier-yaml）" class="headerlink" title="4.4 支持层 — DNS 服务发现（4.4-support_tier.yaml）"></a>4.4 支持层 — DNS 服务发现（4.4-support_tier.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">support-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">support</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">counter</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:counter-v1</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">          <span class="comment"># DNS for service discovery</span></span><br><span class="line">          <span class="comment"># Naming pattern:</span></span><br><span class="line">          <span class="comment">#   IP address: &lt;service_name&gt;.&lt;service_namespace&gt;</span></span><br><span class="line">          <span class="comment">#   Port: needs to be extracted from SRV DNS record</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">http://app-tier.service-discovery:8080</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">poller</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">lrakai/microservices:poller-v1</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">          <span class="comment"># omit namespace to only search in the same namespace</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">http://app-tier:$(APP_TIER_SERVICE_PORT)</span></span><br></pre></td></tr></table></figure><p>两种服务发现方式对比：</p><ul><li>DNS：<code>&lt;service-name&gt;.&lt;namespace&gt;</code> 或同命名空间内直接用 <code>&lt;service-name&gt;</code></li><li>环境变量：<code>$(&lt;SERVICE_NAME&gt;_SERVICE_PORT)</code> 等</li></ul><p>counter 用了完整 DNS（<code>app-tier.service-discovery:8080</code>），poller 省略了命名空间并混合使用了环境变量。DNS 方式更灵活，推荐使用。</p><hr><h3 id="5-Deployment"><a href="#5-Deployment" class="headerlink" title="5. Deployment"></a>5. Deployment</h3><h6 id="5-2-数据层-Deployment（5-2-data-tier-yaml）"><a href="#5-2-数据层-Deployment（5-2-data-tier-yaml）" class="headerlink" title="5.2 数据层 Deployment（5.2-data_tier.yaml）"></a>5.2 数据层 Deployment（5.2-data_tier.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span>          <span class="comment"># Deployment 使用 apps API 组</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">data-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">data</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span>                <span class="comment"># 副本数</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">tier:</span> <span class="string">data</span>             <span class="comment"># 必须与 template.metadata.labels 匹配</span></span><br><span class="line">  <span class="attr">template:</span>                  <span class="comment"># Pod 模板</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">        <span class="attr">tier:</span> <span class="string">data</span></span><br><span class="line">    <span class="attr">spec:</span>                    <span class="comment"># Pod spec</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">redis:latest</span></span><br><span class="line">        <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">6379</span></span><br></pre></td></tr></table></figure><p>从裸 Pod 升级为 Deployment：</p><ul><li><code>replicas</code>：期望的 Pod 副本数</li><li><code>selector.matchLabels</code>：Deployment 用来管理 Pod 的标签选择器</li><li><code>template</code>：Pod 模板，Deployment 据此创建和管理 Pod</li><li>Deployment 提供滚动更新、回滚、自愈（Pod 挂了自动重建）</li></ul><hr><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img2@main/2026/03/09/1773059647273-a15f4913-1e2c-4948-9b4b-082d85b4cff5.png"></p><h6 id="5-3-应用层-Deployment（5-3-app-tier-yaml）"><a href="#5-3-应用层-Deployment（5-3-app-tier-yaml）" class="headerlink" title="5.3 应用层 Deployment（5.3-app_tier.yaml）"></a>5.3 应用层 Deployment（5.3-app_tier.yaml）</h6><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">        <span class="attr">tier:</span> <span class="string">app</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">server</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">lrakai/microservices:server-v1</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">REDIS_URL</span></span><br><span class="line">            <span class="attr">value:</span> <span class="string">redis://$(DATA_TIER_SERVICE_HOST):$(DATA_TIER_SERVICE_PORT_REDIS)</span></span><br></pre></td></tr></table></figure><p>与 4.3 相同的 Service + 环境变量服务发现，但 Pod 改为 Deployment 管理。</p><hr><h6 id="5-4-支持层-Deployment（5-4-support-tier-yaml）"><a href="#5-4-支持层-Deployment（5-4-support-tier-yaml）" class="headerlink" title="5.4 支持层 Deployment（5.4-support_tier.yaml）"></a>5.4 支持层 Deployment（5.4-support_tier.yaml）</h6><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">support-tier</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">support</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">tier:</span> <span class="string">support</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">microservices</span></span><br><span class="line">        <span class="attr">tier:</span> <span class="string">support</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">        <span class="attr">containers:</span></span><br><span class="line"></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">counter</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">lrakai/microservices:counter-v1</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">http://app-tier.deployments:8080</span></span><br><span class="line"></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">poller</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">lrakai/microservices:poller-v1</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">API_URL</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">http://app-tier:$(APP_TIER_SERVICE_PORT)</span></span><br></pre></td></tr></table></figure><p>与 4.4 相同的 DNS 服务发现，但 Pod 改为 Deployment 管理。注意支持层没有 Service（不需要被其他组件访问）。DNS 中的命名空间从 <code>service-discovery</code> 变成了 <code>deployments</code>。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/03/09/1773059667590-ddd7389c-7d97-42b8-889d-5da1e3bbc731.png"></p>]]></content>
    
    
    <summary type="html">Kubernetes 常用 YAML 配置速查，涵盖 Pod、Service、Deployment 等资源定义，基于 cloudacademy 源码整理。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="Kubernetes" scheme="https://blog.no-claw.com/tags/Kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>开源OpenSearch学习笔记｜部署篇（二）：Kubernetes 上用 Helm 快速搭建 OpenSearch.</title>
    <link href="https://blog.no-claw.com/posts/1e02510f/"/>
    <id>https://blog.no-claw.com/posts/1e02510f/</id>
    <published>2026-03-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>OpenSearch 是一个开源的分布式搜索和分析引擎，适用于日志分析、全文检索、应用监控等场景。本文将介绍如何使用 Helm 在 Kubernetes 集群上快速部署 OpenSearch 及 OpenSearch Dashboards，并通过 Secret 管理密码，避免将敏感信息硬编码在配置文件中。本文的 K8S 环境基于 Amazon EKS，部分步骤涉及 EBS CSI Driver 的配置，如果你使用其他 K8S 发行版可以跳过相关内容。</p><h3 id="添加-Helm-仓库"><a href="#添加-Helm-仓库" class="headerlink" title="添加 Helm 仓库"></a>添加 Helm 仓库</h3><p>官方提供了helm包，这使得我们可以很方便的下载OpenSearch的helm chart并且进行更新下载：</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">helm repo add opensearch https://opensearch-project.github.io/helm-charts/</span><br><span class="line">helm repo update</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/03/23/1774263162178-743ea55e-8c3a-470c-bc50-cf249e612246.png"></p><h3 id="创建-Secret-管理密码"><a href="#创建-Secret-管理密码" class="headerlink" title="创建 Secret 管理密码"></a>创建 Secret 管理密码</h3><p>安装文档上来说，我们需要在value.yml里添加我们的密码，不过在我看来这不是一个比较优雅的做法，于是我采取了使用secret的方式来管理密码，这样可以把密码和Helm Chart进行解耦。先创建名为opensearch-admin-secret 的secret，然后指定密码：</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">kubectl create secret generic opensearch-admin-secret \</span><br><span class="line">  --from-literal=OPENSEARCH_INITIAL_ADMIN_PASSWORD=<span class="string">&#x27;&lt;your-password&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="安装-OpenSearch"><a href="#安装-OpenSearch" class="headerlink" title="安装 OpenSearch"></a>安装 OpenSearch</h3><p>然后再使用helm install安装opensearch，在这个时候把刚刚创建的secret传进去。</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">helm install opensearch opensearch/opensearch \</span><br><span class="line">  --<span class="built_in">set</span> <span class="string">&#x27;envFrom[0].secretRef.name=opensearch-admin-secret&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="EKS-环境配置（非-EKS-用户可跳过）"><a href="#EKS-环境配置（非-EKS-用户可跳过）" class="headerlink" title="EKS 环境配置（非 EKS 用户可跳过）"></a>EKS 环境配置（非 EKS 用户可跳过）</h3><p>由于我用的是亚马逊云科技的EKS，实际上还要处理关于CSI driver的问题。包括把gp2设置为默认的storageclass，以及使用 AmazonEBSCSIDriverPolicy这个策略，授权 EBS CSI Driver 操作 EBS 卷所需的 EC2 API 权限。</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">kubectl patch storageclass gp2 -p <span class="string">&#x27;&#123;&quot;metadata&quot;: &#123;&quot;annotations&quot;:&#123;&quot;storageclass.kubernetes.io/is-default-class&quot;:&quot;true&quot;&#125;&#125;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>主要包括：</p><ul><li>ec2:CreateVolume &#x2F; ec2:DeleteVolume — 创建和删除 EBS 卷</li><li>ec2:AttachVolume &#x2F; ec2:DetachVolume — 挂载和卸载卷到 EC2 实例</li><li>ec2:DescribeVolumes &#x2F; ec2:DescribeInstances — 查询卷和实例信息</li><li>ec2:CreateSnapshot &#x2F; ec2:DeleteSnapshot — 快照操作</li><li>ec2:DescribeAvailabilityZones — 查询 AZ 信息</li></ul><p>不过需要注意的是，通常来讲，在EKS上需要创建iam-oidc-provider 以及 iamserviceaccount来关联这个策略，以此来获得操作AWS API的权利。不过为了防止文章更加偏向于EKS，我修改了 hop limit，这是让EKS 的pod 可以像普通K8S一样访问host的资源。生产环境推荐使用 IRSA（IAM Roles for Service Accounts）通过 OIDC provider 关联 IAM 策略，这是 EKS 的最佳实践。本文为了简化流程，采用了修改 hop limit 的方式。</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"># 获取每个node的 instance ID 然后修改</span></span><br><span class="line">aws ec2 modify-instance-metadata-options \</span><br><span class="line">  --instance-id &lt;instance-id&gt; \</span><br><span class="line">  --http-put-response-hop-limit 2 \</span><br><span class="line">  --region &lt;region&gt;</span><br></pre></td></tr></table></figure><p>简单解释下：Pod 在容器网络里，经过 veth pair 到宿主机网络再到 IMDS，比实例本身多了一跳。所以 hop limit 为 1 时，Pod 里的 EBS CSI controller 无法访问 IMDS，拿不到 IAM 凭证，所有 AWS API 调用都失败。改成 2 就是允许多一跳，让 Pod 也能访问 IMDS。当然，如果你用的是其他K8S的发行版那么就可以跳过这个问题。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2026/03/23/1774263093635-519eece7-6810-4f9d-9ae0-9c4256912bf4.png"></p><h3 id="重建与缩容"><a href="#重建与缩容" class="headerlink" title="重建与缩容"></a>重建与缩容</h3><p>修改之后我们删除Helm Chart并且重建：</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">helm uninstall opensearch</span><br><span class="line">kubectl delete pvc -l app.kubernetes.io/name=opensearch</span><br><span class="line">helm install opensearch opensearch/opensearch \</span><br><span class="line">  --<span class="built_in">set</span> <span class="string">&#x27;envFrom[0].secretRef.name=opensearch-admin-secret&#x27;</span></span><br></pre></td></tr></table></figure><p>如果需要更新，那么可以使用helm upgrade 来修改参数，helm默认启动了3个master节点，而我只有两个node，为了合理的分配一下资源，我决定对pod数量进行缩容。</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">helm upgrade opensearch opensearch/opensearch \</span><br><span class="line">  --<span class="built_in">set</span> replicas=2 \</span><br><span class="line">  --<span class="built_in">set</span> <span class="string">&#x27;envFrom[0].secretRef.name=opensearch-admin-secret&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="验证与测试"><a href="#验证与测试" class="headerlink" title="验证与测试"></a>验证与测试</h3><p>这样我们多节点的OpenSearch Cluster就启动好了，当然服务在pod里，我们如果想本地测试（因为EKS控制平面是托管的，我无法登陆），所以使用kubectl port-forward来进行转发，这样就可以把端口代理到本地。</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">kubectl port-forward svc/opensearch-cluster-master 9200:9200 &amp;</span><br><span class="line">curl -sk https://localhost:9200/_cluster/health\?pretty -u <span class="string">&#x27;admin:&lt;your-password&gt;&#x27;</span></span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2026/03/23/1774263112576-fab81fc9-de86-47fc-a65a-0140c8c7e453.png"></p><h3 id="安装-OpenSearch-Dashboards"><a href="#安装-OpenSearch-Dashboards" class="headerlink" title="安装 OpenSearch Dashboards"></a>安装 OpenSearch Dashboards</h3><p>当然opensearch-dashboards也可以如法炮制进行安装：</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"># helm 安装opensearch-dashboards</span></span><br><span class="line">helm install dashboards opensearch/opensearch-dashboards \</span><br><span class="line">  --<span class="built_in">set</span> opensearchHosts=<span class="string">&quot;https://opensearch-cluster-master:9200&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 端口转发</span></span><br><span class="line">kubectl port-forward svc/dashboards-opensearch-dashboards 5601:5601</span><br></pre></td></tr></table></figure><p>然后通过浏览器打开 <a href="http://localhost:5601/">http://localhost:5601</a> 登录，能够看到我们刚刚通过helm创建的两个Pod。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img12@main/2026/03/23/1774263121308-4bd371f7-dc36-46a1-9705-9b158cc16d70.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>到这里，我们已经通过 Helm 在 Kubernetes 上成功部署了 OpenSearch 集群和 Dashboards。整个过程中我们用 Secret 解耦了密码管理，用 Helm upgrade 灵活调整了副本数。如果你想进一步探索，可以尝试配置 Ingress 暴露服务、接入 Fluent Bit 采集日志，或者通过 Index State Management 管理索引生命周期。希望这篇文章对你有帮助。</p>]]></content>
    
    
    <summary type="html">开源OpenSearch学习笔记｜部署篇（二）：Kubernetes 上用 Helm 快速搭建 OpenSearch.</summary>
    
    
    
    <category term="Kubernetes" scheme="https://blog.no-claw.com/categories/Kubernetes/"/>
    
    
    <category term="Kubernetes" scheme="https://blog.no-claw.com/tags/Kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十六）：懒猫微服QEMU虚拟机快速上手</title>
    <link href="https://blog.no-claw.com/posts/5f202ca3/"/>
    <id>https://blog.no-claw.com/posts/5f202ca3/</id>
    <published>2026-02-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>对于 NAS 玩家来说，虚拟机绝对是标配。今天我们要介绍的主角是 <strong>QEMU</strong>。你可能会觉得它太过底层、全命令行操作太硬核，但别担心，看过这篇文章之后，你就能轻松在懒猫微服上操作它。</p><p>在传统 Linux 下装 QEMU，你可能要折腾一堆 <code>kvm-ok</code> 检测、各种动态库依赖。但在懒猫微服上，直接从商店下载即可。这就是<strong>全容器化</strong>的好处：环境全封闭，不会把宿主机的依赖搞坏，不用再和底层依赖打交道，这就是懒猫微服全容器化的好处，彻底解决了让人头疼的环境问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260226215103173.png" alt="image-20260226215103173"></p><span id="more"></span><p>和其他虚拟机一样，我们需要一个 ISO 镜像。QEMU 厉害的地方在于它对镜像来源几乎从不挑剔</p><p>可以直接填入官网的 ISO 下载链接，边下边装，省去中间转手的麻烦。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260226215121062.png" alt="image-20260226215121062"></p><p>如果你的镜像在电脑里，或者在 NAS 上，那么可以直接在存放镜像的文件夹下打开终端，用一行 Python 命令把它变成“下载站”：</p><figure class="highlight plaintext"><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"># 进入镜像文件夹执行，默认开启 8000 端口</span><br><span class="line">python -m http.server 8000</span><br></pre></td></tr></table></figure><p>然后在 QEMU 的镜像地址里填入：<code>http://你的电脑IP:8000/ubuntu-22.04.iso</code>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260226215417448.png" alt="image-20260226215417448"></p><p>在懒猫微服的界面上，你只需要选好分给虚拟机的 <strong>CPU 核心数</strong>和<strong>内存大小</strong>。</p><p>如果你想稍微硬核一点，看看后台它是怎么运行的，其实 QEMU 常见的“加速”命令已经默默为你打好了。比如：</p><ul><li><code>-m 2G</code>：给虚拟机分配 2GB 内存。</li><li><code>-smp 4</code>：给虚拟机 4 核 CPU。</li></ul><p>当然你可以对这些参数进行修改，完成可以改成4C8G或者更高的配置。</p><p>当然也可以安装<strong>安卓系统</strong>，我尝试运行了BlissOS，很流畅，除了必要的X86和ARM指令集转换缺失之外，其他的都可以流程运行。甚至把虚拟机内部的 <strong>5555 端口</strong>（ADB 默认调试端口）通过端口映射的工具映射出来。这样，我们在局域网内的任何一台电脑上，只需要执行这个命令就可以进行ADB调试：</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">adb connect 你的NAS的IP:你映射的端口号</span><br></pre></td></tr></table></figure><p>进行端口转发之后，就可以像操作真机一样，直接用 <code>adb install</code> 往虚拟机里塞 APK，或者在线的调试。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260226214930363.png" alt="image-20260226214930363"></p><p>安装完成之后，完全不用担心远程连接的问题，甚至还自带一个web的no-VNC。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260226220427413.png" alt="image-20260226220427413"></p><p>好了，安装完了，咱们从纯技术的角度聊聊 QEMU，以及它和 KVM 到底是什么关系。</p><h4 id="QEMU：全能模拟器"><a href="#QEMU：全能模拟器" class="headerlink" title="QEMU：全能模拟器"></a><strong>QEMU：全能模拟器</strong></h4><p>QEMU 是一个纯软件实现的虚拟机。它的强大之处在于<strong>“无所不拟”</strong>：它可以在 x86 架构（普通电脑）上模拟出 ARM、MIPS 甚至 PowerPC 的环境。</p><ul><li><strong>代价：</strong> 这种“纯软件模拟”就像是找了个翻译官，每条指令都要翻译一次才能执行，所以单跑 QEMU 的速度比较慢，跑起来像在泥潭里走路。</li></ul><h4 id="KVM：内核加速器"><a href="#KVM：内核加速器" class="headerlink" title="KVM：内核加速器"></a><strong>KVM：内核加速器</strong></h4><p>KVM（Kernel-based Virtual Machine）是 Linux 内核的一个模块。它的作用是让虚拟机直接调用 CPU 的硬件虚拟化指令（如 Intel VT-x）。</p><ul><li><p><strong>优点：</strong> 它的性能极快，几乎能发挥出硬件的真实水平。</p></li><li><p><strong>局限：</strong> 但 KVM 很“高冷”，它只管 CPU 和内存，至于怎么模拟显卡、鼠标、USB 接口，它一概不管。</p></li></ul><p>在懒猫微服里，我们默认用的是 <strong>QEMU-KVM</strong> 模式：</p><ul><li><p><strong>KVM 负责干体力活：</strong> 接管 CPU 和内存，保证运行速度飞快。</p></li><li><p><strong>QEMU 负责干技术活：</strong> 模拟所有的外设（显卡、网卡、声卡、USB 驱动等），并提供管理功能。</p></li></ul><p><strong>总结：QEMU 是大脑和管家，KVM 是强壮的肌肉。两者是黄金搭档，才有了我们在懒猫微服上流畅的虚拟机体验。</strong></p>]]></content>
    
    
    <summary type="html">在懒猫微服上使用 QEMU 虚拟机，全容器化安装免去环境依赖烦恼，轻松上手虚拟机操作。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 数据映射之 Deep Dive：我踩过的 Volume 坑</title>
    <link href="https://blog.no-claw.com/posts/65e472bd/"/>
    <id>https://blog.no-claw.com/posts/65e472bd/</id>
    <published>2026-02-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在用 Docker 部署 Easysearch，本以为是个简单的事情，结果在数据持久化上栽了跟头，每次停止再启动容器之后都会503，在后面成了我百思不得其解的问题，后来一直在某次的meetup中，请教了原厂的罗老师，一句话点醒梦中人，Easysearch用的具名卷，防止宿主机的数据覆盖容器里的数据。<br>  <span id="more"></span></p><h3 id="数据映射的尝试"><a href="#数据映射的尝试" class="headerlink" title="数据映射的尝试"></a>数据映射的尝试</h3><p>volume 和 bind 我就纠结了好久，以前习惯使用的是bind的方式。</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">docker run -d \</span><br><span class="line">  -v ./node1/data:/app/easysearch/data \</span><br><span class="line">  -v ./node1/logs:/app/easysearch/logs \</span><br><span class="line">  -v ./node1/config:/app/easysearch/config \</span><br><span class="line">  infinilabs/easysearch:2.0.2-2499</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">docker logs easysearch-node1</span><br></pre></td></tr></table></figure><p>日志里提示 JVM 配置文件找不到，服务启动失败返回 503。原因：宿主机的 <code>./node1/config</code> 是空目录，Bind Mount 把它挂进去后，<strong>直接遮盖了容器内原有的 JVM 配置和默认配置文件</strong>。</p><p>老老实实按官方文档用 Named Volume：</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">docker run -d \</span><br><span class="line">  -v es-data1:/app/easysearch/data \</span><br><span class="line">  -v es-logs1:/app/easysearch/logs \</span><br><span class="line">  -v es-config1:/app/easysearch/config \</span><br><span class="line">  infinilabs/easysearch:2.0.2-2499</span><br></pre></td></tr></table></figure><p>Named Volume 挂载到容器内非空目录时，会<strong>自动把容器内的文件复制到卷里</strong>，包括 JVM 配置、默认配置等。集群顺利起来了：</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">curl -ku admin:admin https://localhost:9201/_cat/nodes?v</span><br><span class="line"></span><br><span class="line">ip         heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name</span><br><span class="line">172.24.0.3           68          31  31    1.67    0.57     0.21 dimr      -      easysearch-node1</span><br><span class="line">172.24.0.2           55          31  31    1.67    0.57     0.21 dimr      *      easysearch-node2</span><br></pre></td></tr></table></figure><h3 id="为什么-Easysearch-要用-Named-Volume？"><a href="#为什么-Easysearch-要用-Named-Volume？" class="headerlink" title="为什么 Easysearch 要用 Named Volume？"></a>为什么 Easysearch 要用 Named Volume？</h3><p>Easysearch 镜像内自带完整的默认配置：JVM 参数、节点配置、安全证书等。这些文件在容器的 <code>/app/easysearch/config</code> 目录里。</p><ul><li><strong>Named Volume</strong>：空卷挂载时，Docker 会把容器内的文件复制到卷里，JVM 配置等默认文件保留</li><li><strong>Bind Mount</strong>：宿主机目录直接遮盖容器内文件，空目录挂进去 &#x3D; 配置全丢</li></ul><p>除非你用 <code>init.sh</code> 之类的脚本在宿主机预先生成了所有配置文件（包括 JVM 配置），否则不要用 Bind Mount 挂 config 目录。</p><h3 id="附：Bind-Mount-vs-Volume-核心区别"><a href="#附：Bind-Mount-vs-Volume-核心区别" class="headerlink" title="附：Bind Mount vs Volume 核心区别"></a>附：Bind Mount vs Volume 核心区别</h3><table><thead><tr><th>特性</th><th>Bind Mount</th><th>Named Volume</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>容器内自带默认配置</td></tr></tbody></table><p><strong>关键行为差异：</strong></p><p>Bind Mount（<a href="https://docs.docker.com/engine/storage/bind-mounts/">Docker 官方文档</a>）：</p><blockquote><p>如果你把宿主机的文件或目录 bind mount 到容器内一个已有文件的目录，容器内原有的文件会被遮盖。</p></blockquote><p>Volume（<a href="https://docs.docker.com/engine/storage/volumes/">Docker 官方文档</a>）：</p><blockquote><p>如果你启动容器时创建了一个新卷，而容器内挂载目录（如 &#x2F;app&#x2F;）已有文件，Docker 会把这些文件复制到卷里。</p></blockquote><p>Easysearch 这种容器内自带 JVM 配置和默认配置的场景，Named Volume 才是正确选择。Bind Mount 空目录会把这些文件全遮盖掉，别像我一样自作聪明。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://infinilabs.cn/docs/latest/easysearch/getting-started/install/docker-compose/">Easysearch Docker 部署文档</a></li><li><a href="https://docs.docker.com/engine/storage/volumes/">Docker 官方文档 - Volumes</a></li><li><a href="https://docs.docker.com/engine/storage/bind-mounts/">Docker 官方文档 - Bind Mounts</a></li></ul>]]></content>
    
    
    <summary type="html">Docker 部署 Easysearch 时遇到的数据持久化问题，具名卷与绑定挂载的区别，以及 503 错误的排查过程。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>超效率手册</title>
    <link href="https://blog.no-claw.com/posts/b6e1e84a/"/>
    <id>https://blog.no-claw.com/posts/b6e1e84a/</id>
    <published>2026-02-24T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li><p>周&#x2F;日 计划</p></li><li><p>限制时间做事（30-90min） 紧迫感、</p></li><li><p>任务分解</p></li><li><p>短跑。起床坚持10分钟，其他事再坚持20分钟休息。 短跑30天习惯</p></li><li><p>日程校对，相信自己要做的事情。完全日程，避免过度工作和拖延。</p></li><li><p>自律。停下来之前再坚持10分钟。下次做事再坚持10-20%，专注一件事</p></li><li><p>语录刺激生产效率（便利贴</p><span id="more"></span></li><li><p>目标还是否能激励？有没有把生产力当作借口？</p></li><li><p>目标列出来看，承诺，便利贴看见</p></li><li><p>收集想法再整理剔除</p></li><li><p>尽可能的减少干扰</p></li><li><p>想要做&#x2F;不去做。拒绝应该做。</p></li><li><p>先做收到反馈再调整</p></li><li><p>占据你时间但是价值的事情 根除或者压缩时间</p></li><li><p>事情条理性，定期维护</p></li><li><p>任务和物品整理好固定位置。（少混乱，少压力）</p></li><li><p>随时把想法记录下来（捕捉灵感</p></li><li><p>所有物品规定地方（和16差不读</p></li><li><p>GTD组织系统（项目-任务-活动）</p></li><li><p>项目：大类</p></li><li><p>任务：小的独立的行动。（总体待办&#x2F;周目标&#x2F;日目标）</p></li><li><p>活动：特定时间干的事（避免周日历太多）</p></li><li><p>写下目标，1. 不会遗忘。2. 有动力实现 3.模糊的想法变成具体目标</p></li><li><p>没有完美的系统就是设置分支，多文件夹 或者 决策的if else</p></li><li><p>通信记录承诺的事（日期时间 谁 内容 联系方式）</p></li><li><p>承诺的事整理起来不要忘记 设置时间线</p></li><li><p>有条理 减少检索时间</p></li><li><p>阅读笔记 - 提取关键信息</p></li><li><p>数字资料分类调理</p></li><li><p>晨礼（30-60分固定流程，锻炼，早晨应该避免过度脑力劳动，决定而第二天起床时间</p></li><li><p>每周给自己放一天假缓冲（上6休1） 剩下一天无所事事</p></li><li><p>有限的是精力不是时间，尝试早起20分钟慢跑</p></li><li><p>吃的少油少肉，多吃蔬菜和主食。少食多餐</p></li><li><p>吃掉那只青蛙。每天重要的事放在晨礼之后，每周重要的事放在周一周二</p></li><li><p>能量周期循环（休息-回复） 工作看成一系列短跑。每周一天放松不做生产力，每天晚上固定时间不想工作的事。</p></li><li><p>开环闭环理论，任务有明显的结束信号。</p></li><li><p>尽可能不要多进程处理，会让人变笨。</p></li><li><p>停止之前再坚持15分钟。</p></li><li><p>多喝水。杯子接满水。</p></li><li><p>回顾目标来充电。</p></li><li><p>换掉不能提供新想法的信息源。进行脑力训练。</p></li><li><p>工作 - 休息的循环。而不是放任自流。这只会变得懒惰。</p></li><li><p>不同的活动不同思维，累了就换一个。体能低就平衡创造力</p></li><li><p>完成项目不是任务，因为任务是动态的。</p></li><li><p>任务和项目的最后期限</p></li><li><p>每周90分钟回顾。独立思考，隔离其他的干扰源。</p></li><li><p>硬时间之前自己加一个软时间。（硬时间会积压一起）</p></li><li><p>帕金森定律：只要有时间，工作就会不断拓展，直到把时间用满。</p></li><li><p>霍夫施塔定律：在一件事情上花费的时间总是比想象中多得多。</p></li><li><p>生产力 不等于 工作。 简化复杂的项目，不做没有价值的大任务。断舍离用头脑挽救时间精力。</p></li><li><p>框架规划。只规划出来绝对必要的要素就可以了。不要过度规划，灵活决策后面做。</p></li><li><p>不要重复造轮子。用很少的钱换取别人几百个小时的劳动。</p></li><li><p>快速MVP迭代（找出版社之前先写一本电子书）</p></li><li><p>别按照时间会的报酬。（2-3小时干完，剩下时间休息</p></li><li><p>被逼出来的生产力（加任务&#x2F;先休假）</p></li><li><p>每日检查标记</p></li><li><p>出口策略：完成工作之后做什么</p></li></ol>]]></content>
    
    
    <summary type="html">《超效率手册》读书摘抄，涵盖时间管理、任务分解、短跑冲刺等实用方法。</summary>
    
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/"/>
    
    
  </entry>
  
  <entry>
    <title>给Macbook Pro安装Fedora Asahi Remix</title>
    <link href="https://blog.no-claw.com/posts/5743bae5/"/>
    <id>https://blog.no-claw.com/posts/5743bae5/</id>
    <published>2026-02-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>16G的Macbook 经常出问题，应该是内存太小吧，所以安装一个Linux做双系统吧，Asahi搞定UEFI这层，本质上还是Fedora。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/655cadefb7a4b564f74c0f5e98948e51.png" alt="655cadefb7a4b564f74c0f5e98948e51"></p><p>起码目前阶段对我的M2 Macbook 兼容还不错，打算尝尝鲜。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260222132234359.png" alt="image-20260222132234359"></p><p>没找到图形化安装或者ISO，只找到这个命令来做安装。</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">curl https://alx.sh | sh</span><br></pre></td></tr></table></figure><p>纯CLI的安装方式其实没那么太友好，一开始显示还有200多G的空间但是</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260222132037311.png" alt="image-20260222132037311"></p><h5 id="查看本地快照"><a href="#查看本地快照" class="headerlink" title="查看本地快照"></a>查看本地快照</h5><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">tmutil listlocalsnapshots /</span><br></pre></td></tr></table></figure><h5 id="删除所有本地快照"><a href="#删除所有本地快照" class="headerlink" title="删除所有本地快照"></a>删除所有本地快照</h5><p><strong>方法 A（推荐）：</strong> 批量删除所有快照</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tmutil listlocalsnapshotdates | grep <span class="string">&quot;-&quot;</span> | xargs -n1 <span class="built_in">sudo</span> tmutil deletelocalsnapshots</span><br></pre></td></tr></table></figure><p><strong>方法 B：</strong> 逐个删除（如果方法 A 无效）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> tmutil deletelocalsnapshots 2024-01-15-123456</span><br></pre></td></tr></table></figure><h5 id="验证清理结果"><a href="#验证清理结果" class="headerlink" title="验证清理结果"></a>验证清理结果</h5><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">tmutil listlocalsnapshots /</span><br></pre></td></tr></table></figure><p>应该显示为空或 “No local snapshots on this date”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c4c30e27c6856d125a4d11124b0e4787.png" alt="c4c30e27c6856d125a4d11124b0e4787"></p><p>然后就是压缩MacOS磁盘给一个部分到这个Fedora，压缩磁盘的时候MacOS页面会卡死一阵子。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260222131857852.png" alt="image-20260222131857852"></p><p>然后就是选容量和系统类型。剩下就是漫长的等待。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260222134309125.png" alt="image-20260222134309125"></p><p>然后会把第一引导项改成Fedora。</p><ul><li>第一次安装：中间断网了，重装</li><li>第二次安装：无限重启重启（因为手贱按了两次开机键，实际长按看见引导就行）</li><li>第三次成功（要安装之后关机25秒后再开）</li></ul><p>剩下就是无脑跟着提示走，安全模式设置，然后就是初始化了。</p><p>嗯，Retina屏幕看啥都好看，在MacOS越做越烂的这些年，换个Linux玩玩吧。</p>]]></content>
    
    
    <summary type="html">16G MacBook Pro 内存不够用，安装 Fedora Asahi Remix 做双系统的完整过程。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>安卓ADB提取APK安装包</title>
    <link href="https://blog.no-claw.com/posts/17adb539/"/>
    <id>https://blog.no-claw.com/posts/17adb539/</id>
    <published>2026-02-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前的文章写了如何使用无线调试ADB，那么我们就可以用adb提取安装包了。</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">adb shell pm list packages</span><br></pre></td></tr></table></figure><p>这里会列出很多app的包名，如果你知道叫啥名字页可以grep过滤一下。</p><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">adb shell pm path &lt;包名&gt;</span><br></pre></td></tr></table></figure><p>然后使用adb pull这个地址，就可以在当前路径得到apk。</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">adb pull &lt;地址&gt;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">通过无线 ADB 连接安卓设备，使用命令行提取已安装应用的 APK 安装包。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="手机" scheme="https://blog.no-claw.com/tags/%E6%89%8B%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>记一次硬盘满了导致 Coco Server 无法启动的排查</title>
    <link href="https://blog.no-claw.com/posts/db853650/"/>
    <id>https://blog.no-claw.com/posts/db853650/</id>
    <published>2026-02-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>这次排查非常典型：容器日志看起来像“卡在某一行”，Easysearch 甚至已经启动，但 Coco Server 的进程并没有真正跑起来。</p><p>最后发现根因是：<strong>磁盘剩余空间不足（&lt; 5GB）触发 coco 自检阈值，直接 panic 退出</strong>，造成了“日志一直卡住、服务一直起不来”的假象，差不多排查了半个多小时。</p><h3 id="背景：目标与现象"><a href="#背景：目标与现象" class="headerlink" title="背景：目标与现象"></a>背景：目标与现象</h3><p>目标是在 RK3566 &#x2F; Armbian 上把 <code>cocoai-arm:test</code> 跑起来，并通过宿主机端口访问服务：</p><ul><li><p>宿主机映射：<code>-p 9000:9000</code></p></li><li><p>容器挂载 volume：</p><ul><li><code>coco_data_vol:/app/easysearch/data</code></li><li><code>coco_config_vol:/app/easysearch/config</code></li><li><code>coco_logs_vol:/app/easysearch/logs</code><span id="more"></span>一开始看到的现象是：</li></ul></li><li><p><code>docker logs -f</code> 输出大量初始化日志</p></li><li><p>Easysearch 选主、集群状态、模板 &#x2F; 索引迁移都能看到</p></li><li><p>但实际访问服务不通，或者看上去“卡在某一行不动”</p></li></ul><h3 id="Step-1：先处理内核参数（vm-max-map-count）"><a href="#Step-1：先处理内核参数（vm-max-map-count）" class="headerlink" title="Step 1：先处理内核参数（vm.max_map_count）"></a>Step 1：先处理内核参数（vm.max_map_count）</h3><p>Easysearch &#x2F; ES 系产品常见依赖 <code>vm.max_map_count</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">sysctl -w vm.max_map_count=262144</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;vm.max_map_count=262144&quot;</span> &gt;/etc/sysctl.d/99-easysearch.conf</span><br><span class="line">sysctl --system | grep vm.max_map_count</span><br></pre></td></tr></table></figure><p>确认 <code>vm.max_map_count = 262144</code> 已生效。</p><p>日志里还会看到一条：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sysctl: setting key &quot;net.ipv4.conf.all.promote_secondaries&quot;: Invalid argument</span><br></pre></td></tr></table></figure><p>这是系统 &#x2F; 内核不支持该 sysctl 键导致的“噪音”，不影响 <code>vm.max_map_count</code> 是否生效。只要 grep 结果正确，就可以继续下一步。</p><h3 id="Step-2：确认-entrypoint-进程结构（关键转折）"><a href="#Step-2：确认-entrypoint-进程结构（关键转折）" class="headerlink" title="Step 2：确认 entrypoint &#x2F; 进程结构（关键转折）"></a>Step 2：确认 entrypoint &#x2F; 进程结构（关键转折）</h3><p>这一步是整个排查的关键转折点：<br><strong>不要只盯日志，要看容器里到底跑了哪些进程。</strong></p><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">docker inspect --format <span class="string">&#x27;Entrypoint=&#123;&#123;json .Config.Entrypoint&#125;&#125; Cmd=&#123;&#123;json .Config.Cmd&#125;&#125;&#x27;</span> cocoai-arm:<span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>输出类似：</p><ul><li>Entrypoint：<code>[&quot;/sbin/tini&quot;,&quot;--&quot;,&quot;/sbin/entrypoint.sh&quot;]</code></li><li>Cmd：<code>[&quot;easysearch&quot;]</code></li></ul><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">docker <span class="built_in">exec</span> -it cocoserver sh -lc <span class="string">&#x27;ps -ef&#x27;</span></span><br></pre></td></tr></table></figure><p>可以看到非常关键的进程关系：</p><ul><li>PID 1：tini → entrypoint.sh easysearch</li><li>PID 7：java（Easysearch）</li><li>PID 147：python supervisord</li><li>PID 581：&#x2F;bin&#x2F;bash &#x2F;app&#x2F;easysearch&#x2F;data&#x2F;coco&#x2F;start-coco.sh</li></ul><p>这一步直接改变了排查方向：</p><blockquote><p>容器里不仅跑了 Easysearch，还跑了 supervisord，它负责拉起 coco。<br>所以“服务用不了”，问题很可能根本不在 Easysearch，而在 coco。</p></blockquote><hr><h3 id="Step-3：端口检查发现-Easysearch-只监听-127-0-0-1-9200"><a href="#Step-3：端口检查发现-Easysearch-只监听-127-0-0-1-9200" class="headerlink" title="Step 3：端口检查发现 Easysearch 只监听 127.0.0.1:9200"></a>Step 3：端口检查发现 Easysearch 只监听 127.0.0.1:9200</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">docker <span class="built_in">exec</span> -it cocoserver sh -lc \</span><br><span class="line"><span class="string">&quot;ss -lntp | grep -E &#x27;:9000|:9200&#x27; || netstat -lntp | grep -E &#x27;:9000|:9200&#x27; || true&quot;</span></span><br></pre></td></tr></table></figure><p>容器里没有 <code>ss</code>，回退到 <code>netstat</code>，看到类似：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tcp6  0 0 127.0.0.1:9200 :::* LISTEN -</span><br></pre></td></tr></table></figure><p>解读：</p><ul><li>Easysearch 在容器内监听 <code>127.0.0.1:9200</code>（loopback）</li><li>容器内部组件访问 <code>127.0.0.1:9200</code> 是没问题的</li><li>但如果你想从宿主机直接访问 9200，那一定不行（而且你也没映射 9200）</li><li>coco server 在9000 端口到底有没有起来？外部访问返回RST。</li></ul><h3 id="Step-4：抓到致命错误：磁盘空间不足导致-coco-panic-退出"><a href="#Step-4：抓到致命错误：磁盘空间不足导致-coco-panic-退出" class="headerlink" title="Step 4：抓到致命错误：磁盘空间不足导致 coco panic 退出"></a>Step 4：抓到致命错误：磁盘空间不足导致 coco panic 退出</h3><p>把视角切到 coco 的 supervisor 日志（<strong>这是最关键的一步</strong>）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it cocoserver sh -lc \</span><br><span class="line"><span class="string">&quot;tail -n 120 /app/easysearch/data/coco/supervisor.out.log; echo &#x27;----&#x27;; tail -n 120 /app/easysearch/data/coco/supervisor.err.log || true&quot;</span></span><br></pre></td></tr></table></figure><p>日志里可以看到两条定性信息：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">api server listen at: http://0.0.0.0:2900</span><br></pre></td></tr></table></figure><p>以及真正的致命错误：</p><figure class="highlight text"><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">[app.go:407] panic: disk free space [3.7G] &lt; threshold [5G]</span><br><span class="line">[app.go:429] coco now terminated.</span><br></pre></td></tr></table></figure><p>到这里就完全对上了：</p><p>1）coco 确实尝试启动<br>2）启动后立即做磁盘空间自检<br>3）可用空间 3.7G &lt; 5G 阈值<br>4）直接 panic 退出<br>5）日志不再输出，看起来像“卡住”</p><p>这次排查最后可以一句话总结为：</p><blockquote><p>Easysearch 启动正常、集群 Green；真正导致 Coco Server 不可用的原因是：<br>coco 因为磁盘可用空间不足（&lt; 5GB）触发保护阈值直接 panic 退出，造成“像卡住”的假象。</p></blockquote><p>删除了一堆Docker images 释放了磁盘空间，Coco Server就能顺利启动了～</p><h5 id="下次直接照抄的「快速定位清单」"><a href="#下次直接照抄的「快速定位清单」" class="headerlink" title="下次直接照抄的「快速定位清单」"></a>下次直接照抄的「快速定位清单」</h5><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"><span class="comment"># 1) 容器状态</span></span><br><span class="line">docker ps -a | grep coco</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2) 镜像入口：确认是否存在 supervisord / 多进程</span></span><br><span class="line">docker inspect --format <span class="string">&#x27;Entrypoint=&#123;&#123;json .Config.Entrypoint&#125;&#125; Cmd=&#123;&#123;json .Config.Cmd&#125;&#125;&#x27;</span> cocoai-arm:<span class="built_in">test</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3) 进程树：确认 coco 是否被 supervisor 拉起</span></span><br><span class="line">docker <span class="built_in">exec</span> -it cocoserver sh -lc <span class="string">&#x27;ps -ef&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4) 监听端口：没 ss 就用 netstat</span></span><br><span class="line">docker <span class="built_in">exec</span> -it cocoserver sh -lc <span class="string">&quot;netstat -lntp | grep -E &#x27;:9200|:2900|:9000&#x27; || true&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 5) coco 的失败原因：优先看 supervisor.out.log</span></span><br><span class="line">docker <span class="built_in">exec</span> -it cocoserver sh -lc <span class="string">&quot;tail -n 120 /app/easysearch/data/coco/supervisor.out.log&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 6) 如果看到 disk threshold，立即在宿主机查空间</span></span><br><span class="line"><span class="built_in">df</span> -h</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Coco Server 启动失败的排查过程，最终定位到磁盘空间不足导致服务无法正常运行。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>安卓也有terminal，使用无线ADB解锁X300的终端</title>
    <link href="https://blog.no-claw.com/posts/d5ad04d5/"/>
    <id>https://blog.no-claw.com/posts/d5ad04d5/</id>
    <published>2026-02-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>刷视频的时候看见有人说X300自带了一个终端，所以打算复现一下。</p><p>我的手机上安装甲壳虫ADB一直闪退，所以使用无线ADB，开启之后效果大概这样子。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220225346095.png" alt="image-20260220225346095"></p><h3 id="无线ADB"><a href="#无线ADB" class="headerlink" title="无线ADB"></a>无线ADB</h3><p>需要先打开开发者模式，然后在Mac上安装ADB。</p><figure class="highlight plaintext"><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">brew install android-platform-tools</span><br><span class="line">adb version</span><br></pre></td></tr></table></figure><p>X300的ADB用的不是常用的5555端口，所以需要在这里找到配对码和配对端口（port1）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220230044192.png" alt="image-20260220230044192"></p><p>大改就是先 adb pair 然后再 adb connect ，需要注意的是port1和port2是俩不同的端口。</p><p>Port1: 配对端口</p><p>Port2: 连接端口</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">(base) ➜  ~ adb pair ip:port1</span><br><span class="line">Enter pairing code: xxxxx</span><br><span class="line">Successfully paired to ip:port [guid=adb-10AG1G06C9005PH-m5M0Ww]</span><br><span class="line">(base) ➜  ~ adb connect ip:port2</span><br><span class="line">connected to 192.168.5.25:40779</span><br><span class="line">(base) ➜  ~ adb devices</span><br><span class="line">List of devices attached</span><br><span class="line">ip:port2 device</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>然后adb查看手机软件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">adb -s ip:port shell pm list packages | grep terminal</span><br><span class="line"></span><br><span class="line">package:com.android.virtualization.terminal</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="解锁终端"><a href="#解锁终端" class="headerlink" title="解锁终端"></a>解锁终端</h3><p>然后使用这个命令解锁终端，看到enabled之后就可以使用手机查看了。</p><figure class="highlight plaintext"><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">adb -s ip:port shell pm enable com.android.virtualization.terminal</span><br><span class="line">Package com.android.virtualization.terminal new state: enabled</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220225332746.png" alt="image-20260220225332746"></p><p>在手机里看操作系统信息，竟然是debian，不过看起来是纯内网使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/272987be9a4409949276b5c42bb0c5f8.jpg" alt="272987be9a4409949276b5c42bb0c5f8"></p><p>可以调整磁盘大小。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220225251412.png" alt="image-20260220225251412"></p><p>还可以调整端口。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220225242353.png" alt="image-20260220225242353"></p><p>从这里看好像是一个没有不联网的Linux虚拟机，不过崩溃的频率还是有点高，有条件还是自己弄VPS尝鲜吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260220230710644.png" alt="image-20260220230710644"></p>]]></content>
    
    
    <summary type="html">通过无线 ADB 解锁 X300 设备隐藏的终端功能，让安卓设备也能用命令行。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="手机" scheme="https://blog.no-claw.com/tags/%E6%89%8B%E6%9C%BA/"/>
    
    <category term="ADB" scheme="https://blog.no-claw.com/tags/ADB/"/>
    
  </entry>
  
  <entry>
    <title>RK3566嵌入式开发板运行Coco AI Sever</title>
    <link href="https://blog.no-claw.com/posts/ce955007/"/>
    <id>https://blog.no-claw.com/posts/ce955007/</id>
    <published>2026-02-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前在泰山派上运行了Easysearch，这次也想着是不是可以在泰山派开发板RK3566上运行Coco server，毕竟这板子功耗小，适合常开。</p><p>我的RK3566上是Armbian，但是没有配置网络环境，访问Dockerhub有问题，所以从Macbook 上下载玩，然后通过离线方式导成tar文件。我的镜像改了tag叫做cocoai-arm:test，你也可以不改。</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">docker save -o cocoai-arm_test.tar cocoai-arm:test</span><br></pre></td></tr></table></figure><p>然后通过SCP上传到RK3566的开发板</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">scp cocoai-arm_test.tar lckfb@192.168.5.36:~</span><br><span class="line">** WARNING: connection is not using a post-quantum key exchange algorithm.</span><br><span class="line">** This session may be vulnerable to &quot;store now, decrypt later&quot; attacks.</span><br><span class="line">** The server may need to be upgraded. See https://openssh.com/pq.html</span><br><span class="line">lckfb@192.168.5.36&#x27;s password:</span><br><span class="line">cocoai-arm_test.tar                            14%  125MB   5.6MB/s   02:14 ETA^cocoai-arm_test.tar                            16%  143MB   5.7MB/s   02:10 ETA^cocoai-arm_test.tar                            38%  337MB   5.7MB/s   01:35 ETA</span><br><span class="line">cocoai-arm_test.tar                            44%  391MB   5.5MB/s   01:29 ETA</span><br></pre></td></tr></table></figure><blockquote><p>补充：这个 warning 主要是 SSH 协商算法提示，不影响传输本身。真正要关注的是传输速度和是否中断——毕竟 900MB 级别的镜像，板子这边 IO 慢一点就容易“感觉很久”。</p></blockquote><p>然后ssh到开发板上，使用docker laod命令还原这个Docker镜像。</p><figure class="highlight plaintext"><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">root@lckfb:/home/lckfb# docker load -i cocoai-arm_test.tar</span><br><span class="line">45e40363867d: Loading layer  336.5MB/922.7MB</span><br></pre></td></tr></table></figure><p>这个过程会花费一些时间，所以有时候假死直接等待就好了。漫长的时间过去之后，我们可以通过Docker images来查看镜像，还有一个Easysearch 镜像，是我之前测试的，也能够在嵌入式开发板上运行的很好。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">root@lckfb:/home/lckfb# docker images</span><br><span class="line">REPOSITORY              TAG       IMAGE ID       CREATED        SIZE</span><br><span class="line">cocoai-arm              test      172b428f2dcf   45 hours ago   915MB</span><br><span class="line">infinilabs/easysearch   1.15.0    295014c1f959   5 months ago   697MB</span><br></pre></td></tr></table></figure><p><code>docker load</code> “假死”一般是写盘&#x2F;解包慢，我这个板子的 eMMC 速度就很一般。</p><blockquote><p>顺手可以记一下：板子上镜像多了以后，存储空间会很快见底，后续最好固定一个清理策略（比如只留当前版本）。</p></blockquote><p>然后使用这个命令启动，然后使用Docker PS查看。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name cocoserver -p 9000:9000 \</span><br><span class="line">           -v coco_data_vol:/app/easysearch/data \</span><br><span class="line">           -v coco_config_vol:/app/easysearch/config \</span><br><span class="line">           -v coco_logs_vol:/app/easysearch/logs \</span><br><span class="line">              cocoai-arm:test</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217221525932.png" alt="image-20260217221525932"></p><p><strong>图：容器已经跑起来，端口映射也生效。</strong><br><strong>我当时会顺手确认几个问题：</strong></p><ul><li><code>STATUS</code> 是不是一直 <code>Up</code>，有没有出现反复重启（<code>Restarting</code>）？</li><li><code>PORTS</code> 是否显示 <code>0.0.0.0:9000-&gt;9000/tcp</code>（如果只绑到 <code>127.0.0.1</code>，局域网其他设备就访问不到）</li><li>容器名 <code>cocoserver</code> 固定后，后续排查日志&#x2F;重启都更方便：<code>docker logs -f cocoserver</code><br>开发板的性能有限，所以初始化的时候需要多等一会。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217221617329.png" alt="image-20260217221617329"></p><p><strong>图：初始化日志在跑，说明服务在“慢慢起身”。</strong><br><strong>这张图我会重点盯两类信息：</strong></p><ul><li>有没有明显的报错关键词：<code>error / exception / failed / oom / killed</code></li><li>有没有持续输出（哪怕很慢）——只要日志还在动，一般就不是卡死<br><strong>补充一个小习惯：</strong> 如果你怀疑卡住了，可以开另一个窗口 <code>docker stats</code> 看 CPU&#x2F;内存是否还有波动；也可以 <code>docker logs --tail 200 cocoserver</code> 快速看最近输出。</li></ul><p>漫长的等待之后，CPU很清闲，不过内存快用满了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217225210333.png" alt="image-20260217225210333"></p><p><strong>补充：</strong> 这种“CPU 很闲、内存紧张”的状态，在小板跑 Java&#x2F;搜索组件&#x2F;AI 服务时很常见，能跑起来不奇怪，关键是别被 OOM 一刀带走。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217225343990.png" alt="image-20260217225343990"></p><p>到这里，Coco AI Server 算是成功在 RK3566 上跑起来了：容器状态正常、日志能持续输出、端口映射也能对外提供访问。</p>]]></content>
    
    
    <summary type="html">在 RK3566 嵌入式开发板上部署运行 Coco AI Server 的实践记录。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>被CUPS共享打印机驱动搞疯了，换windows10 做打印机 server吧</title>
    <link href="https://blog.no-claw.com/posts/7cc2eba9/"/>
    <id>https://blog.no-claw.com/posts/7cc2eba9/</id>
    <published>2026-02-17T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前黑群晖上部署的CUPS挂了，后来查了一下是数据盘坏了，还有一个原因是CUPS对联想打印机的兼容不是很好，索性就刷成windows10，有原生驱动，出问题还能RDP。</p><p>参考了这个方案：</p><p>兜兜转转回到了一个不折腾的方案：<strong>Win10 安装 Mobility Print Server → 发布本地打印机 → 手机走 AirPrint &#x2F; Mobility Print 直接打印</strong>。</p><p>其实就是：</p><p>让 Win10 这台电脑充当“打印服务器”，把 USB&#x2F;本地打印机发布到局域网里，手机就能像用无线打印机一样用它。</p><p>如果你家里有打印机可以试试，比CUPS的安装和运维成本小很多。</p>  <span id="more"></span><h3 id="windows-安装打印机驱动"><a href="#windows-安装打印机驱动" class="headerlink" title="windows 安装打印机驱动"></a>windows 安装打印机驱动</h3><p>先把打印机的 <strong>Windows 驱动和配套软件</strong>装好（重点是：确保 Win10 上“本地打印”完全正常）。</p><p>驱动下载页（示例）：<br><a href="https://www.lenovoimage.com/index.php/services/servers_drivers?cat_id=2&ProCode=48001877&OS=%E5%85%A8&baseclass=&key_words=">https://www.lenovoimage.com/index.php/services/servers_drivers?cat_id&#x3D;2&amp;ProCode&#x3D;48001877&amp;OS&#x3D;%E5%85%A8&amp;baseclass&#x3D;&amp;key_words&#x3D;</a></p><p>有种折腾半生，安装Windows养老的感觉。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217101806744.png" alt="image-20260217101806744"></p><p>可以从这里看到了我安装的软件列表。Windows自带的打印机共享好像是SMB，总之是不太好用，我直接给他关掉了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217101946540.png" alt="image-20260217101946540"></p><p>这个办法算是解决技术债，有条件还是建议买带无线打印的机器。</p><h3 id="条件确认"><a href="#条件确认" class="headerlink" title="条件确认"></a>条件确认</h3><ul><li>打印机在 Win10 上 <strong>能正常本地打印</strong></li><li>Win10 这台电脑后续要当“打印服务器”<ul><li><strong>不要睡眠&#x2F;休眠</strong>（至少在你要打印的时间段）</li><li>手机和这台电脑要在<strong>同一个 Wi-Fi&#x2F;同一网段</strong>（不要访客网络）</li></ul></li></ul><p>这里最容易翻车的是两件事：</p><p>1）Win10 睡死<br>很多人以为“屏幕熄灭”没关系，但如果机器进入睡眠&#x2F;休眠，手机就会直接找不到打印机。</p><p>2）访客 Wi-Fi &#x2F; 网络隔离<br>有些路由器默认把“访客网络”做了隔离，手机在访客 Wi-Fi 下是看不到你内网 Win10 电脑的——打印服务自然也发现不了。</p><p>让GPT写了一个命令，目测还挺稳的：<strong>让这台 Win10 在你需要打印时别睡过去</strong>（屏幕可以灭，但系统别休眠&#x2F;睡眠）。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">powercfg -h off</span><br><span class="line">powercfg /x standby-timeout-ac 0</span><br><span class="line">powercfg /x standby-timeout-dc 0</span><br><span class="line">powercfg /x hibernate-timeout-ac 0</span><br><span class="line">powercfg /x hibernate-timeout-dc 0</span><br><span class="line">powercfg /x monitor-timeout-ac 10</span><br><span class="line">powercfg /x monitor-timeout-dc 10</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/44b63ab9261745c8b94c7462a616eeb5.png" alt="44b63ab9261745c8b94c7462a616eeb5"></p><p>稍微解释一下这几行大概在干嘛（不用深究，但有助于排错）：</p><ul><li><code>powercfg -h off</code>：关掉休眠（顺带会禁用“快速启动”相关的一些行为）</li><li><code>standby-timeout-* 0</code>：不进入睡眠</li><li><code>hibernate-timeout-* 0</code>：不进入休眠</li><li><code>monitor-timeout-* 10</code>：屏幕 10 分钟后熄灭（省电，但不影响打印服务）</li></ul><h3 id="Win10-安装-Mobility-Print-Server-并发布打印机"><a href="#Win10-安装-Mobility-Print-Server-并发布打印机" class="headerlink" title="Win10 安装 Mobility Print Server 并发布打印机"></a>Win10 安装 Mobility Print Server 并发布打印机</h3><ol><li><p>在 Win10 上安装 <strong>Mobility Print Server</strong>（安装过程基本一路下一步即可），不需要额外安装组件</p></li><li><p>打开 Mobility Print Server 管理界面，在打印机列表里 <strong>勾选“发布&#x2F;共享”</strong> 你的那台打印机，中间简单配置下用户名米啊么</p><ul><li>例如：<code>Lenovo M7400 Pro</code>（或者你自定义的队列名）</li></ul></li><li><p>给这台打印机设置一个<strong>共享显示名称</strong></p></li></ol><p>发布后，你会在界面里看到打印机处于可用状态，并且共享名称生效。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217101853752.png" alt="image-20260217101853752"></p><h3 id="iPhone-打印"><a href="#iPhone-打印" class="headerlink" title="iPhone 打印"></a>iPhone 打印</h3><p>iPhone 这边基本是“傻瓜式”：<br>只要手机和 Win10 在同一网络里，你在任意支持打印的 App（备忘录、照片、文件）里点“打印”，就能看到你发布出来的打印机名称，选中即可打印。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217103054171.png" alt="image-20260217103054171"></p><p>也就是说：<strong>iPhone 端通常不需要额外装 App</strong>，体验非常接近原生 AirPrint。</p><h3 id="安卓打印"><a href="#安卓打印" class="headerlink" title="安卓打印"></a>安卓打印</h3><p>安卓这边装一个 <strong>Mobility Print</strong>（App）就行。装好后同样确保在同一 Wi-Fi&#x2F;同网段，然后在打印选择里找到你刚才设置的共享显示名称，直接打印。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260217105135583.png" alt="image-20260217105135583"></p><p>如果你家里安卓机比较多，这个方案的好处是：<strong>一次配置，全家通用</strong>，后续基本就是“选打印机 → 打印”。</p><h3 id="实际效果"><a href="#实际效果" class="headerlink" title="实际效果"></a>实际效果</h3><p>把 Win10 当打印服务器之后，手机端看到的体验就是：打印机像一台“真正的无线打印机”一样出现。</p><p>下面两张图就是我这边的实际效果（手机端能直接发现、直接选择、直接打印）：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3f1404043041bf6725fab5cee1d89f30.jpg"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3f1404043041bf6725fab5cee1d89f30-20260217110154905.jpg"></p><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>用熟悉的Windows做Server，出问题还能RDP，有window原厂驱动还比CUPS少折腾了很多。也不需要买任何“无线打印盒子”，只要家里有一台 Win10 电脑能长期在线即可。</p><p>关键点就两个：</p><ul><li>Win10 必须能稳定在线（别睡死）</li><li>手机和 Win10 必须在同一网络（访客 Wi-Fi 很容易把设备隔离掉）</li></ul><p>当然了，这个windows还能当远程小主机用，安装微信，Office远程办公也行啊。</p><p><a href="https://sspai.com/post/63776">https://sspai.com/post/63776</a></p><ol><li>Bonjour Print Services (Windows)</li><li>AirPrint Installer</li><li>修改防火墙</li></ol><p>其中AirPrint Installer对我没有起到什么明显的作用，只是可以在移动端搜到打印机了，但是每次都打印失败。<br>Bonjour 还留着了，也不确定是不是真的有用，现在能用就不折腾了。</p>]]></content>
    
    
    <summary type="html">CUPS 对联想打印机兼容性差，改用 Windows 10 做打印服务器，原生驱动更稳定还能 RDP 远程管理。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="打印机" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E6%89%93%E5%8D%B0%E6%9C%BA/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>Docker启动Coco AI Server后，如何访问内置Easysearch</title>
    <link href="https://blog.no-claw.com/posts/b7b40656/"/>
    <id>https://blog.no-claw.com/posts/b7b40656/</id>
    <published>2026-02-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用 Docker 启动 Coco AI 的时候会自带一个 Easysearch，我们使用连接器连接外部数据源的时候，就会把这个数据解析到 Easysearch 里。</p><p>但是默认的话，容器不会把这个 Easysearch 的端口映射出来，那就需要我们自己做些小的技巧：在官网的命令上修改一下，把 <code>9200</code> 端口先映射出来。</p><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">docker run -d --name cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  -v coco_data_vol:/app/easysearch/data \</span><br><span class="line">  -v coco_config_vol:/app/easysearch/config \</span><br><span class="line">  -v coco_logs_vol:/app/easysearch/logs \</span><br><span class="line">  infinilabs/coco:0.10.0-2678</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260216121827526.png" alt="image-20260216121827526"></p><span id="more"></span><p>启动之后，可以使用 netstat 看到容器端口的情况。换句话说，这个自带的 Easysearch 把 <code>9200</code> 和 <code>9300</code> 的端口确实启动起来了，但默认只绑定在 <code>127.0.0.1</code> 上，所以外部访问不到——即使你已经加了 <code>-p 9200</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">docker <span class="built_in">exec</span> -it cocoserver sh -lc <span class="string">&quot;netstat -lnt | egrep &#x27;:9000|:9200|:9300&#x27; || true&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tcp6       0      0 127.0.0.1:9200          :::*                    LISTEN</span><br><span class="line">tcp6       0      0 127.0.0.1:9300          :::*                    LISTEN</span><br><span class="line">tcp6       0      0 :::9000                 :::*                    LISTEN</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260216122129558.png" alt="image-20260216122129558"></p><p>因为这部分是 <code>easysearch.yml</code> 控制的，所以我们可以直接通过命令更改，就使用 sed 替换吧。使用之前最好确认 Coco Server 进程已经起来了，防止不必要的问题。</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">docker <span class="built_in">exec</span> -it cocoserver bash -lc <span class="string">&#x27;</span></span><br><span class="line"><span class="string">CFG=/app/easysearch/config/easysearch.yml</span></span><br><span class="line"><span class="string">cp -a &quot;$CFG&quot; &quot;$CFG.bak.$(date +%Y%m%d%H%M%S)&quot;</span></span><br><span class="line"><span class="string">sed -i &quot;s/^network\.host:\s*127\.0\.0\.1/network.host: 0.0.0.0/&quot; &quot;$CFG&quot;</span></span><br><span class="line"><span class="string">grep -n &quot;^network\.host:&quot; &quot;$CFG&quot;</span></span><br><span class="line"><span class="string">&#x27;</span></span><br></pre></td></tr></table></figure><p>然后重启服务，就能通过 <code>https://IP:9200</code> 的方式访问 cocoserver 自带的 Easysearch 了。</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">docker restart cocoserver</span><br></pre></td></tr></table></figure><p>这个时候我们再看端口的占用情况，已经开了外网访问（<code>9200/9300</code> 不再是 <code>127.0.0.1</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">(base) lzcbox-029c588e ~ <span class="comment"># docker exec -it cocoserver sh -lc &quot;netstat -lnt | egrep &#x27;:9000|:9200|:9300&#x27; || true&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tcp6       0      0 :::9300                 :::*                    LISTEN</span><br><span class="line">tcp6       0      0 :::9200                 :::*                    LISTEN</span><br><span class="line">tcp6       0      0 :::9000                 :::*                    LISTEN</span><br></pre></td></tr></table></figure><p>Easysearch 的进程会先起来，Coco Sever 会慢一些。这时候可以通过 <code>http://ip:9000</code> 访问 Coco Server，也可以通过 <code>https://IP:9200</code> 访问 Easysearch 和 Easysearch 的 UI。密码还是老样子去 log 里找。</p><p>可以直接从 log 里把 curl 示例过滤出来，一般会直接告诉你默认账号密码：</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">pg-docker logs cocoserver 2&gt;&amp;1 | egrep -i <span class="string">&quot;curl&quot;</span> | <span class="built_in">tail</span> -n 50</span><br></pre></td></tr></table></figure><p>输出里会有类似这一行（账号通常是 admin，密码就是那串 <code>Coco-Server-xxxx==</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">curl -ku <span class="string">&#x27;admin:Coco-Server-6dsxtGXhD+MUNhPBKf1hug==&#x27;</span> https://localhost:9200</span><br></pre></td></tr></table></figure><p>如果你是从宿主机访问，把 <code>localhost</code> 换成你的 <code>IP</code> 或 <code>127.0.0.1</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">curl -k -u <span class="string">&#x27;admin:Coco-Server-6dsxtGXhD+MUNhPBKf1hug==&#x27;</span> https://127.0.0.1:9200</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260216123333103.png" alt="image-20260216123333103"></p><p>然后我们就收获了一台可以在 Coco server 里使用的 Easysearch，可以实时查看数据。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/09c6e7525db4e26cee9f961d619c3764.png" alt="09c6e7525db4e26cee9f961d619c3764"></p><p>平时用 API 写进去的数据还能被 Coco Server 索引，自带 UI 真的太爽了！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8e435b3209ab7dd2110e6f55c4f0d79a.png" alt="8e435b3209ab7dd2110e6f55c4f0d79a"></p><p>到这里就搞定了：Coco Server 自带的 Easysearch 不仅能正常跑起来，还能把 <code>9200</code> 端口暴露出来给外部访问。</p><p>日常用法也很简单：</p><ul><li>连接器同步进来的数据会实时写进这台 Easysearch 里；</li><li>自己用 API 写进去的数据也会被 Coco Server 索引；</li><li>遇到“数据到底进没进、字段长啥样、索引有没有建对”这种问题，直接打开 Easysearch UI 看一眼就能确认，再也不用抓瞎！</li></ul>]]></content>
    
    
    <summary type="html">Docker 部署 Coco AI Server 后访问内置 Easysearch 的方法和配置说明。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十五）：不要再买打印机小白盒了，用懒猫微服把有线打印机改成无线，还支持airprint</title>
    <link href="https://blog.no-claw.com/posts/846eeff9/"/>
    <id>https://blog.no-claw.com/posts/846eeff9/</id>
    <published>2026-02-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>家里的打印机有十多年了，最近使用频率不是很高，所以把打印机放在柜子里了，需要用的时候再拿出来接线——每次都像在搬砖：找线、找口、开机，试打一张、再把它塞回去。</p><p>那时候还不流行网络打印，但是现在看来确实是硬伤了。那我就计划把它改成“随时可用”。不求快，不求高级，只求你在任何设备上点一下就能打，别让我再拿着电脑跟着打印机跑。</p><p>大概前前后后摸索了这些方案：</p><ol><li>用小白盒连接路由器：其实这个思路跟网络打印机很类似，就是打印机 over IP，企业里几乎也都是这样的做法。缺点就是需要客户端安装驱动，所以相比之下就牺牲了移动端。</li><li>windows&#x2F;MacOS共享：由于缺少 airprint，所以Apple 设备无法使用隔空打印。其实 Windows 的兼容性是最好的。</li><li>在OpenWrt上安装CUPS驱动，然后打印机接路由器当做无线使用。Apple生态会舒服很多。</li></ol><p>我在第三个方法上进行了改良：不在OpenWrt上折腾了，换成用懒猫微服充当CUPS驱动传递的Server。</p><p>我这套里懒猫微服就干三件事：跑CUPS（带AirPrint）、挂网盘做中转、把Windows远程入口映射出去。</p><span id="more"></span><h3 id="应用商店CUPS"><a href="#应用商店CUPS" class="headerlink" title="应用商店CUPS"></a>应用商店CUPS</h3><p>懒猫应用商店里直接可以下载CUPS服务端，内置了airprint协议，省去了自己折腾docker镜像的麻烦——别人测试好的我来开箱即用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215214245528.png" alt="image-20260215214245528"></p><p>我们需要做的就是把USB直通给这个从商店下载的CUPS Docker。</p><p>这里有个很真实的小问题：每次打印机关机&#x2F;重启、或者USB重新插拔以后，容器可能会读不到这个USB外设，所以每次都要重启一下容器来重新识别USB。</p><p>这个镜像内置了很多打印机驱动，所以我们选和打印机相同或者相近的型号的驱动。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215223042216.png" alt="image-20260215223042216"></p><p>我这里识别到了联想的打印机，可以直接添加：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20260215215326699.png" alt="image-20250224164153529"></p><p>填写信息，选择共享这个打印机。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20260215215341647.png" alt="image-20250224164118890"></p><p>列表里没有打印机的驱动，所以我选了兄弟的。（尽管测试下来兼容性有些问题，总之还能全平台凑合用）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20260215215351128.png" alt="image-20250224164059690"></p><p>打印机信息一览：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20260215215400587.png" alt="image-20250224164032734"></p><p>使用心得：</p><p>全平台的单页打印几乎都没什么问题，iPhone&#x2F;iPad&#x2F;Mac&#x2F;Windows都能打，体验上就是“家里终于有一台网络打印机了”。</p><p>不过我的联想M7400 Pro在使用过程中遇到了两个很奇怪的问题，这是联想自己闭门搞驱动导致我使用的开源驱动无法适配导致的。</p><ul><li>大图片偶尔无法打印（队列里看着像发过去了，实际就是不动）</li><li>双页打印变成四页打印</li></ul><p>然后这一套方式也用了蛮久的。</p><p>如果你的打印机型号恰好在驱动列表里，那这套方案会非常舒服；你要做的就是商店装CUPS → 接打印机 → 选型号 → 结束。</p><p>如果型号不在列表里，那就像我这样：找个相近的驱动凑合用，能用到什么程度全凭缘分（以及厂商良心）。</p><h3 id="转发RDP，网盘传文件打印"><a href="#转发RDP，网盘传文件打印" class="headerlink" title="转发RDP，网盘传文件打印"></a>转发RDP，网盘传文件打印</h3><p>我是一个爱折腾的人，前面的办法只适用于局域网打印，那么广域网怎么办呢？</p><p>再纠结了好久之后，我还是弄了一台Windows，一方面是给家里人使用，一旦出问题调试难度小一些，也顺便使用了联想的官方驱动，防止他再搞幺蛾子。</p><p>首先Windows通过客户端自动挂载SMB，这样就可以直接读到懒猫网盘的文件。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215214203202.png" alt="image-20260215214203202"></p><p>我对SMB挂载顺便改了名字。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215221511572.png" alt="image-20260215221511572"></p><p>大概打印流程是这样的：</p><p>收到文件 -&gt; 传到懒猫网盘 -&gt; 同步windows -&gt; RDP登录WINDOW -&gt; 直接打印</p><p>打开SMB之后，我们就可以看到从其他终端收到的文件。微信收到的文件也可以直接分享到懒猫网盘，这一步比“RDP里传文件”舒服太多了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215211307101.png" alt="image-20260215211307101"></p><p>懒猫微服在这期间做了两件事：</p><ol><li>因为直接通过RDP传输文件很慢，而且移动端传起来也很麻烦。所以使用懒猫网盘来做文件的中转，私人云盘用起来就很舒服。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215223559760.png" alt="image-20260215223559760"></p><ol start="2"><li>懒猫微服可以把windows的端口映射到公网，这样子在外边的时候也可以远程登录直接打印了，你的客户端只要有一个Window APP可以远程登录就好。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215223423183.png" alt="image-20260215223423183"></p><ol start="3"><li>如果使用的是懒猫微服的商店的虚拟机windows，那么甚至可以使用Web VNC登录，只要想办法把打印机USB映射进去就好。</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/02/15/1771166114867-a870195d-972e-451c-a7ee-2d74ee6eba8b.png"></p>]]></content>
    
    
    <summary type="html">用懒猫微服替代打印机小白盒，将有线打印机改造成无线打印机并支持 AirPrint。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="打印机" scheme="https://blog.no-claw.com/tags/%E6%89%93%E5%8D%B0%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>Macbook Pro 虚拟机安装ARM飞牛</title>
    <link href="https://blog.no-claw.com/posts/567b12ef/"/>
    <id>https://blog.no-claw.com/posts/567b12ef/</id>
    <published>2026-02-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>飞牛公测有一阵子了，一直没腾出时间折腾这些。手里的ARM设备有泰山派开发板和Macbook Pro。由于泰山派实在没啥资料，所以还是先用Macbook + PD 安装飞牛吧，官网也提供了安装包下载。</p><p>我本来想给泰山派也刷一个：<a href="https://www.fnnas.com/download-arm">https://www.fnnas.com/download-arm</a> 不过我看这里没有镜像了，估计是和OEC的关系影响到了RK3566的适配。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215105650976.png" alt="image-20260215105650976"><br> <span id="more"></span><br>不用UTM的原因是大家都觉得很难用，所以还是选择了Parallel Desktop，安装很丝滑，几分钟就完成了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112004395.png" alt="image-20260215112004395"></p><p>自动识别到了飞牛的ISO，因为FN用的debian内核，就当debian用吧。（本质就是一套 Linux 安装流程，PD 对 Debian 这套适配也成熟，少踩坑。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112025557.png" alt="image-20260215112025557 "></p><p>然后进入图形化安装流程，其实和安装debian一样的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112211407.png" alt="image-20260215112211407"></p><p>选择安装磁盘，这里是系统盘。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112144639.png" alt="image-20260215112144639"></p><p>然后无脑安装就行了，等进度条。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112100974.png" alt="image-20260215112100974"></p><p>安装进度条结束之后就看到了FNOS的标志，没有图形GUI。会显示IP地址。（这一步其实已经装完系统了，后续基本都在 Web 后台做初始化。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112159477.png" alt="image-20260215112159477"></p><p>然后从IP地址进入web后台，设置管理员用户名密码。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112233024.png" alt="image-20260215112233024"></p><p>进入系统之后我们可以看到系统盘的容量，现在没有数据盘，所以没办法安装软件。（飞牛的应用基本都要落到存储空间里：没有数据盘&#x3D;没建存储池&#x3D;应用中心很多东西会直接灰掉。）</p><p>而且这个IP地址是虚拟机DHCP分的，我们需要改成和局域网一个网段。（不然每次重启 DHCP 变一下 IP，就得重新找；另外后面想从别的设备访问，也希望它像“局域网里的一台 NAS”，而不是“宿主机后面的一台小黑盒”。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112250789.png" alt="image-20260215112250789"></p><p>先停止虚拟机，我们做配置变更，加数据盘和改WIFI配置。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112527114.png" alt="image-20260215112527114"></p><p>停机之后，加硬盘2。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112541088.png" alt="image-20260215112541088"></p><p>网络配置也改成和宿主机共享无线网卡。（PD 这里如果走默认的 NAT，虚拟机通常在一个私有网段里，外面设备访问会绕一圈甚至直接不通；共享无线网卡等价于让虚拟机“挂”到当前 WiFi 这张网里，局域网可达性更好。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112345977.png" alt="image-20260215112345977"></p><p>修改完配置重启之后，我们会看到新的IP地址，然后进入后台之后，就可以看到新的数据盘了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112601105.png" alt="image-20260215112601105"></p><p>创建存储空间，我这里选Btrfs。（主要图它快照&#x2F;校验这些特性，NAS 场景挺合适。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112701506.png" alt="image-20260215112701506"></p><p>就一块盘，存储模式也无所谓了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112649988.png" alt="image-20260215112649988"></p><p>接下来就是格式化。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112719577.png" alt="image-20260215112719577"></p><p>等待存储池创建完成。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215112735667.png" alt="image-20260215112735667"></p><p>然后我们就可以从应用中心安装软件了。（有存储池之后，应用的安装路径、数据目录才有地方落盘。）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215115336822.png" alt="image-20260215115336822"></p><p>配置一览。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20260215120559904.png" alt="image-20260215120559904"></p><p>这套在 Mac 上用 PD 跑 FNOS 的方式，优点就是“快”和“稳”：几分钟起一台 NAS 环境，装应用、建存储池、跑基础功能都够用。等啥时候有时间，再慢慢折腾泰山派安装飞牛吧。</p>]]></content>
    
    
    <summary type="html">在 MacBook Pro 上通过虚拟机安装 ARM 版飞牛 NAS 系统的教程。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>在Macbook Pro上突破原生限制，实现菊花链三屏显示（曲线救国版）</title>
    <link href="https://blog.no-claw.com/posts/cb0bcf1b/"/>
    <id>https://blog.no-claw.com/posts/cb0bcf1b/</id>
    <published>2026-02-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>M芯片的Macbook Pro 原生只支持外接两个4K显示器，接第三个显示器就黑屏，意外在网上看到了display link的方案。于是买回来突破了原生的MacOS的限制。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2026/02/09/1770635368544-93b0e056-1749-4693-9b45-8c88258c7930.png"></p><span id="more"></span><p>我买的：display link 是这个配置：</p><ul><li>输入可以切换Typec + USB</li><li>输出是俩HDMI</li><li>还有几个USB2.0: 没啥用 hhhh，也就接个耳机和键鼠</li></ul><p>需要下载驱动Displaylink manager，然后系统里就能看到你在共享屏幕，<br>不管你在哪家买的硬件，应该用的都是这个软件。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/02/09/1770634898066-7197f9eb-170a-4478-8ac2-7e4ea889b7c0.png"></p><p>Macbook Pro有三个Typec口，打算预留一个TypeC给其他设备，所以一开始用2个TypeC + 一个HDMI，而Display link也是实现的Typec转HDMI，所以整体的线就很乱。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Macbook</span><br><span class="line">1. -&gt; TypeC - 显示器1</span><br><span class="line">2. -&gt; HDMI - 显示器2</span><br><span class="line">3. -&gt; TypeC(Display link) - HDMI - 显示器3</span><br></pre></td></tr></table></figure><p>如果用Typec普通拓展坞接出来的HDMI是4K@30， 直接TypeC就是 4k@60。</p><p>突发奇想如果直接把USB接到显示器2上呢，好消息是能亮，还能4k@60，这不就是变相的菊花链～ MacOS不支持，但是Display link可以啊！！</p><p>虽然性能不如PCLE直连，不过CPU额外的算力对M芯片也不算啥，线都扔在后面了，桌面也比以前干净了。</p>]]></content>
    
    
    <summary type="html">M 芯片 MacBook Pro 原生只支持双外接显示器，第三块屏接上就黑屏。通过 DisplayLink 扩展坞 + USB 直连显示器，曲线实现菊花链三屏 4K@60Hz 输出，桌面走线也更清爽。</summary>
    
    
    
    <category term="MacOS" scheme="https://blog.no-claw.com/categories/MacOS/"/>
    
    <category term="Apple" scheme="https://blog.no-claw.com/categories/MacOS/Apple/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>国民NAS 飞牛零日漏洞之后：我们需要什么样的 NAS ？</title>
    <link href="https://blog.no-claw.com/posts/75d5cfe5/"/>
    <id>https://blog.no-claw.com/posts/75d5cfe5/</id>
    <published>2026-02-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>前些日子被飞牛刷屏了，因为他们的零日漏洞。对于这种事我并不意外，这种“某个厂商又被爆出高危漏洞”的新闻，在过去几年里已经见过太多次了。威联通被勒索软件批量打过，群晖也出过远程代码执行的高危洞，甚至于阿里云盘也有照片“串号”的问题。只是平时刷到这类消息的时候，大多数人都会下意识地划过去，觉得这更多是厂商的锅，恰好我们没有用这个产品，或者没有被攻击过。直到这次轮到飞牛，论坛里有人说“已经被扫到”“后台被进过”“文件目录被翻过”，甚至资料被公开售卖，我甚至想说，我只想安安静静地存个东西就那么难吗。</p><p>但是感慨的原因，并不在于某一个厂商被爆出漏洞，而在于我们一直默认家用 NAS 是一类“相对安全的设备”，但现实并不完全是这样。当我们做内网穿透、端口转发的时候，运营商会禁止我们对外提供服务，这么看来倒是一种保护了。在漫长的斗智斗勇过程中，我们也学到了不少专业知识，但如果非要用数据泄露来交学费，那实在是太过惨痛了。</p><p>先说清楚，这篇文章无意针对飞牛以及其他厂家，我也是飞牛，群晖和威联通的忠实用户。他们能在市场上有这么多用户量，靠的是真本事，这一点谁都没法否认。<br>但这次漏洞事件暴露出来的东西，不是换个品牌、打个补丁就能翻篇的。它让我开始琢磨一个更根本的问题：我们玩了这么多年的家用 NAS，这套远程访问的路子，是不是从架构上就有一道天花板在那摆着？</p><span id="more"></span><p>如果把情绪抽离掉，从工程视角来看，飞牛这次零日漏洞暴露出来的问题其实很清晰。攻击路径并不是“有人猜中了你的账号密码”，也不是“你没开双因素认证”。根据已公开的信息，这次事件中至少涉及一个 CVSS 评分 9.8 的严重目录遍历漏洞（CVE-2025-0510），攻击者无需任何认证即可从OS中获取敏感信息，执行任意脚本，完成从“数据窃取”到“完全控制”的整条攻击链。</p><p>从飞牛论坛用户的清理反馈来看，攻击者的持久化手段相当专业：修改系统启动脚本（system_startup.sh）植入恶意代码，加载恶意内核模块隐藏进程，给恶意文件设置 immutable 属性防止删除，甚至篡改 DNS 设置将 OTA 升级域名指向无效地址，让设备无法自动更新补丁。<br>有用户反馈恶意程序在凌晨三点定时激活，每隔一小时执行一次，反复清理三天才最终平息。飞牛官方虽然紧急发布了 fnOS 1.1.15 和 1.1.18 安全补丁，但对于那些已经被植入内核级后门的设备来说，补丁来得再快也已经晚了——因为攻击者早就把升级通道堵死了。<br>这不是飞牛独有的问题。SQL 注入是 OWASP Top 10 里最经典的漏洞类型之一，它出现在一个已经上市的 NAS 产品中，恰恰说明了一个残酷的现实：NAS 厂商的安全开发水平参差不齐，而用户对此几乎没有任何鉴别能力。放在任何一家做 NAS 的厂商身上，本质都是同一个模型下的必然风险。</p><p>我跟很多人的第一反应一样：后台开了 MFA，SSH 加了密钥认证，密码也够复杂，应该没事吧？</p><p>但这次事件让我不得不面对一个很残酷的事实：这些防护手段，防的是“有人试图登录你的账号”，而不是“有人绕过登录直接打穿你的服务”。零日漏洞的可怕之处恰恰在于，它往往发生在认证流程之外。攻击者不需要知道你的密码，不需要通过你的 MFA，他直接请求一个有漏洞的接口，就能拿到系统权限。你精心配置的那些安全措施，在这条攻击路径上根本没有出场的机会。</p><p>就像你给大门装了三道锁，但小偷是从窗户翻进来的。</p><p>这件事真正让我警醒的，是它逼我重新审视“家用 NAS 到底是什么”这个问题。我们习惯把 NAS 当成一个“带点智能的网盘”，买回来插上硬盘，装几个套件，配好远程访问，就觉得万事大吉了。</p><p>但从技术结构上看，它更像是一台长期在线的小服务器：有 Web 管理后台，有文件服务接口，有各种插件和后台服务在跑，有远程访问需求，有公网可达入口。换句话说，它具备了服务器的全部攻击面，却经常被用户用消费电子的心态来对待。我们会很自然地为了方便打开端口映射、配置 DDNS，让设备可以随时在外网访问，却很少有人真正意识到：这相当于把一台服务复杂、补丁节奏完全依赖厂商的服务器，直接放在了公网边缘，24 小时不间断地接受全球扫描器的“体检”。</p><p>飞牛的零日漏洞，其实只是把这个结构性问题放大到了所有人眼前。</p><p>我后来花了不少时间去想一个问题：为什么 NAS 的安全事故总是以这种方式发生？不是用户密码太弱，不是配置太离谱，而是厂商的某个服务组件出了洞，然后大量暴露在公网的设备被批量扫描、批量利用。</p><p>想来想去，答案其实很简单——因为传统 NAS 的远程访问模型，从根子上就是“让公网能直接打到你家设备”。无论你是通过端口映射、DDNS，还是厂商提供的云中转服务，最终都意味着你的管理面或服务面在公网有一个可被发现的入口。这个模型在便利性上没问题，但安全性完全依赖两个前提：厂商永远不出漏洞，以及用户永远配置正确。而这两个前提，在现实中从来没有同时成立过。</p><p>做过安全的人都知道一个很朴素的原则：缩小攻击面，永远优先于修补漏洞。与其指望“厂商永不出洞”，不如从架构上减少公网暴露。如果攻击者连你的设备都扫不到，那么即使系统里存在未知漏洞，被利用的概率也会断崖式下降。这不是什么高深的安全理论，这是安全工程的第一课。但遗憾的是，绝大多数消费级 NAS 的产品设计，并没有把这个原则放在优先位置。它们更关心的是“用户能不能方便地远程访问”，而不是“这种访问方式是否在架构上足够安全”。</p><p>也正因为这样，我后来开始反思一个更根本的问题：是不是我们一开始就选错了“家用私有云”的技术路线？传统 NAS 的远程访问模型，本质上是假设“公网直连 + 用户自行加固”，这在早期小规模使用时尚且可控，但随着 NAS 功能越来越复杂、用户群体越来越非技术化，这种模型注定会不断放大安全风险。它把服务器级别的安全责任，转嫁给了普通家庭用户，而这本身就是一个不太合理的设计前提。你不能一边把产品卖给“想要一个家庭网盘”的普通人，一边要求他们具备运维一台公网服务器的安全能力。</p><p>想清楚这一点之后，我重新选方案的第一标准就变了：不是功能多不多，不是品牌响不响，而是——公网能不能扫到我。在这个标准下，任何能做到“默认不暴露公网”的方案，都比传统 NAS 架构更符合我的安全预期。</p><p>后来接触到懒猫微服的时候，说实话我一开始是带着怀疑态度的。市面上打着“私有云”旗号的产品太多了，很多不过是换了个壳的 NAS。但真正让我停下来认真看的，是它的整体架构设计思路。</p><p>根据懒猫微服官方开发者文档的描述，懒猫微服的系统分为三层架构：最底层是一个极度精简的底层系统，只负责网络连接、安全认证，以及业务操作系统的启动和更新，目标是保证不管怎么升级，系统永远不会挂；中间层是业务操作系统，负责网络隔离、应用调度、资源管理；最上层是 LPK 应用层，这是懒猫自己的容器格式，官方强调这种容器格式在权限隔离与运行时控制上的安全边界更可控，并计划逐步补充网络流量审计、网络限制和用户权限控制等能力，用来尽量降低应用层对系统整体安全的影响。这种分层设计的好处很直观：底层保证稳定性，中间层保证隔离性，应用层尽量把风险限制在可控范围内，不会因为某一层出问题就牵连整个系统。</p><p>但真正让我觉得这个产品在安全思路上跟传统 NAS 拉开差距的，是它的网络传输机制。懒猫微服的远程访问分为两种模式：当终端设备所在网络具备 IPv6 或 NAT3 条件时，系统会自动与用户设备建立直连传输；当网络环境较差时，系统会自动切换到中继数据传输服务。关键在于，无论哪种模式，数据传输都采用端到端加密技术，传输内容对包括平台运营团队在内的任何第三方均不可见。而且这个穿透服务属于系统网络层能力，所有应用——包括官方应用、开发者自主开发的应用、甚至开发者搭建的虚拟机——都自动受益，不需要用户为每个服务单独配置网络穿透规则。</p><p>这意味着什么？意味着你不需要在路由器上开端口映射，不需要配置 DDNS，不需要自己搭建穿透机制或反向代理。设备是主动向外建立加密通道的，公网扫描器看到你家的公网 IP，也发现不了任何可直接访问的服务入口。这从根本上改变了攻击面模型：传统 NAS 是“我把服务摆在公网等你来连”，懒猫微服更像是“我主动出去建立连接，但外面的人看不到我”。对于这类零日漏洞的攻击场景——攻击者批量扫描公网 IP、发现暴露的管理接口、利用漏洞打进去——在这种架构下，第一步就很难成立，因为根本没有暴露在公网的入口可以被扫描到。</p><p>当然，这种“默认不暴露公网、依赖穿透与中继”的模型也不是没有代价：它对平台控制平面的稳定性依赖更强，也意味着用户在一定程度上需要信任厂商的网络基础设施能力。安全复杂度从用户侧转移到了平台侧，这对厂商自身的安全工程能力提出了更高要求。一旦平台侧出现故障，设备本身是安全的，但远程访问体验可能会受到影响，这是这种模型天然需要承担的代价。而且作为一个相对年轻的产品，它的生态成熟度跟老牌 NAS 厂商相比还有差距，自研的容器格式也需要更长时间的社区检验。这些都是选择这种架构路线之前必须接受的现实成本。</p><p>懒猫微服的理念文档里有一段话让我印象很深。它的创始人做了二十多年开源社区，他观察到的一个长期问题是：当个人数据逐步被公有云化之后，商业模式往往会不可避免地走向基于数据的变现逻辑。懒猫微服希望通过私有硬件把个人数据重新放回用户手里。这句话本身听起来像口号，但如果从产品架构反推，这确实是它很多设计选择背后的出发点——例如默认不暴露公网入口、强调端到端加密、尽量减少对第三方的信任假设。当一个产品从理念层面就把“数据尽量掌握在用户自己手里”当成优先目标，它在安全设计上的取舍，确实会和那些更偏向功能堆叠的产品不太一样。</p><p>从工程实现的角度看，懒猫微服更像是在家庭场景下尝试零信任网络的落地：默认不信任公网，默认不暴露服务，通过身份认证与加密通道来建立访问路径。它并不能保证系统永远没有漏洞——没有任何系统能做到这一点——但它把风险从“公网随意可达、被扫到就可能被打穿”，压缩到了“必须先突破身份体系和加密隧道”的范围内，攻击成本的量级明显不在一个级别上。</p><p>硬件层面的事情也值得单独提一下，因为很多人忽略了一个事实：安全能力本身是吃资源的。端到端加密、容器隔离、服务沙箱、AI 本地索引，这些能力全部打开之后，对 CPU 和内存的消耗是实打实存在的。很多入门级 NAS 为了控制成本，硬件资源本身就偏紧张，用户为了“用得顺”，最后只能关掉一部分功能，甚至包括一些安全相关的能力。懒猫的硬件定位更偏向“家庭私有云工作站”，整体资源相对充裕一些，所以在实践中更容易做到“该开的安全能力不需要为了性能被迫关闭”。这一点对安全体验的影响，其实比参数表本身更重要。</p><p>还有一点我觉得值得单独说的，是这个团队给人的感觉。</p><p>用过 NAS 的人大概都有过类似体验：遇到问题去官方论坛发帖，要么石沉大海，要么收到比较模板化的回复，很难确认对面是否真正理解你的具体场景。</p><p>懒猫这边给我的感受完全不一样。他们的一线技术支持是真正懂 Linux 的工程师在做，不是外包给念话术脚本的客服。你提一个问题，回你的人可能就是写那段代码的人，给出的不是套话，而是具体的排查思路或实现路径。用他们自己的话说，逻辑其实很简单：我们是最会玩 Linux 的那批人，你要的我们都有，花钱支持我们的客户，我们就实时响应。这种态度在国内硬件厂商里不多见，对我这种爱折腾的人来说，这种沟通方式本身就是产品体验的一部分。</p><p>实际使用下来也确实如此。懒猫有自己的用户论坛，也有比较活跃的社群，用户反馈的问题经常能看到开发团队跟进。社区里有一个长期存在的主题是“说出你的需求，我们来移植”，开发者会协助把用户常用的自托管应用移植到他们的应用体系里。官方还提供了一些社区激励机制，鼓励开发者参与生态建设。这些机制本身并不决定产品好坏，但它们让生态的扩展变成了一种可持续的系统行为，而不是完全依赖官方单点投入。</p><p>我在论坛上写了 80 多篇懒猫微服的实战连载，从开箱到进阶到炫技，写着写着就停不下来了。推荐你在论坛的攻略里感受到我折腾这台机器的状态：《懒猫智慧屏，我以为是地表最强电视盒子，结果竟然可以改装成闺蜜机？》《用懒猫微服倒推停电时间》《sunshine+moonlight 双人串流打游戏》《蓝牙音浪，懒猫开唱》《西湖邂逅后，我手把手教她玩转 NAS》《坏掉的 Windows 不要扔，硬盘插在懒猫上还能用》《服务器宕机之后，我和前端靠懒猫微服结对编程》。这些都是我在真实场景里折腾出来的记录。写着写着发现社区里其他人也在这么玩：有人用它存 Steam 游戏，有人用它替代 1Password 订阅，有人用它给大疆 Pocket3 导素材，有人用它做 24×7 在线开发机，有人用它的穿透服务让车机远程听黑群晖的歌。这些用法本身说明的不是“产品多强”，而是这种架构在家庭场景下确实覆盖了一些真实需求。如果你恰好是开发者，参与应用移植的过程本身也是学习——Docker 实践、npm 构建这些东西，社区里的工程师会直接带着你走一遍，至少在某个深夜，帮我解答了 docker 的 bind mount 和 volume 的异同和场景对比。</p><p>说回我自己现在的方案。折腾了这么一圈之后，我最终把家里的存储和服务迁到了懒猫微服上。不是因为它完美，而是因为它的安全模型让我第一次觉得“在远程访问这件事上不用操心”。不用开端口，不用配 DDNS，不用自己搭反向代理，也不用担心哪天又爆出一个新的零日漏洞就得手忙脚乱地去堵洞——因为公网本身看不到我的设备。端到端加密是默认开启的，平台侧也无法看到你的数据内容。三层系统架构把底层稳定性、业务隔离和应用安全分得比较清楚，不至于因为装了一个有问题的应用就牵连整个系统。</p><p>对我个人而言，权衡之后答案很明确：我宁可接受“平台偶尔不稳定”带来的体验波动，也不太愿意继续承担“设备 24 小时暴露在公网”这种结构性风险。前者更多是体验问题，后者本质上是安全问题，两者的风险量级并不在一个维度上。</p><p>如果一定要给这次零日漏洞事件一个更有价值的意义，那可能不是提醒我们“哪个厂商不安全”，而是提醒我们：家用 NAS 这条路，本身就存在一个被长期忽视的安全天花板。这个天花板不是靠换品牌能突破的，也不是靠多加几层认证能突破的，它是由“公网暴露”这个基本架构决定的。</p><p>这大概是这次事件教给我的最重要一课：安全这件事，靠补洞永远是被动的，靠架构才是主动的。选 NAS 不是在选品牌，而是在选默认风险模型。</p>]]></content>
    
    
    <summary type="html">飞牛 NAS 零日漏洞事件引发的思考，从威联通到群晖再到飞牛，NAS 安全问题频发，我们到底需要什么样的 NAS。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>使用 AWS SES + S3 发送 HTML 邮件</title>
    <link href="https://blog.no-claw.com/posts/d51f292a/"/>
    <id>https://blog.no-claw.com/posts/d51f292a/</id>
    <published>2026-02-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在营销、通知等场景中，我们经常需要发送格式丰富的 HTML 邮件。本文介绍如何用 Python + boto3，从 S3 读取 HTML 模板并通过 SES 发送邮件。</p><h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p>S3 (HTML模板) → Python脚本 → SES → 收件人</p><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ol><li>AWS 账号已开通 SES 服务，且发件地址已验证</li><li>S3 Bucket 中已上传 HTML 模板文件</li><li>本地已配置 AWS 凭证（aws configure 或 IAM Role）</li><li>安装依赖：pip install boto3</li></ol><h2 id="核心代码"><a href="#核心代码" class="headerlink" title="核心代码"></a>核心代码</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;从 S3 读取 HTML 模板并通过 SES 发送邮件&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> boto3</span><br><span class="line"></span><br><span class="line">REGION = <span class="string">&#x27;&lt;region&gt;&#x27;</span></span><br><span class="line">BUCKET = <span class="string">&#x27;&lt;your-bucket-name&gt;&#x27;</span></span><br><span class="line">TEMPLATE_KEY = <span class="string">&#x27;&lt;your-template-key&gt;.html&#x27;</span></span><br><span class="line"></span><br><span class="line">s3 = boto3.client(<span class="string">&#x27;s3&#x27;</span>, region_name=REGION)</span><br><span class="line">ses = boto3.client(<span class="string">&#x27;ses&#x27;</span>, region_name=REGION)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_html_from_s3</span>(<span class="params">bucket: <span class="built_in">str</span>, key: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;从 S3 读取 HTML 内容&quot;&quot;&quot;</span></span><br><span class="line">    response = s3.get_object(Bucket=bucket, Key=key)</span><br><span class="line">    <span class="keyword">return</span> response[<span class="string">&#x27;Body&#x27;</span>].read().decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">send_html_email</span>(<span class="params">to: <span class="built_in">str</span>, subject: <span class="built_in">str</span>, html_content: <span class="built_in">str</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;发送 HTML 邮件&quot;&quot;&quot;</span></span><br><span class="line">    ses.send_email(</span><br><span class="line">        Source=<span class="string">&#x27;&lt;sender-email&gt;&#x27;</span>,</span><br><span class="line">        Destination=&#123;<span class="string">&#x27;ToAddresses&#x27;</span>: [to]&#125;,</span><br><span class="line">        Message=&#123;</span><br><span class="line">            <span class="string">&#x27;Subject&#x27;</span>: &#123;<span class="string">&#x27;Data&#x27;</span>: subject, <span class="string">&#x27;Charset&#x27;</span>: <span class="string">&#x27;UTF-8&#x27;</span>&#125;,</span><br><span class="line">            <span class="string">&#x27;Body&#x27;</span>: &#123;<span class="string">&#x27;Html&#x27;</span>: &#123;<span class="string">&#x27;Data&#x27;</span>: html_content, <span class="string">&#x27;Charset&#x27;</span>: <span class="string">&#x27;UTF-8&#x27;</span>&#125;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&#x27;邮件已发送至 <span class="subst">&#123;to&#125;</span>&#x27;</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">    html = get_html_from_s3(BUCKET, TEMPLATE_KEY)</span><br><span class="line">    send_html_email(<span class="string">&#x27;&lt;recipient-email&gt;&#x27;</span>, <span class="string">&#x27;测试邮件&#x27;</span>, html)</span><br></pre></td></tr></table></figure><h2 id="关键点说明"><a href="#关键点说明" class="headerlink" title="关键点说明"></a>关键点说明</h2><ul><li>get_html_from_s3：通过 s3.get_object 拉取 HTML 文件内容，注意 decode(‘utf-8’) 确保中文正常显示</li><li>send_html_email：调用 ses.send_email，将 HTML 作为邮件 Body 发送，指定 Charset: UTF-8 避免乱码</li><li>Source 地址必须是 SES 中已验证的邮箱或域名</li></ul>]]></content>
    
    
    <summary type="html">用 Python + boto3 从 S3 读取 HTML 模板，通过 AWS SES 发送格式丰富的营销或通知邮件。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>一次拿trace把langfuse打挂的修复</title>
    <link href="https://blog.no-claw.com/posts/c4eda619/"/>
    <id>https://blog.no-claw.com/posts/c4eda619/</id>
    <published>2026-01-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>用 Langfuse 做 LLM 观测平台，拉 trace 数据时不小心把服务端打挂了。本文记录从发现 502 到定位 Node.js OOM，再到写脚本安全导出标注数据的完整过程。</p></blockquote><p>Langfuse 是一个开源的 LLM 观测平台，用来追踪 LLM 应用的调用链路、记录 input&#x2F;output、做人工标注评估等.</p><p>跑了一段时间，积累了不少 trace 数据和人工标注。某天想通过 API 批量拉取 trace 数据做分析，结果把服务端打挂了。</p><h2 id="故障现象"><a href="#故障现象" class="headerlink" title="故障现象"></a>故障现象</h2><h3 id="第一阶段：502-后端超时"><a href="#第一阶段：502-后端超时" class="headerlink" title="第一阶段：502 后端超时"></a>第一阶段：502 后端超时</h3><p>请求 <code>/api/public/traces/{id}</code> 接口拉取单个 trace 的完整数据时，先是返回 502 Bad Gateway，Nginx&#x2F;OpenResty 报后端超时。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9bc971ea-9c98-4812-baec-5082f0c27824.png" alt="image.png" title="image.png"></p><h3 id="第二阶段：整个应用挂了"><a href="#第二阶段：整个应用挂了" class="headerlink" title="第二阶段：整个应用挂了"></a>第二阶段：整个应用挂了</h3><p>多请求几次之后，不只是 API 超时了，整个 Langfuse Web 界面都打不开了，彻底 503。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9a0346e3-7932-4352-88a0-ad5b4373d2d9.png" alt="image.png" title="image.png"></p><h2 id="根因分析"><a href="#根因分析" class="headerlink" title="根因分析"></a>根因分析</h2><p>把容器日志扔给 AI 分析，定位到了问题：</p><h3 id="因果链"><a href="#因果链" class="headerlink" title="因果链"></a>因果链</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">大 trace 请求</span><br><span class="line">  → 服务端序列化 &gt;4MB 的响应体</span><br><span class="line">    → Node.js 堆内存爆了（OOM）</span><br><span class="line">      → 进程崩溃</span><br><span class="line">        → Nginx/OpenResty 返回 502/503</span><br></pre></td></tr></table></figure><h3 id="具体原因"><a href="#具体原因" class="headerlink" title="具体原因"></a>具体原因</h3><ol><li>Langfuse 是 Next.js 应用，跑在 Node.js 上，默认堆内存上限大约 2GB</li><li>我请求的那几个 trace 数据量很大，每个响应体超过 4MB（日志里反复提示 <code>exceeds 4MB</code>）</li><li>多个大 trace 请求同时处理时，Node.js 内存直接爆了，进程崩溃</li><li>进程挂了之后，前面的反向代理（Nginx&#x2F;OpenResty）拿不到后端响应，就返回 502&#x2F;503</li></ol><h3 id="核心问题"><a href="#核心问题" class="headerlink" title="核心问题"></a>核心问题</h3><p><code>/api/public/traces/{id}</code> 这个接口会返回 trace 的完整数据，包括所有 observations、spans、events 的全部 input&#x2F;output。如果一个 trace 里有多轮 LLM 调用，每轮的 prompt 和 completion 都很长，那整个 trace 的 JSON 响应轻松超过 4MB。</p><p>Node.js 在序列化这么大的 JSON 时，内存占用会远超 JSON 本身的大小（因为要构建字符串、做 UTF-8 编码等），几个大 trace 同时处理就足以把 2GB 堆内存撑爆。</p><h2 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h2><p>我的需求其实很简单：导出所有被人工标注为 “Good” 的 trace 的 input&#x2F;output，用来做后续的微调数据集。</p><p>既然直接拉 trace 会把服务端打挂，那就绕开它：</p><table><thead><tr><th>接口</th><th>返回内容</th><th>风险</th></tr></thead><tbody><tr><td><code>/api/public/traces/{id}</code></td><td>完整 trace（所有 spans、events、input&#x2F;output）</td><td>响应体巨大，容易 OOM</td></tr><tr><td><code>/api/public/observations</code></td><td>按 traceId 查询 observations</td><td>数据量可控，安全</td></tr><tr><td><code>/api/public/scores</code></td><td>所有标注数据（不含 trace 内容）</td><td>很轻量</td></tr></tbody></table><p>策略：</p><ol><li>先通过 <code>/api/public/scores</code> 拿到所有标注，过滤出 Good 的</li><li>再通过 <code>/api/public/observations?traceId=xxx</code> 逐个拉取对应的 input&#x2F;output</li><li>加上重试和限流，避免再次打挂服务端</li></ol><hr><h2 id="抢救脚本"><a href="#抢救脚本" class="headerlink" title="抢救脚本"></a>抢救脚本</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">PUBLIC_KEY = <span class="string">&quot;pk-lf-xxx&quot;</span></span><br><span class="line">SECRET_KEY = <span class="string">&quot;sk-lf-xxx&quot;</span></span><br><span class="line">BASE_URL = <span class="string">&quot;https://your-langfuse-instance.example.com&quot;</span></span><br><span class="line"></span><br><span class="line">session = requests.Session()</span><br><span class="line">session.auth = (PUBLIC_KEY, SECRET_KEY)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_with_retry</span>(<span class="params">url, params=<span class="literal">None</span>, max_retries=<span class="number">3</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;带重试的 GET 请求&quot;&quot;&quot;</span></span><br><span class="line">    r = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(max_retries):</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            r = session.get(url, params=params, timeout=<span class="number">60</span>)</span><br><span class="line">            <span class="keyword">if</span> r.status_code == <span class="number">200</span>:</span><br><span class="line">                <span class="keyword">return</span> r</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;  第<span class="subst">&#123;i+<span class="number">1</span>&#125;</span>次请求返回 <span class="subst">&#123;r.status_code&#125;</span>，等待重试...&quot;</span>)</span><br><span class="line">        <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;  第<span class="subst">&#123;i+<span class="number">1</span>&#125;</span>次请求异常: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        time.sleep(<span class="number">3</span>)</span><br><span class="line">    <span class="keyword">return</span> r</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第一步：获取所有人工标注</span></span><br><span class="line">t0 = time.time()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] === 获取标注数据 ===&quot;</span>)</span><br><span class="line">resp = get_with_retry(</span><br><span class="line">    <span class="string">f&quot;<span class="subst">&#123;BASE_URL&#125;</span>/api/public/scores&quot;</span>,</span><br><span class="line">    params=&#123;<span class="string">&quot;source&quot;</span>: <span class="string">&quot;ANNOTATION&quot;</span>&#125;</span><br><span class="line">)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] 获取标注耗时 <span class="subst">&#123;time.time()-t0:<span class="number">.2</span>f&#125;</span>s, status=<span class="subst">&#123;resp.status_code&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> resp <span class="keyword">or</span> resp.status_code != <span class="number">200</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;请求失败: <span class="subst">&#123;resp.status_code <span class="keyword">if</span> resp <span class="keyword">else</span> <span class="string">&#x27;None&#x27;</span>&#125;</span>&quot;</span>)</span><br><span class="line">    exit()</span><br><span class="line"></span><br><span class="line">scores = resp.json().get(<span class="string">&quot;data&quot;</span>, [])</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;共找到 <span class="subst">&#123;<span class="built_in">len</span>(scores)&#125;</span> 条标注&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二步：过滤 Good 的</span></span><br><span class="line">good_scores = [</span><br><span class="line">    s <span class="keyword">for</span> s <span class="keyword">in</span> scores</span><br><span class="line">    <span class="keyword">if</span> s.get(<span class="string">&quot;value&quot;</span>) == <span class="number">1</span> <span class="keyword">or</span> s.get(<span class="string">&quot;stringValue&quot;</span>) == <span class="string">&quot;Good&quot;</span></span><br><span class="line">]</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;其中 Good 的有 <span class="subst">&#123;<span class="built_in">len</span>(good_scores)&#125;</span> 条&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第三步：用 observations API 逐个拉取（比 traces 轻量，不会撑爆服务端）</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fetch_observations</span>(<span class="params">trace_id</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;拉取 trace 下的 observations，只取 GENERATION 类型的 input/output&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        r = get_with_retry(</span><br><span class="line">            <span class="string">f&quot;<span class="subst">&#123;BASE_URL&#125;</span>/api/public/observations&quot;</span>,</span><br><span class="line">            params=&#123;<span class="string">&quot;traceId&quot;</span>: trace_id&#125;</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> r <span class="keyword">or</span> r.status_code != <span class="number">200</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        obs_list = r.json().get(<span class="string">&quot;data&quot;</span>, [])</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 取 GENERATION 类型的（即 LLM 调用的 input/output）</span></span><br><span class="line">        generations = [o <span class="keyword">for</span> o <span class="keyword">in</span> obs_list <span class="keyword">if</span> o.get(<span class="string">&quot;type&quot;</span>) == <span class="string">&quot;GENERATION&quot;</span>]</span><br><span class="line">        <span class="keyword">if</span> generations:</span><br><span class="line">            <span class="comment"># 取最后一个 generation（通常是最终输出）</span></span><br><span class="line">            gen = generations[-<span class="number">1</span>]</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;input&quot;</span>: gen.get(<span class="string">&quot;input&quot;</span>), <span class="string">&quot;output&quot;</span>: gen.get(<span class="string">&quot;output&quot;</span>)&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 没有 generation 就取第一个 observation</span></span><br><span class="line">        <span class="keyword">if</span> obs_list:</span><br><span class="line">            o = obs_list[<span class="number">0</span>]</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;input&quot;</span>: o.get(<span class="string">&quot;input&quot;</span>), <span class="string">&quot;output&quot;</span>: o.get(<span class="string">&quot;output&quot;</span>)&#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  获取 observations 失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">results = []</span><br><span class="line">t1 = time.time()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] 开始逐个拉取 observations...&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i, s <span class="keyword">in</span> <span class="built_in">enumerate</span>(good_scores):</span><br><span class="line">    trace_id = s.get(<span class="string">&quot;traceId&quot;</span>)</span><br><span class="line">    t = time.time()</span><br><span class="line">    obs = fetch_observations(trace_id)</span><br><span class="line">    elapsed = time.time() - t</span><br><span class="line"></span><br><span class="line">    record = &#123;</span><br><span class="line">        <span class="string">&quot;trace_id&quot;</span>: trace_id,</span><br><span class="line">        <span class="string">&quot;score_name&quot;</span>: s.get(<span class="string">&quot;name&quot;</span>),</span><br><span class="line">        <span class="string">&quot;score_value&quot;</span>: s.get(<span class="string">&quot;value&quot;</span>),</span><br><span class="line">        <span class="string">&quot;string_value&quot;</span>: s.get(<span class="string">&quot;stringValue&quot;</span>),</span><br><span class="line">        <span class="string">&quot;comment&quot;</span>: s.get(<span class="string">&quot;comment&quot;</span>),</span><br><span class="line">        <span class="string">&quot;input&quot;</span>: obs.get(<span class="string">&quot;input&quot;</span>) <span class="keyword">if</span> obs <span class="keyword">else</span> <span class="literal">None</span>,</span><br><span class="line">        <span class="string">&quot;output&quot;</span>: obs.get(<span class="string">&quot;output&quot;</span>) <span class="keyword">if</span> obs <span class="keyword">else</span> <span class="literal">None</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    results.append(record)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] <span class="subst">&#123;i+<span class="number">1</span>&#125;</span>/<span class="subst">&#123;<span class="built_in">len</span>(good_scores)&#125;</span> trace=<span class="subst">&#123;trace_id[:<span class="number">8</span>]&#125;</span>... <span class="subst">&#123;elapsed:<span class="number">.2</span>f&#125;</span>s&quot;</span>)</span><br><span class="line">    time.sleep(<span class="number">0.5</span>)  <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="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] 拉取总耗时 <span class="subst">&#123;time.time()-t1:<span class="number">.2</span>f&#125;</span>s&quot;</span>)</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;good_annotations.json&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(results, f, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>, default=<span class="built_in">str</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;time.strftime(<span class="string">&#x27;%H:%M:%S&#x27;</span>)&#125;</span>] 全部完成，总耗时 <span class="subst">&#123;time.time()-t0:<span class="number">.2</span>f&#125;</span>s&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;已导出 <span class="subst">&#123;<span class="built_in">len</span>(results)&#125;</span> 条 Good 标注到 good_annotations.json&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="脚本设计要点"><a href="#脚本设计要点" class="headerlink" title="脚本设计要点"></a>脚本设计要点</h3><ol><li><strong>绕开 traces 接口</strong>：不用 <code>/api/public/traces/{id}</code>，改用 <code>/api/public/observations?traceId=xxx</code>，返回的数据量小得多</li><li><strong>重试机制</strong>：<code>get_with_retry</code> 最多重试 3 次，每次间隔 3 秒，应对偶发的超时或 5xx</li><li><strong>限流</strong>：每个请求之间 <code>sleep(0.5)</code>，避免并发请求再次打挂服务端</li><li><strong>只取需要的数据</strong>：从 observations 里只取 GENERATION 类型的 input&#x2F;output，不拉完整的 span 树</li></ol><h3 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h3><p>导出的 <code>good_annotations.json</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><span class="line">6</span><br><span class="line">7</span><br><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="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;trace_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc12345-...&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;score_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;quality&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;score_value&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;string_value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Good&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;comment&quot;</span><span class="punctuation">:</span> <span class="string">&quot;回答准确&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;input&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;messages&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span> <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;output&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;choices&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span> <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span> <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>每条记录包含 trace ID、标注信息、以及对应的 LLM input&#x2F;output，可以直接用来构建微调数据集。</p><h2 id="经验总结"><a href="#经验总结" class="headerlink" title="经验总结"></a>经验总结</h2><h3 id="关于-Langfuse"><a href="#关于-Langfuse" class="headerlink" title="关于 Langfuse"></a>关于 Langfuse</h3><ul><li>Langfuse 的 <code>/api/public/traces/{id}</code> 接口会返回完整的 trace 数据，如果 trace 里有大量 LLM 调用，响应体很容易超过 4MB</li><li>Node.js 序列化大 JSON 时内存占用远超 JSON 本身大小，几个大请求就能把默认 2GB 堆内存撑爆</li><li>批量拉数据时，优先用 <code>/api/public/observations</code> 和 <code>/api/public/scores</code> 这类更轻量的接口，按需取数据</li><li>如果确实需要拉大 trace，考虑加 <code>--max-old-space-size</code> 参数给 Node.js 扩大堆内存，或者在 Langfuse 前面加请求大小限制</li></ul><h3 id="关于数据抢救"><a href="#关于数据抢救" class="headerlink" title="关于数据抢救"></a>关于数据抢救</h3><ul><li>服务挂了不要慌，数据还在数据库里，只是 Web 服务进程崩了</li><li>重启容器通常就能恢复，但要避免再次触发同样的问题</li><li>写抢救脚本时，重试 + 限流 + 只取必要字段，三件套缺一不可</li><li>自建 Langfuse 的好处是数据完全在自己手里，但也意味着运维问题得自己扛</li></ul><blockquote><p>相关链接：</p><ul><li>Langfuse 官网：<a href="https://langfuse.com/">https://langfuse.com</a></li><li>Langfuse API 文档：<a href="https://api.reference.langfuse.com/">https://api.reference.langfuse.com</a></li></ul></blockquote>]]></content>
    
    
    <summary type="html">Langfuse 拉 trace 数据导致 Node.js OOM 502，从发现问题到安全导出标注数据的完整排查过程。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="LLM" scheme="https://blog.no-claw.com/tags/LLM/"/>
    
    <category term="Langfuse" scheme="https://blog.no-claw.com/tags/Langfuse/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十四）：方寸之间，自有天地：懒猫微服，男人的网络瑞士军刀</title>
    <link href="https://blog.no-claw.com/posts/5226a270/"/>
    <id>https://blog.no-claw.com/posts/5226a270/</id>
    <published>2026-01-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>曾几何时，中文互联网圈流行着这样一个段子，叫做中年男人三件套：NAS、软路由、充电头。</p><p>我们今天就来聊聊第一个。NAS 似乎是给职业运维人员的福音，而广大的爱好者们通常都是野路子，靠着一腔孤勇或者是兴趣来维护自己的小小世界，能够借鉴参考的，也就是互联网的帖子以及各种群里的答疑而已。</p><p>靠着坚持不懈，入门了 Linux 和网络，但是不求甚解，安静的文件存储，只躺在方寸之间。</p><p>如果你恰好有一台懒猫微服，那么我们正好可以一起来学习这繁杂的网络，拆解这美丽的网络新世界。<br> <span id="more"></span><br>我从事过几年的云行业，在发烧友和职业人员之间横跳，于是心有所感，立志让爱好者可以有专业的技术，让职业人员可以真的产生兴趣。这是网络篇。</p><p>首先你一定听说过 IP 地址，这是互联网通信的门牌号。我们的手机、电脑，包括懒猫微服都有一个 IP 地址。当你连上网络的时候，这个地址就被分配给了设备。准确地说，是分配给了网卡，所以懒猫微服可以用转接口来拓展第二张网卡。</p><p>连上网线之后，第一步我们习惯在路由器上看 IP 地址，然后 ping 一下确认连通。懒猫微服使用 IPv6，可以用 <code>ping -6</code> 或 <code>ping6</code>。</p><h3 id="循迹（ping、telnet）"><a href="#循迹（ping、telnet）" class="headerlink" title="循迹（ping、telnet）"></a>循迹（ping、telnet）</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></pre></td><td class="code"><pre><span class="line">ping 192.168.x.x</span><br><span class="line"><span class="comment"># 如果是 IPv6</span></span><br><span class="line">ping -6 xxx.heiyu.space</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">ping6 xxx.heiyu.space</span><br></pre></td></tr></table></figure><p>我们也可以用 dig 查看域名解析的 IP 地址：</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">dig +short xxx.heiyu.space <span class="comment"># 因为是 IPv6，没有 IPv4 所以解析不到</span></span><br><span class="line">dig AAAA +short xxx.heiyu.space  <span class="comment"># 查 IPv6</span></span><br><span class="line">dig @8.8.8.8 xxx.heiyu.space AAAA +short <span class="comment"># 指定 DNS 服务器查询，也是可以查询到的</span></span><br><span class="line">nslookup xxx.heiyu.space  <span class="comment"># Windows 可以使用这个</span></span><br></pre></td></tr></table></figure><p>在登录之前，可以用 telnet 看端口连通性，然后 SSH 上去。不过一般来说，直接 <code>ssh root@域名/地址</code> 就可以登录了。</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">telnet xxx.heiyu.space 22</span><br></pre></td></tr></table></figure><p>登录之后，我们可以使用 ifconfig 来看网卡信息。主要有这几张网卡比较有用：</p><ul><li>enp2s0物理网卡，连接家里路由器</li><li>wlp129s0无线网卡</li><li>lo回环接口，本机访问本机用（127.0.0.1）</li><li>heiyu-0懒猫穿透隧道，IPv6 内网穿透用</li><li>docker0Playground Docker 默认网桥</li></ul><p>或者使用 <code>ip addr</code>，会更加现代一点。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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"> lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000</span><br><span class="line">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00</span><br><span class="line">    inet 127.0.0.1/8 scope host lo</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br><span class="line">    inet6 ::1/128 scope host noprefixroute</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br><span class="line"></span><br><span class="line">2: enp2s0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP group default qlen 1000</span><br><span class="line">    link/ether xxxxxx brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    altname enx500a52087489</span><br><span class="line">    inet 192.168.8.145/24 brd 192.168.8.255 scope global dynamic noprefixroute enp2s0</span><br><span class="line">       valid_lft 27083sec preferred_lft 27083sec</span><br><span class="line">    inet6 fe80::b7ea:8784:599d:506e/64 scope link noprefixroute</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br></pre></td></tr></table></figure><p>我们再来看一下路由表，使用 <code>route -n</code> 或者 <code>ip route</code> 来查看：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Kernel IP routing table</span><br><span class="line">Destination     Gateway         Genmask         Flags Metric Ref    Use Iface</span><br><span class="line">0.0.0.0         192.168.8.1     0.0.0.0         UG    100    0        0 enp2s0</span><br><span class="line">172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-0da48190134d</span><br><span class="line">......</span><br><span class="line">172.100.0.0     0.0.0.0         255.255.255.0   U     0      0        0 docker0</span><br><span class="line">192.168.8.0     0.0.0.0         255.255.255.0   U     100    0        0 enp2s0</span><br></pre></td></tr></table></figure><p>路由表决定了数据包往哪里走。</p><p>第一条 <code>0.0.0.0/0</code> 是默认路由，所有未知目的地的数据包都会被转发到网关 192.168.8.1（你的路由器）。路由器会通过 NAPT 来实现多台设备共用一个公网 IP 访问互联网。最后一条 <code>192.168.8.0/24</code> 是局域网直连，不需要经过网关。<code>172.100.0.0/24</code> 是 Docker 网桥。</p><blockquote><p>NAPT（Network Address Port Translation）是 NAT 的一种，<br>也叫 PAT（Port Address Translation），它允许多个内部主机共享一个公网 IP 地址，<br>通过不同的端口号来区分不同的连接。<br>你家路由器做的其实是 NAPT：</p><p>192.168.1.100:12345 → 公网IP:50001<br>192.168.1.101:12345 → 公网IP:50002<br>192.168.1.102:12345 → 公网IP:50003</p></blockquote><p>如果你家里恰好有公网 IP，那么也可以通过在路由器上配置端口映射，将外部端口映射到内部的 IP 地址和端口上，让 NAS 可以被外网访问。</p><p>比如，你想让 NAS 的 SSH 服务（默认端口 22）可以从外网访问，可以在路由器上配置端口映射，将路由器的端口（比如 2222）映射到 NAS 的 IP 地址（比如 192.168.1.100）的 22 端口上。</p><p>不过懒猫微服自带了内网穿透，我们可以不用折腾这个部分了。</p><p>一般来说，家庭路由器的 IP 是 192.168.X.1，所以我们的机器可能是 192.168.X.2 或者其它地址。也就是说 192.168.X.0&#x2F;24 就是我们局域网的网段。</p><blockquote><p>192.168开头的是C类IP地址，后面&#x2F;24表示子网掩码，也就是255.255.255.0<br>二进制表示就是 11111111.11111111.11111111.00000000</p><p>也就是前24位是网络号，后8位是主机号。</p><p>一个子网内，主机号不能全为0或者全为1</p><p>192.168.1.0  网络地址（全0）</p><p>192.168.1.255 广播地址（全1）</p><p>192.168.1.1-192.168.1.254 可用地址<br>如果你的设备足够多，200 多个可能不太够用，那么就需要用更大的网段，比如 172.16.0.0&#x2F;12，也就是 172.16.0.0 - 172.31.255.255 可用地址。<br>你也可能看到过 10 开头的地址，这是 A 类私有地址，范围更大：10.0.0.0 - 10.255.255.255。</p></blockquote><p>好了，回到 DNS（Domain Name System）。它是互联网的核心服务之一，作为域名和 IP 地址相互映射的分布式数据库，让我们可以通过域名来访问网站，而不用记一串数字。由于懒猫做了转发，实际看到的不是真正的 IP 地址，这里我们快速略过。</p><p>在局域网内也可以用 dig 查看 IPv4 地址：</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">dig +short your-lazycat.local</span><br></pre></td></tr></table></figure><p>这里看到的地址就和路由器上显示的一样了。</p><p>如果想要使用IPv6，那么你需要确保你的网络环境支持IPv6，并且你的路由器也支持IPv6。你可以通过以下命令来查看你的IPv6地址：</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">ip addr show | grep inet6</span><br></pre></td></tr></table></figure><p>因为 IPv4 地址已枯竭且存在 NAT 性能损耗，现代互联网依据 RFC 6724 国际标准和 Happy Eyeballs 算法，在操作系统和浏览器底层默认赋予了 IPv6 更高的连接优先级（Precedence），旨在确保连接更直接、高效的同时，通过给 IPv6 几十毫秒的“起跑优势”来实现全球网络向下一代协议的平滑过渡。</p><h3 id="下载（curl-wget"><a href="#下载（curl-wget" class="headerlink" title="下载（curl wget)"></a>下载（curl wget)</h3><p>那我们如果想从微服的终端下载东西呢，有 curl 和 wget。通常来讲，curl 用来请求 API，比如测试接口，如果你的懒猫微服里发布了 API 可以用 curl 简单地测试；而 wget 主要用来下载文件。直接在微服上下载，比先下到电脑再 SFTP 传过去快多了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl -X POST https://api.example.com/login \</span><br><span class="line">     -H &quot;Content-Type: application/json&quot; \</span><br><span class="line">     -d &#x27;&#123;&quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;123&quot;&#125;&#x27;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://example.com/file.zip</span><br></pre></td></tr></table></figure><p>好了，接下来就是端口。</p><p>端口是一个逻辑概念，用于标识计算机上的不同服务。每个服务都会监听特定的端口，当有请求到达该端口时，系统会将请求转发给对应的服务。例如，HTTP 服务通常监听 80 端口，HTTPS 服务监听 443 端口，SSH 服务监听 22 端口。</p><p>在懒猫微服中，由于服务大多运行在容器中，所以似乎只有使用 pg-docker 的时候才会注意到端口，或者在终端里临时启动 web server：</p><figure class="highlight plaintext"><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">lzcbox-e66ccc4a ~ # python -m http.server</span><br><span class="line">Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...</span><br></pre></td></tr></table></figure><p>那么我们如何知道哪些端口被占用呢？可以使用 netstat 或 ss 命令来查看。懒猫微服上容器多、服务杂，有时候端口冲突了，用这俩命令一查便知。我通常使用 <code>-nltp</code>：</p><p><strong>参数解释：</strong></p><ul><li><code>-n</code> 显示数字端口（不解析服务名）</li><li><code>-l</code> 只看监听状态</li><li><code>-t</code> 只看 TCP</li><li><code>-p</code> 显示进程信息</li></ul><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">ss -nltp | grep 8000</span><br><span class="line">netstat -nltp | grep 8000</span><br></pre></td></tr></table></figure><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">lzcbox-e66ccc4a ~ <span class="comment"># ss -nltp | grep 8000</span></span><br><span class="line">LISTEN 0      5                                     0.0.0.0:8000       0.0.0.0:*    <span class="built_in">users</span>:((&quot;python&quot;,pid=<span class="number">618243</span>,fd=<span class="number">3</span>))</span><br></pre></td></tr></table></figure><blockquote><p>日常用 <code>ss</code> 就行，更快。老系统或习惯了 <code>netstat</code> 也没问题，结果基本一样。</p></blockquote><p>你可能也会看到其他人使用 lsof，同样可以查端口。lsof 除了查询端口之外，还能查询文件、进程等，这里不再赘述。</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">apt update &amp;&amp; apt install lsof</span><br><span class="line">lsof -i :8000</span><br></pre></td></tr></table></figure><h3 id="测试远程端口：telnet-和-nc"><a href="#测试远程端口：telnet-和-nc" class="headerlink" title="测试远程端口：telnet 和 nc"></a>测试远程端口：telnet 和 nc</h3><p>知道端口被谁占了，下一步就是测试远程端口通不通。比如你在懒猫微服上起了个服务，想从电脑上确认能不能连上。</p><p>telnet 是最经典的方式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">apt install telnet </span><br><span class="line"></span><br><span class="line">lzcbox-e66ccc4a ~ # telnet 192.168.8.1 22</span><br><span class="line">Trying 192.168.8.1...</span><br><span class="line">Connected to 192.168.8.1.</span><br><span class="line">Escape character is &#x27;^]&#x27;.</span><br><span class="line">SSH-2.0-dropbear</span><br></pre></td></tr></table></figure><p>这就是 telnet 的用处——能看到服务返回的 banner 信息，做简单的服务识别。退出方式：按 <code>Ctrl + ]</code> 进入命令模式，然后输入 <code>quit</code>。</p><p>nc（netcat）更灵活，可以批量扫描端口：</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">nc -zv 192.168.8.1 20-100 2&gt;&amp;1 | grep succeeded</span><br><span class="line"></span><br><span class="line">Connection to 192.168.8.1 22 port [tcp/ssh] succeeded!</span><br><span class="line">Connection to 192.168.8.1 53 port [tcp/domain] succeeded!</span><br><span class="line">Connection to 192.168.8.1 80 port [tcp/http] succeeded!</span><br></pre></td></tr></table></figure><h3 id="路径追踪：mtr-和-traceroute"><a href="#路径追踪：mtr-和-traceroute" class="headerlink" title="路径追踪：mtr 和 traceroute"></a>路径追踪：mtr 和 traceroute</h3><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">apt install mtr traceroute</span><br></pre></td></tr></table></figure><p>网络不通的时候，光 ping 只能告诉你”不通”，但不知道卡在哪一跳。这时候就需要 traceroute 或 mtr。</p><p>traceroute 是单次探测，跑一遍就结束：</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">traceroute baidu.com</span><br></pre></td></tr></table></figure><p>mtr 更强，结合了 ping 和 traceroute，持续发包，实时显示每一跳的丢包率和延迟：</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">mtr baidu.com</span><br></pre></td></tr></table></figure><p>网络时好时坏的时候，traceroute 可能刚好跑的时候没问题就抓不到，mtr 跑几分钟就能看到哪一跳在丢包。日常排查推荐用 mtr。</p><h3 id="抓包：tshark"><a href="#抓包：tshark" class="headerlink" title="抓包：tshark"></a>抓包：tshark</h3><p>和 tcpdump 相比，tshark 的协议解析能力更强，能直接看到 HTTP 请求内容，而 tcpdump 只能看到十六进制。</p><p>懒猫微服上跑着各种服务，有时候想看看某个应用到底在发什么请求、收什么响应，tshark 就派上用场了。</p><p>懒猫的系统环境如果默认没装 tshark，需要使用这个命令进行安装</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">apt update &amp;&amp; apt install -y tshark</span><br></pre></td></tr></table></figure><p>用 tshark 抓一个本地 HTTP 请求，看看 TCP&#x2F;HTTP 到底是怎么通信的。先在一个终端启动抓包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 抓 lo 回环接口（本机访问本机走这里）</span></span><br><span class="line">tshark -i lo -f <span class="string">&quot;port 8000&quot;</span></span><br></pre></td></tr></table></figure><p>然后另一个终端访问 <code>curl localhost:8000</code>，抓到 12 个包：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">tshark -i lo -f &quot;port 8000&quot;</span><br><span class="line">Running as user &quot;root&quot; and group &quot;root&quot;. This could be dangerous.</span><br><span class="line">Capturing on &#x27;Loopback: lo&#x27;</span><br><span class="line"> ** (tshark:1297291) 16:39:23.579519 [Main MESSAGE] -- Capture started.</span><br><span class="line"> ** (tshark:1297291) 16:39:23.579553 [Main MESSAGE] -- File: &quot;/tmp/wireshark_loK51JK3.pcapng&quot;</span><br><span class="line">    1 0.000000000 192.168.8.145 → 192.168.8.145 TCP 74 59548 → 8000 [SYN] Seq=0 Win=65495 Len=0 MSS=65495 SACK_PERM TSval=665319807 TSecr=0 WS=1024</span><br><span class="line">    2 0.000007817 192.168.8.145 → 192.168.8.145 TCP 74 8000 → 59548 [SYN, ACK] Seq=0 Ack=1 Win=65483 Len=0 MSS=65495 SACK_PERM TSval=665319807 TSecr=665319807 WS=1024</span><br><span class="line">    3 0.000012956 192.168.8.145 → 192.168.8.145 TCP 66 59548 → 8000 [ACK] Seq=1 Ack=1 Win=65536 Len=0 TSval=665319807 TSecr=665319807</span><br><span class="line">    4 0.000035036 192.168.8.145 → 192.168.8.145 HTTP 148 GET / HTTP/1.1</span><br><span class="line">    5 0.000036994 192.168.8.145 → 192.168.8.145 TCP 66 8000 → 59548 [ACK] Seq=1 Ack=83 Win=65536 Len=0 TSval=665319807 TSecr=665319807</span><br><span class="line">    6 0.000801645 192.168.8.145 → 192.168.8.145 TCP 221 HTTP/1.0 200 OK  [TCP segment of a reassembled PDU]</span><br><span class="line">    7 0.000813247 192.168.8.145 → 192.168.8.145 TCP 66 59548 → 8000 [ACK] Seq=83 Ack=156 Win=65536 Len=0 TSval=665319808 TSecr=665319808</span><br><span class="line">    8 0.000834148 192.168.8.145 → 192.168.8.145 HTTP 976 HTTP/1.0 200 OK  (text/html)</span><br><span class="line">    9 0.000837352 192.168.8.145 → 192.168.8.145 TCP 66 59548 → 8000 [ACK] Seq=83 Ack=1066 Win=65536 Len=0 TSval=665319808 TSecr=665319808</span><br><span class="line">   10 0.000857121 192.168.8.145 → 192.168.8.145 TCP 66 8000 → 59548 [FIN, ACK] Seq=1066 Ack=83 Win=65536 Len=0 TSval=665319808 TSecr=665319808</span><br><span class="line">   11 0.000901652 192.168.8.145 → 192.168.8.145 TCP 66 59548 → 8000 [FIN, ACK] Seq=83 Ack=1067 Win=65536 Len=0 TSval=665319808 TSecr=665319808</span><br><span class="line">   12 0.000914845 192.168.8.145 → 192.168.8.145 TCP 66 8000 → 59548 [ACK] Seq=1067 Ack=84 Win=65536 Len=0 TSval=665319808 TSecr=665319808</span><br></pre></td></tr></table></figure><p>针对上图的结果，我也总结了一个流程图，请看：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">客户端 (59548)                    服务端 (8000)</span><br><span class="line">    |                                  |</span><br><span class="line">    |  -------- [SYN] ---------&gt;       |  1. 请求连接</span><br><span class="line">    |  &lt;----- [SYN,ACK] --------       |  2. 同意连接</span><br><span class="line">    |  -------- [ACK] ---------&gt;       |  3. 确认</span><br><span class="line">    |                                  |</span><br><span class="line">    |  ===== 连接建立 =====            |</span><br><span class="line">    |                                  |</span><br><span class="line">    |  ---- GET / HTTP/1.1 ----&gt;       |  4. 发请求</span><br><span class="line">    |  &lt;------- [ACK] ----------       |  5. 确认</span><br><span class="line">    |  &lt;---- 200 OK + HTML -----       |  6-8. 返回数据</span><br><span class="line">    |  -------- [ACK] ---------&gt;       |  9. 确认</span><br><span class="line">    |                                  |</span><br><span class="line">    |  ===== 数据传完 =====            |</span><br><span class="line">    |                                  |</span><br><span class="line">    |  &lt;------ [FIN,ACK] -------       |  10. 服务端关闭</span><br><span class="line">    |  ------- [FIN,ACK] ------&gt;       |  11. 客户端关闭</span><br><span class="line">    |  &lt;-------- [ACK] ---------       |  12. 最终确认</span><br><span class="line">    |                                  |</span><br><span class="line">    |  ===== 连接关闭 =====            |</span><br></pre></td></tr></table></figure><p>TCP 三次握手（建立连接）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1  59548 → 8000 [SYN]      &quot;我想连接你&quot;</span><br><span class="line">2  8000 → 59548 [SYN,ACK]  &quot;好的，我也准备好了&quot;</span><br><span class="line">3  59548 → 8000 [ACK]      &quot;收到，开始通信&quot;</span><br></pre></td></tr></table></figure><p>为什么要三次？确保双方都能收发。两次不够——服务端不知道客户端能不能收到它的回复。</p><p>HTTP 请求&#x2F;响应</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">4  GET / HTTP/1.1          客户端请求首页</span><br><span class="line">5  [ACK]                   服务端确认收到</span><br><span class="line">6-8  HTTP/1.0 200 OK       服务端返回网页</span><br><span class="line">9  [ACK]                   客户端确认收到</span><br></pre></td></tr></table></figure><p>TCP 四次挥手（关闭连接）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">10  [FIN,ACK]  服务端 → 客户端  &quot;我发完了&quot;</span><br><span class="line">11  [FIN,ACK]  客户端 → 服务端  &quot;我也发完了&quot;</span><br><span class="line">12  [ACK]      服务端 → 客户端  &quot;好的，再见&quot;</span><br></pre></td></tr></table></figure><p>虽然抓包看只有三行，但逻辑上它们依然是四次挥手的变体。</p><p>一共 12 个包，一次完整的 HTTP 请求就完成了。</p><h3 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h3><p>网络之道，说繁亦繁，说简亦简。繁在协议层层叠叠，简在万变不离其宗——包从哪来，到哪去。</p><p>能用 tshark 看懂三次握手，用 mtr 定位丢包，用 ss 追溯端口归属，便已胜过大多数人。</p><p>所谓高手，不过是在无数次折腾中，把书本上的知识化作了指尖的肌肉记忆。</p><p>而懒猫微服，恰是这样一方天地——自带穿透，容器隔离，root 权限在握，尽可放手施为。纵有闪失，重置便是，不伤筋骨。</p><p>这大概就是它最大的魅力：不只是一台 NAS，更是折腾 infra、钻研安全的瑞士军刀。</p><p>所有的好奇心，皆有安放之处。</p>]]></content>
    
    
    <summary type="html">懒猫微服作为家庭网络核心设备的全方位体验，集网络管理、运维工具于一身的瑞士军刀。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="网络" scheme="https://blog.no-claw.com/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十五）：超越systemd，不用每次开机都再安装软件～</title>
    <link href="https://blog.no-claw.com/posts/c1ba52cc/"/>
    <id>https://blog.no-claw.com/posts/c1ba52cc/</id>
    <published>2026-01-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>懒猫微服是分层文件系统，所以在之前的文章里前面我们使用用 <code>systemctl --user</code> 在开机时自动安装软件，解决重启丢包的问题。但说实话，每次开机都要跑一遍 <code>apt install</code>，当面对软件包过多以及网络延迟的问题的时候，使用dkpg会卡住，这不够优雅，所以才有了这个方案。</p><p>后来我换了个思路：既然懒猫微服天生就是为 Docker 而生的，为什么不直接用容器来跑这些工具呢？</p><p>想想看，htop、iotop、glances 这些运维神器，本质上就是读取 <code>/proc</code> 和 <code>/sys</code> 里的系统信息。只要让容器能访问宿主机的这些文件，不就能正常工作了吗？</p><p>这篇文章就来聊聊怎么用 Docker 容器跑系统监控工具。不用装软件，不怕重启丢失，一条命令就能用，用完自动清理。这让我们既享受了分层文件系统的优势，也不用再为软件持久化烦恼了。<br> <span id="more"></span></p><blockquote><p><strong>关于镜像源</strong>：本文使用了 <code>docker.1ms.run</code> 作为镜像加速站，仅作示例，不对镜像源的可用性负责。如果某个镜像拉取失败，可以换成其他镜像源，比如 <code>dockerpull.org</code>、<code>docker.xuanyuan.me</code> 等。</p></blockquote><h3 id="进程监控类"><a href="#进程监控类" class="headerlink" title="进程监控类"></a>进程监控类</h3><h4 id="htop-经典进程查看器"><a href="#htop-经典进程查看器" class="headerlink" title="htop - 经典进程查看器"></a>htop - 经典进程查看器</h4><p>我们习惯的使用方式是 <code>apt install htop</code>，但在懒猫微服的分层文件系统下重启后会丢失。所以我们可以用Docker来一键设置：</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">pg-docker run --<span class="built_in">rm</span> -it --pid host docker.1ms.run/frapsoft/htop</span><br></pre></td></tr></table></figure><p>这条命令的作用是：启动一个包含 <code>htop</code> 工具的 Docker 容器，并以交互式方式查看宿主机的进程列表。容器停止后会自动删除。<code>htop</code> 是一个用于实时查看和管理系统进程的交互式工具。我们可以逐部分解析一下这条命令：</p><ol><li><strong><code>docker run</code></strong>:<br> 这是 Docker 命令，用来启动一个新的容器并执行指定的命令。</li><li><strong><code>--rm</code></strong>:<br> 这个选项表示容器在停止后会被自动删除。这样可以避免容器停留在系统上，占用空间。</li><li><strong><code>-it</code></strong>:<ul><li><code>-i</code> 表示让容器保持交互模式（interactive），即允许你输入命令。</li><li><code>-t</code> 表示分配一个伪终端（tty），让你可以以终端方式与容器交互。</li></ul></li><li><strong><code>--pid host</code></strong>:<br> 这个选项使得容器能够访问宿主机的进程信息。正常情况下，容器只能看到自己内部的进程，但通过 <code>--pid host</code>，容器可以查看宿主机的所有进程，这样你就可以通过 <code>htop</code> 查看宿主机的进程。</li><li><strong><code>frapsoft/htop</code></strong>:<br> 这是一个 Docker 镜像的名称，它提供了一个包含 <code>htop</code> 工具的容器镜像。运行这个镜像会启动 <code>htop</code>，让你可以在容器中查看进程。</li></ol><p>为了方便，还可以在 <code>.bashrc</code> 里加个 alias，这样就能像本地命令一样直接敲 <code>htop</code> 使用了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;alias htop=&#x27;pg-docker run --rm -it --pid host docker.1ms.run/frapsoft/htop&#x27;&quot;</span> &gt;&gt; ~/.bashrc</span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</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">lzcbox-e66ccc4a ~ <span class="comment"># pg-docker images</span></span><br><span class="line">REPOSITORY                      TAG       IMAGE ID       CREATED       SIZE</span><br><span class="line">docker.1ms.run/library/alpine   latest    a40c03cbb81c   3 days ago    8.44MB</span><br><span class="line">docker.1ms.run/frapsoft/htop    latest    4b76ab24430c   9 years ago   8.19MB</span><br></pre></td></tr></table></figure><p>如果你经常玩docker，那么除了–pid之外一定都不陌生。对于默认来讲，–pid 的参数是 private，所以我们常说每个容器有独立的进程命名空间，容器只能看到自己内部的进程。用了**<code>--pid host</code>** 之后，容器就可以共享宿主机的进程命名空间。这样，容器可以查看宿主机上的所有进程，而不仅仅是容器内部的进程。所以我们可以用它来做一些监控。甚至还有，**<code>--pid container:&lt;container_id&gt;</code>**:- 容器将能够看到并与指定容器的进程共享命名空间。通常在多容器间需要共享进程信息或调试时使用。例如，一个容器需要查看另一个容器的进程或进行故障排查。当然我们这里不做仔细展开。</p><blockquote><p>关于 Docker 参数解释以及 alias、bashrc 的修改，这里只以 htop 为例，后面的工具不再赘述。</p></blockquote><p>同理我们也可以测试其他工具。</p><h4 id="glances-全能系统监控"><a href="#glances-全能系统监控" class="headerlink" title="glances - 全能系统监控"></a>glances - 全能系统监控</h4><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">pg-docker run --<span class="built_in">rm</span> -it --pid host --net host \</span><br><span class="line">  -v /var/run/docker.sock:/var/run/docker.sock:ro \</span><br><span class="line">  docker.1ms.run/nicolargo/glances</span><br></pre></td></tr></table></figure><p>glances 比 htop 更全面，是个「一屏看所有」的监控工具。CPU、内存、网络、磁盘、Docker 容器状态全都有。参数说明：</p><ul><li><code>--pid host</code>：读取宿主机进程信息</li><li><code>--net host</code>：读取宿主机网络信息</li><li><code>-v docker.sock</code>：让 glances 能监控 Docker 容器</li></ul><h4 id="btop-htop-的现代化替代"><a href="#btop-htop-的现代化替代" class="headerlink" title="btop - htop 的现代化替代"></a>btop - htop 的现代化替代</h4><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">pg-docker run --<span class="built_in">rm</span> -it --pid host terorie/btop</span><br></pre></td></tr></table></figure><p>btop 界面更炫酷，功能也更丰富，支持鼠标操作。不过这个镜像比较冷门，<code>docker.1ms.run</code> 等加速站可能没缓存，所以这里直接用 Docker Hub 官方源。如果拉取慢，可以换成其他加速源试试。</p><h4 id="ctop-Docker-容器专用监控"><a href="#ctop-Docker-容器专用监控" class="headerlink" title="ctop - Docker 容器专用监控"></a>ctop - Docker 容器专用监控</h4><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">pg-docker run --<span class="built_in">rm</span> -it \</span><br><span class="line">  -v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">  quay.io/vektorlab/ctop</span><br></pre></td></tr></table></figure><p>这个镜像来自 quay.io（Red Hat 的镜像仓库），国内访问通常比 Docker Hub 顺畅，可以直接拉取。ctop 专门用来监控 Docker 容器的资源占用。</p><p>不过要注意，ctop 默认连接 <code>/var/run/docker.sock</code>，这是系统 Docker 的 socket。如果你想监控 playground 里的容器，需要挂载 playground 的 socket：</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">pg-docker run --<span class="built_in">rm</span> -it \</span><br><span class="line">  -v /data/playground/docker.sock:/var/run/docker.sock:ro \</span><br><span class="line">  quay.io/vektorlab/ctop</span><br></pre></td></tr></table></figure><h4 id="netshoot-网络排障瑞士军刀"><a href="#netshoot-网络排障瑞士军刀" class="headerlink" title="netshoot - 网络排障瑞士军刀"></a>netshoot - 网络排障瑞士军刀</h4><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">pg-docker run --<span class="built_in">rm</span> -it --net host --pid host --privileged docker.1ms.run/nicolaka/netshoot</span><br></pre></td></tr></table></figure><p>netshoot 内置了 tcpdump、iftop、nmap、curl、dig 等一堆网络工具，临时排查问题特别方便。网络监控工具必须加 <code>--net host</code>，否则测的是容器网络而不是宿主机网络。</p><p>进入容器后，先用 <code>ip link</code> 查看网卡名。懒猫微服的主网卡通常是 <code>enp2s0</code> 而不是 <code>eth0</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"># 进入容器后</span></span><br><span class="line">ip <span class="built_in">link</span>                    <span class="comment"># 查看网卡列表</span></span><br><span class="line">iftop -i enp2s0           <span class="comment"># 监控主网卡流量</span></span><br><span class="line">tcpdump -i enp2s0         <span class="comment"># 抓包</span></span><br></pre></td></tr></table></figure><p>注意：netshoot 里没有 nethogs，如果需要按进程查看流量，可以用 alpine 临时安装：</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">pg-docker run --<span class="built_in">rm</span> -it --net host --pid host --privileged alpine sh -c <span class="string">&quot;apk add --no-cache nethogs &amp;&amp; nethogs&quot;</span></span><br></pre></td></tr></table></figure><h4 id="工具选择指南"><a href="#工具选择指南" class="headerlink" title="工具选择指南"></a>工具选择指南</h4><table><thead><tr><th>工具</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td>htop</td><td>轻量、经典</td><td>快速查看进程，日常使用</td></tr><tr><td>btop</td><td>界面炫酷、支持鼠标</td><td>喜欢好看界面的用户</td></tr><tr><td>glances</td><td>功能全面、一屏显示所有</td><td>需要综合监控的场景</td></tr><tr><td>ctop</td><td>专注 Docker 容器</td><td>监控容器资源占用</td></tr><tr><td>netshoot</td><td>网络工具箱</td><td>网络排障、抓包分析</td></tr></tbody></table><h3 id="理解-–pid-host-的本质"><a href="#理解-–pid-host-的本质" class="headerlink" title="理解 –pid host 的本质"></a>理解 –pid host 的本质</h3><p><code>--pid host</code> 的本质是让容器挂载宿主机的 <code>/proc</code>，而不是容器自己隔离的那份。</p><p>容器内的监控工具之所以能工作，是因为 Linux 的系统信息都存在 <code>/proc</code> 虚拟文件系统里：</p><table><thead><tr><th>文件</th><th>内容</th></tr></thead><tbody><tr><td><code>/proc/cpuinfo</code></td><td>CPU 信息</td></tr><tr><td><code>/proc/meminfo</code></td><td>内存信息</td></tr><tr><td><code>/proc/uptime</code></td><td>运行时间</td></tr><tr><td><code>/proc/version</code></td><td>内核版本</td></tr><tr><td><code>/proc/loadavg</code></td><td>系统负载</td></tr><tr><td><code>/proc/&lt;pid&gt;/stat</code></td><td>进程状态</td></tr></tbody></table><p>常见工具的数据来源：</p><table><thead><tr><th>工具</th><th>读取的文件</th></tr></thead><tbody><tr><td>htop&#x2F;top</td><td><code>/proc/&lt;pid&gt;/stat</code>, <code>/proc/meminfo</code>, <code>/proc/loadavg</code></td></tr><tr><td>free</td><td><code>/proc/meminfo</code></td></tr><tr><td>df</td><td><code>/proc/mounts</code>, <code>/proc/diskstats</code></td></tr><tr><td>ps</td><td><code>/proc/&lt;pid&gt;/status</code>, <code>/proc/&lt;pid&gt;/cmdline</code></td></tr><tr><td>netstat&#x2F;ss</td><td><code>/proc/net/tcp</code>, <code>/proc/net/udp</code></td></tr><tr><td>vmstat</td><td><code>/proc/stat</code>, <code>/proc/vmstat</code></td></tr><tr><td>iostat</td><td><code>/proc/diskstats</code></td></tr></tbody></table><p>所以 <code>--pid host</code> 能让这些工具在容器里正常工作，因为它们本质上就是读文件，共享了 <code>/proc</code> 就等于共享了系统信息。</p><h3 id="proc-vs-etc-os-release"><a href="#proc-vs-etc-os-release" class="headerlink" title="&#x2F;proc vs &#x2F;etc&#x2F;os-release"></a>&#x2F;proc vs &#x2F;etc&#x2F;os-release</h3><p>有个细节需要注意：<code>/proc/version</code> 和 <code>/etc/os-release</code> 记录的是不同层面的信息。</p><p><code>/proc/version</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="built_in">cat</span> /proc/version</span><br><span class="line"></span><br><span class="line">Linux version 6.16.6-x64v3-xanmod1 (root@runner-6areqvs9r-project-51590166-concurrent-0) (Debian clang version 19.1.7 (3), Debian LLD 19.1.7) <span class="comment">#0~20250909.g439cb47 SMP PREEMPT_DYNAMIC Tue Sep  9 22:24:21 UTC</span></span><br></pre></td></tr></table></figure><p><code>/etc/os-release</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/os-release</span><br><span class="line"></span><br><span class="line">PRETTY_NAME=<span class="string">&quot;Debian GNU/Linux 12 (bookworm)&quot;</span></span><br><span class="line">NAME=<span class="string">&quot;Debian GNU/Linux&quot;</span></span><br><span class="line">VERSION_ID=<span class="string">&quot;12&quot;</span></span><br><span class="line">VERSION=<span class="string">&quot;12 (bookworm)&quot;</span></span><br><span class="line">VERSION_CODENAME=bookworm</span><br><span class="line">ID=debian</span><br><span class="line">HOME_URL=<span class="string">&quot;https://www.debian.org/&quot;</span></span><br><span class="line">SUPPORT_URL=<span class="string">&quot;https://www.debian.org/support&quot;</span></span><br><span class="line">BUG_REPORT_URL=<span class="string">&quot;https://bugs.debian.org/&quot;</span></span><br></pre></td></tr></table></figure><p>打个比方：</p><ul><li><code>/proc/version</code> &#x3D; 引擎型号（Linux 6.16）</li><li><code>/etc/os-release</code> &#x3D; 车的品牌（Debian Linux）</li></ul><p>同一个内核可以跑不同发行版，所以如果你想让容器里的工具显示正确的操作系统名称，需要手动挂载 <code>/etc/os-release</code>。</p><h3 id="进阶：自己打包docker工具"><a href="#进阶：自己打包docker工具" class="headerlink" title="进阶：自己打包docker工具"></a>进阶：自己打包docker工具</h3><p>我们经常习惯使用，fastfetch 显示系统信息，但还是没有找到别人打包好的容器的版本。fastfetch 是 neofetch 的现代替代品，速度更快。</p><p>我们可以用使用alpine安装二进制包的方式来运行，也可以自定义Docker images来跑：</p><p>先看一个使用alpine安装的：</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">pg-docker run --<span class="built_in">rm</span> -it \</span><br><span class="line">  --pid host \</span><br><span class="line">  --net host \</span><br><span class="line">  -v /etc/os-release:/etc/os-release:ro \</span><br><span class="line">  -v /etc/hostname:/etc/hostname:ro \</span><br><span class="line">  docker.1ms.run/library/alpine sh -c <span class="string">&quot;apk add --no-cache fastfetch &amp;&amp; fastfetch&quot;</span></span><br></pre></td></tr></table></figure><p>参数说明：</p><ul><li><code>--pid host</code>：让 fastfetch 读到宿主机的 CPU、内存、内核信息</li><li><code>-v /etc/os-release:/etc/os-release:ro</code>：让 fastfetch 读到正确的操作系统名称</li><li><code>-v /etc/hostname:/etc/hostname:ro</code>：让 fastfetch 读到正确的主机名</li></ul><p>虽然不是很完美，但是已经可以读取到大多数的信息了，</p><p>再看一个自己打包的Docker版本。</p><figure class="highlight dockerfile"><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">FROM</span> alpine:latest</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apk add --no-cache fastfetch</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;fastfetch&quot;</span>]</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></pre></td><td class="code"><pre><span class="line">pg-docker build -t my-fastfetch .</span><br><span class="line">pg-docker run --<span class="built_in">rm</span> -it --pid host \</span><br><span class="line">  -v /etc/os-release:/etc/os-release:ro \</span><br><span class="line">  my-fastfetch</span><br></pre></td></tr></table></figure><p>同理我们也可以基于alpine构建基于任何软件的镜像工具，我这里再举一个例子，比如iotop。</p><figure class="highlight plaintext"><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">pg-docker run --rm -it --pid host --privileged docker.1ms.run/library/alpine sh -c &quot;apk add --no-cache iotop &amp;&amp; iotop -o&quot;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="关于-sys-文件系统"><a href="#关于-sys-文件系统" class="headerlink" title="关于 &#x2F;sys 文件系统"></a>关于 &#x2F;sys 文件系统</h3><p>除了 <code>/proc</code>，Linux 还有 <code>/sys</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 读网卡 MAC 地址</span></span><br><span class="line">pg-docker run --<span class="built_in">rm</span> -it -v /sys:/sys:ro docker.1ms.run/library/alpine <span class="built_in">cat</span> /sys/class/net/enp2s0/address</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有网卡</span></span><br><span class="line">pg-docker run --<span class="built_in">rm</span> -it -v /sys:/sys:ro docker.1ms.run/library/alpine <span class="built_in">ls</span> /sys/class/net/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读 CPU 温度</span></span><br><span class="line">pg-docker run --<span class="built_in">rm</span> -it -v /sys:/sys:ro docker.1ms.run/library/alpine <span class="built_in">cat</span> /sys/class/thermal/thermal_zone0/temp</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读磁盘大小</span></span><br><span class="line">pg-docker run --<span class="built_in">rm</span> -it -v /sys:/sys:ro docker.1ms.run/library/alpine <span class="built_in">cat</span> /sys/block/nvme0n1/size</span><br></pre></td></tr></table></figure><h3 id="其他命名空间参数"><a href="#其他命名空间参数" class="headerlink" title="其他命名空间参数"></a>其他命名空间参数</h3><p>除了 <code>--pid host</code>，Docker 还支持其他命名空间共享：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 共享网络命名空间</span></span><br><span class="line">--net host</span><br><span class="line"></span><br><span class="line"><span class="comment"># 共享 UTS 命名空间（主机名和域名）</span></span><br><span class="line">--uts host</span><br><span class="line"></span><br><span class="line"><span class="comment"># 共享 IPC 命名空间（进程间通信）</span></span><br><span class="line">--ipc host</span><br><span class="line"></span><br><span class="line"><span class="comment"># 共享指定容器的进程命名空间</span></span><br><span class="line">--pid container:&lt;container_id&gt;</span><br></pre></td></tr></table></figure><p><code>--pid container:&lt;container_id&gt;</code> 在多容器调试时很有用，可以让一个容器看到另一个容器的进程。</p><h4 id="关于-–privileged"><a href="#关于-–privileged" class="headerlink" title="关于 –privileged"></a>关于 –privileged</h4><p>你可能注意到有些命令用了 <code>--privileged</code>，这个参数会让容器获得几乎所有的宿主机权限：</p><ul><li>访问所有设备（<code>/dev/*</code>）</li><li>加载内核模块</li><li>修改系统配置</li><li>访问完整的 <code>/proc</code> 和 <code>/sys</code></li></ul><p>像 iotop、nethogs 这类需要深度访问内核信息的工具，没有 <code>--privileged</code> 可能跑不起来。但要注意，这个参数权限很大，只在信任的镜像上使用，用完记得 <code>--rm</code> 自动清理。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>懒猫微服的分层文件系统固然好，但是常用软件会有丢失安装的问题，所以我们用容器来跑这些监控工具，容器镜像重启不会丢失，配置一个alias和bashrc就可以完美兼容这个feature。</p><p>容器里读取宿主机的参数有这三板斧：</p><ul><li>看进程用 <code>--pid host</code></li><li>看网络用 <code>--net host</code>  </li><li>看磁盘用 <code>--privileged</code></li></ul><p>记住这三个，大多数监控工具都能在容器里跑起来。</p><p>回头看看，之前用 <code>systemctl --user</code> 开机自动装软件，虽然能用，但每次重启都要跑一遍 <code>apt install</code>，还会受限于软件体积和网络问题。现在换成 Docker 镜像，拉一次就永久缓存，重启秒开，彻底告别「开机等安装」的烦恼。</p><p>超越systemd，不用每次开机都再安装软件～</p>]]></content>
    
    
    <summary type="html">用 Docker 容器运行系统监控工具，免去每次开机重装软件的烦恼，懒猫微服上的容器化运维实践。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服：不只是 NAS，更适合云计算宝宝体质</title>
    <link href="https://blog.no-claw.com/posts/b0a96c84/"/>
    <id>https://blog.no-claw.com/posts/b0a96c84/</id>
    <published>2026-01-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>提到 NAS，大多数人的第一反应是”存储设备”——用来备份照片、存电影、共享文件。但懒猫微服想做的，远不止于此。</p><p>在云计算时代，个人和小团队同样需要强大的计算能力、灵活的开发环境和丰富的应用生态。懒猫微服正是为此而生，它不仅能存储数据，更能像云服务器一样运行虚拟机、部署应用、搭建开发环境。从底层的虚拟化基础设施，到中间层的平台服务，再到上层的软件应用，懒猫微服构建了一个完整的私有云生态。</p><h3 id="云计算三层架构：IaaS、PaaS、SaaS"><a href="#云计算三层架构：IaaS、PaaS、SaaS" class="headerlink" title="云计算三层架构：IaaS、PaaS、SaaS"></a>云计算三层架构：IaaS、PaaS、SaaS</h3><p>云计算的核心架构分为三层：<strong>IaaS</strong>（Infrastructure as a Service，基础设施即服务）提供虚拟化的计算、存储和网络资源；<strong>PaaS</strong>（Platform as a Service，平台即服务）在 IaaS 之上提供应用运行平台和开发工具；<strong>SaaS</strong>（Software as a Service，软件即服务）则提供开箱即用的应用软件。</p><p>用一个形象的比喻：IaaS 像是租了一块地和建筑材料，你要自己盖房子、装修、买家具；PaaS 像是租了一套毛坯房，你只需要装修和买家具；SaaS 则像是租了一套精装修的酒店房间，拎包入住。</p><p>懒猫微服正是基于这一架构理念，构建了完整的私有云计算平台。接下来，我们将深入探讨懒猫微服在这三个层面的技术实现。</p><h3 id="懒猫微服的-IaaS-实现：虚拟化基础设施"><a href="#懒猫微服的-IaaS-实现：虚拟化基础设施" class="headerlink" title="懒猫微服的 IaaS 实现：虚拟化基础设施"></a>懒猫微服的 IaaS 实现：虚拟化基础设施</h3><h4 id="IaaS-层的技术本质"><a href="#IaaS-层的技术本质" class="headerlink" title="IaaS 层的技术本质"></a>IaaS 层的技术本质</h4><p>让我们从最底层说起。IaaS 提供的是虚拟化的计算基础设施——虚拟机实例、块存储（Block Storage）、对象存储（Object Storage）、虚拟网络（VPC）、负载均衡器。这意味着什么？意味着你拥有了完整的基础设施控制权，但也需要承担相应的管理责任：操作系统、中间件、应用程序的安装配置，系统更新、安全补丁、网络策略的维护。AWS EC2、阿里云 ECS、Azure Virtual Machines 走的都是这条路。</p><h4 id="懒猫微服的虚拟机能力"><a href="#懒猫微服的虚拟机能力" class="headerlink" title="懒猫微服的虚拟机能力"></a>懒猫微服的虚拟机能力</h4><p>懒猫微服在 IaaS 层做了什么？我们集成了 WebVirtCloud 虚拟化管理平台，让你可以轻松创建和管理各种操作系统的虚拟机。Windows 轻办公？没问题。各类 Linux 发行版（Ubuntu、Debian、CentOS、Arch Linux）做服务器？当然可以。想要 macOS 开发环境？甚至 Android 移动应用测试？统统支持。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/984eb45a-7d48-4f94-aa47-0018f8e511fc.png" alt="1e5eea3caa005a7025b6b988190374c1.png" title="1e5eea3caa005a7025b6b988190374c1.png"></p><span id="more"></span><p>更重要的是，我们在商店里内置了大量预配置的系统镜像。你不需要到处找 ISO 文件，不需要手动配置，就像安装手机 App 一样点击安装，虚拟机就跑起来了。这才是真正的开箱即用。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/59ed09b8-f44c-4bbb-a138-fc25d40493c0.png" alt="12285a4a0e1edbb4ae5ac5a6a2af15a6.png" title="12285a4a0e1edbb4ae5ac5a6a2af15a6.png"></p><p>有意思的是，社区开发者还上传了群晖（Synology DSM）的虚拟机镜像。是的，你可以在懒猫微服里运行群晖系统——NAS 嵌套 NAS，这种玩法让你可以对比测试不同 NAS 系统的特性。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/7672206b-edb4-4195-861d-e5bd86315394.png" alt="a76b29f90ace0509948889804209a9a3.png" title="a76b29f90ace0509948889804209a9a3.png"></p><h4 id="虚拟化技术架构"><a href="#虚拟化技术架构" class="headerlink" title="虚拟化技术架构"></a>虚拟化技术架构</h4><p>说到虚拟化，我们需要理解两种根本不同的架构。</p><p><strong>Type 1 Hypervisor（裸金属虚拟化）</strong> 直接运行在物理硬件之上，Hypervisor 直接掌控硬件资源。这是性能、资源利用率、安全隔离性的最优解，也是企业级数据中心和云服务提供商的标准选择。VMware ESXi、Xen、KVM、Microsoft Hyper-V Server 都属于这一阵营。</p><p><strong>Type 2 Hypervisor（寄居虚拟化）</strong> 则运行在宿主操作系统之上，作为应用程序层存在。安装配置简单，易于使用，这是它的优势。性能？确实略逊于 Type 1，但对个人用户和开发测试场景来说，易用性和灵活性更重要。VMware Workstation、Oracle VirtualBox、Parallels Desktop 走的是这条路。</p><p><strong>那么懒猫微服选择了什么？</strong> 我们采用 Type 2 架构，但不止于此。团队基于精简的 Linux 内核进行了大量性能调优，减少虚拟化层的开销，让性能表现接近 Type 1。这意味着什么？你既能获得高性能的虚拟化能力，又无需复杂的裸金属部署和配置。这才是真正适合个人和小团队的技术路线。</p><h4 id="容器化技术实现"><a href="#容器化技术实现" class="headerlink" title="容器化技术实现"></a>容器化技术实现</h4><p>虚拟机很强大，但有时候你需要更轻量级的方案。这就是容器的价值所在。</p><p>懒猫微服基于 Docker 容器引擎，构建了三层容器管理架构。让我们逐层看看：</p><p><strong>系统级 Docker（System-level Container Runtime）</strong> 运行着懒猫微服的核心系统组件和基础服务。这一层对你透明，系统自动管理，采用资源隔离和安全沙箱机制，确保核心服务的稳定性不受任何用户操作影响。</p><p><strong>Playground Docker（开发测试环境）</strong> 这是你的实验场。支持 lzc-docker 和 Dockge 两种管理工具，你可以快速创建、销毁容器实例。想测试新版本数据库？想体验最新的开源项目？在 Playground 里随便折腾，完全隔离于生产环境，不会影响系统稳定性。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d4ce27ef-1a0d-40b9-aa52-854814584ca6.png" alt="image.png" title="image.png"></p><p><strong>商店 Docker（容器商店）</strong> 这里的应用都经过我们工程师的专门审核，支持版本更新和一键部署。涵盖了你能想到的几乎所有中间件和应用：数据库服务（MySQL、PostgreSQL、MongoDB、Redis、Milvus 向量数据库）、搜索引擎（Elasticsearch、Meilisearch）、Web 服务器（Nginx、Apache、Caddy）、数据库管理工具（Adminer、phpMyAdmin、MongoDB Compass）、AI 大语言模型平台（Ollama、Ollama WebUI）、工作流自动化（n8n、Dify）。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/32d576f1-a301-4bcd-84a2-d2d3262da6c6.png" alt="image.png" title="image.png"></p><p>虚拟机和容器，重量级隔离和轻量级部署，懒猫微服给了你完整的技术栈。虚拟机适合运行复杂应用和多租户场景，容器适合微服务架构和快速迭代。选择权在你手里。</p><p><strong>技术应用场景：</strong> 独立开发者可以利用懒猫微服搭建 Kubernetes 集群学习环境。传统方案需要购买多台云服务器（每月数百元成本），而在懒猫微服上只需创建 3-5 个 Linux 虚拟机，部署 K8s 控制平面和工作节点，即可在本地环境进行容器编排学习和实验，零云服务成本。对于需要 macOS 开发环境但缺少 Mac 硬件的开发者，也可以通过虚拟化 macOS 系统进行应用开发和测试。</p><h3 id="懒猫微服的-PaaS-实现：平台服务层"><a href="#懒猫微服的-PaaS-实现：平台服务层" class="headerlink" title="懒猫微服的 PaaS 实现：平台服务层"></a>懒猫微服的 PaaS 实现：平台服务层</h3><h4 id="PaaS-层的技术定位"><a href="#PaaS-层的技术定位" class="headerlink" title="PaaS 层的技术定位"></a>PaaS 层的技术定位</h4><p>PaaS 在 IaaS 之上提供了更高层次的抽象，封装了应用运行所需的平台环境和开发工具。在 PaaS 层，用户无需关心操作系统补丁、运行时环境配置、负载均衡策略等底层细节，只需专注于应用代码开发和业务逻辑实现。PaaS 通常提供预配置的运行时环境（Node.js、Python、Java、Go 等）、托管数据库服务（自动备份、主从复制、故障转移）、CI&#x2F;CD 流水线、API 网关、服务网格等能力。Heroku、Google App Engine、Azure App Service 是典型的 PaaS 产品。</p><h4 id="懒猫微服的平台服务能力"><a href="#懒猫微服的平台服务能力" class="headerlink" title="懒猫微服的平台服务能力"></a>懒猫微服的平台服务能力</h4><p>懒猫微服在 PaaS 层构建了完整的平台服务体系，涵盖了应用托管、数据存储、服务治理等多个维度。</p><p><strong>统一服务门户（Service Portal）</strong> 懒猫微服提供了平台级的服务管理能力，用于统一管理和访问 PaaS 层的各类技术服务（数据库、中间件、DevOps 工具等）。管理员可以通过服务门户进行服务配置、权限控制、资源监控等操作。此外，懒猫微服还支持静态网站托管服务，用户可以部署基于 HTML&#x2F;CSS&#x2F;JavaScript 的静态网站、单页应用（SPA）、技术文档站点等，无需配置 Web 服务器。</p><p><strong>对象存储服务（Object Storage）</strong> 懒猫微服集成了 MinIO 和 RustFS 企业级对象存储系统，提供与 AWS S3 兼容的 API 接口。MinIO 支持分布式部署、数据冗余、版本控制、生命周期管理等企业级特性。用户可以通过 S3 SDK 或 MinIO Client 进行对象存储操作，适用于图片&#x2F;视频存储、数据备份归档、大文件分发等场景。MinIO 的高性能架构可以充分利用本地存储的 I&#x2F;O 能力，提供低延迟的数据访问。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a385fa42-e045-41b5-a0b9-df8aeeecd961.png" alt="image.png" title="image.png"><br><strong>数据库服务（Database as a Service）</strong> 懒猫微服提供了多种托管数据库服务，包括关系型数据库（MySQL、PostgreSQL）、NoSQL 数据库（MongoDB、Redis）、时序数据库（InfluxDB）等。这些数据库服务支持一键部署、自动备份、性能监控等功能，用户无需手动配置数据库参数和优化策略。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/81869e3a-466b-4d7d-af44-ef47b018465c.png" alt="77e3f87ad300c8a9c6ecbc37b3fd43ec.png" title="77e3f87ad300c8a9c6ecbc37b3fd43ec.png"></p><p><strong>DevOps 工具链</strong> 懒猫微服集成了完整的 DevOps 工具栈，包括代码仓库（GitLab、Gitea）、CI&#x2F;CD 平台（Jenkins、GitLab Runner）、制品仓库（Nexus、Harbor）。开发团队可以在懒猫微服上搭建完整的软件交付流水线，实现代码提交、自动构建、测试、部署的全流程自动化。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d8874efa-3b8c-4c7a-be5f-5411da1f102b.png" alt="image.png" title="image.png"></p><p><strong>监控与可观测性（Observability Stack）</strong> 提供了 Prometheus + Grafana 监控方案、ELK（Elasticsearch + Logstash + Kibana）日志分析栈。用户可以根据需求实时监控系统资源使用情况、应用性能指标、业务数据趋势，快速定位和排查问题。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a1beb53b-5d75-4445-ac42-7414ec1c56fb.png" alt="image.png" title="image.png"><br><strong>技术应用场景：</strong> 小型技术团队（3-5人）需要搭建开发基础设施。传统方案需要运维工程师花费数天时间采购服务器、安装操作系统、配置网络、部署各类中间件。而在懒猫微服上，技术负责人只需在应用商店中依次安装：GitLab（代码管理）、Jenkins（CI&#x2F;CD）、PostgreSQL（业务数据库）、Redis（缓存）、Grafana（监控面板），配置导航页统一入口，整个过程可在 30 分钟内完成。团队成员通过导航页即可访问所有开发工具，大幅提升协作效率，节省了服务器采购和运维成本。</p><h3 id="懒猫微服的-SaaS-实现：应用软件生态"><a href="#懒猫微服的-SaaS-实现：应用软件生态" class="headerlink" title="懒猫微服的 SaaS 实现：应用软件生态"></a>懒猫微服的 SaaS 实现：应用软件生态</h3><h4 id="SaaS-层的服务模式"><a href="#SaaS-层的服务模式" class="headerlink" title="SaaS 层的服务模式"></a>SaaS 层的服务模式</h4><p>SaaS 是云计算的最上层，向最终用户提供开箱即用的应用软件。在 SaaS 模式下，软件的安装、配置、更新、备份、安全防护等工作全部由服务提供商负责，用户只需通过浏览器或客户端访问应用，专注于使用软件功能完成业务目标。SaaS 应用通常采用多租户架构（Multi-tenancy），支持按需订阅、弹性计费、数据隔离等特性。Gmail、Microsoft 365、Salesforce、Slack、Notion 都是典型的 SaaS 产品。</p><h4 id="懒猫微服的应用生态"><a href="#懒猫微服的应用生态" class="headerlink" title="懒猫微服的应用生态"></a>懒猫微服的应用生态</h4><p>懒猫微服在 SaaS 层构建了丰富的应用生态，涵盖了个人生产力、协作办公、智能家居、安全管理、娱乐休闲等多个领域。</p><p><strong>官方应用套件</strong> 懒猫微服提供了一系列官方开发的核心应用：懒猫网盘（基于 WebDAV 协议的私有云存储，支持跨平台同步）、相册管理（集成 AI 图像识别，支持人脸识别、场景分类、智能搜索）、Todo 清单（任务管理和 GTD 工作流）、智慧屏（信息聚合展示，支持自定义 Widget）。这些应用覆盖了个人用户的日常办公和生活需求，提供了完整的数据主权和隐私保护。</p><p><strong>导航页应用生态</strong> 在 SaaS 层，懒猫微服支持多种导航页应用，用户可以根据个人喜好选择和配置。这些导航页应用需要用户自行安装和配置，用于管理个人书签、快速访问常用网站和服务。包括 LazyCat 导航（官方导航页）、HomeNexus 导航（现代化服务导航面板）、T-Nav 导航网站（轻量级导航工具）、Van-nav（轻量级导航站）、Nav8（现代化个人导航页）、Sun-Panel（NAS 导航面板）、BookNav（基于 Flask 的书签导航）、Flare（个人导航、快速、美观的个人导航页）、Catsite（美观实用的个人导航）、Easy Gate（开箱即用的导航管理）、homepage（高度可定制的导航页）等。每个导航页应用都有不同的设计风格和功能特点，用户可以根据自己的审美和使用习惯进行选择和定制。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9869c282-98a5-465a-a5bf-7647cc6b01a3.png" alt="image.png" title="image.png"></p><p><strong>智能家居与自动化</strong> 懒猫微服支持 Home Assistant（开源智能家居平台，支持数千种智能设备接入，提供自动化场景编排、设备联动、语音控制等功能）。用户可以在懒猫微服上搭建完整的智能家居中枢，实现本地化的设备控制和数据隐私保护，无需依赖云服务商。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d4e70e60-36df-438f-9518-7508f203fc5c.png" alt="image.png" title="image.png"><br><strong>安全与隐私工具</strong> 集成了 Bitwarden（开源密码管理器，支持端到端加密、多设备同步、密码生成、安全审计等功能）。用户可以在懒猫微服上自建密码管理服务，完全掌控敏感凭证数据，避免第三方密码管理服务的潜在风险。</p><p><strong>社区开发者生态</strong> 懒猫微服拥有活跃的开发者社区，第三方开发者贡献了大量高质量应用。音乐播放器（支持本地音乐库管理和流媒体播放）、Rustdesk 远程桌面服务端（开源的 TeamViewer 替代方案，提供端到端加密的远程访问）、微信排版工具（Markdown 编辑器，支持微信公众号格式导出）、Office 办公套件（基于 OnlyOffice 或 Collabora Online 的在线文档编辑）等应用，极大扩展了懒猫微服的应用场景。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d687f715-95df-4387-8d47-ffbf9ee575ae.png" alt="image.png" title="image.png"></p><p><strong>游戏娱乐平台</strong> 懒猫微服还支持游戏应用的部署。包括 Web 小游戏、PSP 模拟器、甚至移植的 3A 游戏大作。用户可以在懒猫微服上构建个人游戏库，通过流式传输技术在不同设备上游玩。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a5c532d6-c779-4e13-ac2c-45fae12204cf.png" alt="image.png" title="image.png"></p><p><strong>技术应用场景：</strong> 摄影爱好者拥有数万张照片（总容量超过 500GB），需要一个私密且便捷的管理方案。使用公有云存储服务（如 iCloud、Google Photos）面临隐私风险和持续订阅成本。在懒猫微服上部署懒猫网盘和相册应用后，可以实现照片的自动备份（通过移动端 App 或 WebDAV 协议）、AI 智能分类（人脸识别、场景标签）、全文搜索、跨设备访问。数据完全存储在本地，无隐私泄露风险，也无需支付月度订阅费用。家庭成员可以通过权限管理共享相册，实现家庭照片的集中管理和协作浏览。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9cb9d521-976c-4e9a-a9ca-5ffa838f3882.png" alt="image.png" title="image.png"></p><h3 id="总结：属于你的私有云时代"><a href="#总结：属于你的私有云时代" class="headerlink" title="总结：属于你的私有云时代"></a>总结：属于你的私有云时代</h3><p>懒猫微服不仅仅是一个 NAS 存储设备，更是一个完整的云计算平台。它打破了云计算只属于大企业的刻板印象，让个人用户、独立开发者、小团队也能拥有媲美云服务商的基础设施能力。</p><p>从底层的虚拟化基础设施（IaaS），到中间层的平台服务（PaaS），再到上层的软件应用（SaaS），懒猫微服为用户提供了全栈的云计算解决方案。你不需要每月支付高昂的云服务费用，不需要担心数据隐私泄露，不需要受限于云服务商的各种限制。一台懒猫微服，就是你的私有云数据中心。</p><p>无论你是想搭建个人博客、学习新技术、开发应用、管理家庭数据，还是为小团队提供开发环境，懒猫微服都能满足你的需求。云计算不再遥不可及，它就在你的桌面上，触手可及。</p><p>这就是懒猫微服的愿景：让每个人都能拥有自己的云计算平台，让技术真正为生活和工作赋能。</p>]]></content>
    
    
    <summary type="html">懒猫微服不只是 NAS，它构建了从虚拟化到应用层的完整私有云生态，适合个人和小团队的云计算需求。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>装修日记</title>
    <link href="https://blog.no-claw.com/posts/a7fd58f1/"/>
    <id>https://blog.no-claw.com/posts/a7fd58f1/</id>
    <published>2025-12-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li>卫浴能买九牧不要买四季沐歌，毕竟不是做一条产品线起家的。四季沐歌虽然听着耳熟，但是最早是做太阳能的。</li><li>厨房的电闸要多留一些冗余，16A不太够用，烧水壶+冰箱+油烟机+饭锅 就小3000W了，如果再有电锅啥的可能直接跳闸。</li><li>三个连排的插座额定功率也是10A，所以也没有那么方便，大用电器容器超。<span id="more"></span></li></ol>]]></content>
    
    
    <summary type="html">装修踩坑记录，卫浴品牌选择、厨房电闸预留、插座功率等实用经验分享。</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>Python小抄们</title>
    <link href="https://blog.no-claw.com/posts/11ca0dba/"/>
    <id>https://blog.no-claw.com/posts/11ca0dba/</id>
    <published>2025-12-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>git clone –depth 1 是浅克隆（shallow clone），只拉取最近一次提交的历史记录，而不是整个仓库的完整历史。</p><p>主要好处：</p><p>下载速度快，节省带宽和磁盘空间<br>适合只想获取最新代码、不关心历史记录的场景<br>比如一个有几千次提交的大仓库，完整克隆可能要几百 MB，用 –depth 1 可能只需要几十 MB。</p><p>常见用法：</p><h1 id="只拉最新一次提交"><a href="#只拉最新一次提交" class="headerlink" title="只拉最新一次提交"></a>只拉最新一次提交</h1><p>git clone –depth 1 <a href="https://github.com/user/repo.git">https://github.com/user/repo.git</a></p><h1 id="拉最近-10-次提交"><a href="#拉最近-10-次提交" class="headerlink" title="拉最近 10 次提交"></a>拉最近 10 次提交</h1><p>git clone –depth 10 <a href="https://github.com/user/repo.git">https://github.com/user/repo.git</a><br>缺点是你看不到完整的 commit 历史，也没法切换到旧的分支或标签。如果后续需要完整历史，可以用 git fetch –unshallow 补全。</p>]]></content>
    
    
    <summary type="html">开发常用命令速查，涵盖 Git 浅克隆、Python 技巧等实用小抄合集。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="Python" scheme="https://blog.no-claw.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>告别 DSL！我用 MCP 让 AI 帮我操作 Easysearch</title>
    <link href="https://blog.no-claw.com/posts/9ca6531a/"/>
    <id>https://blog.no-claw.com/posts/9ca6531a/</id>
    <published>2025-12-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>还在为记不住 Elasticsearch DSL 语法发愁？<code>match</code> 和 <code>term</code> 傻傻分不清？聚合查询写半天还报错？</p><p>今天教你用 MCP（Model Context Protocol）封装 Easysearch，让 AI 帮你操作集群。从此告别 DSL，用自然语言就能完成搜索、写入、聚合统计！</p><h3 id="效果演示"><a href="#效果演示" class="headerlink" title="效果演示"></a>效果演示</h3><p>先看看最终效果。以下是我和 Kiro 的真实对话记录：</p><p><strong>我</strong>：集群健康吗？</p><p><strong>Kiro</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;infinilabs&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;green&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;number_of_nodes&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_shards&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_shards_percent_as_number&quot;</span><span class="punctuation">:</span> <span class="number">100.0</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>🟢 集群很健康！状态 green，1 个节点，2 个活跃分片，100% 健康。</p><span id="more"></span><p><strong>我</strong>：创建一个产品索引，有名称、价格、分类、描述字段</p><p><strong>Kiro</strong>：</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;acknowledged&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;shards_acknowledged&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;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;products&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>产品索引创建成功！字段结构：</p><table><thead><tr><th>字段</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td>name</td><td>text</td><td>产品名称，支持中文分词搜索</td></tr><tr><td>price</td><td>integer</td><td>价格</td></tr><tr><td>category</td><td>keyword</td><td>分类，精确匹配</td></tr><tr><td>description</td><td>text</td><td>描述，支持中文分词搜索</td></tr></tbody></table><p><strong>我</strong>：写入 5 个苹果产品</p><p><strong>Kiro</strong>：</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;took&quot;</span><span class="punctuation">:</span> <span class="number">23</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;errors&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="attr">&quot;items_count&quot;</span><span class="punctuation">:</span> <span class="number">5</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>5 个产品写入成功！</p><p><strong>我</strong>：搜一下带芯片的产品</p><p><strong>Kiro</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;took_ms&quot;</span><span class="punctuation">:</span> <span class="number">37</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;iPad Air&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">4799</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;平板&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;M1芯片平板电脑&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;MacBook Pro 14&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">16999</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;电脑&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;M3 Pro芯片笔记本电脑&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;iPhone 15 Pro&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">8999</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;手机&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;苹果最新旗舰手机，A17芯片&quot;</span><span class="punctuation">&#125;</span><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>找到 3 个带芯片的产品！</p><p><strong>我</strong>：5000 以下的有哪些？</p><p><strong>Kiro</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;AirPods Pro 2&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">1899</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;耳机&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;iPad Air&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">4799</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;平板&quot;</span><span class="punctuation">&#125;</span><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>5000 以下有 2 个：AirPods Pro 2（¥1899）和 iPad Air（¥4799）</p><p><strong>我</strong>：按分类统计数量</p><p><strong>Kiro</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;buckets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;平板&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;手机&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;手表&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;电脑&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;耳机&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><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><strong>我</strong>：平均价格多少？</p><p><strong>Kiro</strong>：</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;value&quot;</span><span class="punctuation">:</span> <span class="number">7839.0</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>平均价格 ¥7839</p><p>全程不用写一行 DSL！我只说人话，Kiro 自动选择合适的工具、生成查询、返回结果。</p><h3 id="什么是-MCP？"><a href="#什么是-MCP？" class="headerlink" title="什么是 MCP？"></a>什么是 MCP？</h3><p>MCP（Model Context Protocol）是一个让 AI 调用外部工具的协议。简单说：</p><ol><li>你定义一些”工具”（函数）</li><li>AI 根据用户意图选择合适的工具</li><li>AI 自动填参数、调用、返回结果</li></ol><p>把 Easysearch 的操作封装成 MCP 工具，AI 就能帮你操作集群了。</p><h3 id="为什么用-FastMCP？"><a href="#为什么用-FastMCP？" class="headerlink" title="为什么用 FastMCP？"></a>为什么用 FastMCP？</h3><p>FastMCP 是 MCP 官方提供的 Python 高级封装，让你用最少的代码写 MCP Server。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line">mcp = FastMCP(<span class="string">&quot;easysearch&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cluster_health</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">    <span class="comment"># 实现逻辑...</span></span><br><span class="line">    <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><p>FastMCP 的优势：</p><ul><li><strong>装饰器语法</strong> - <code>@mcp.tool()</code> 一行搞定工具注册</li><li><strong>自动生成 Schema</strong> - 根据函数签名和类型注解自动生成参数定义</li><li><strong>docstring 即描述</strong> - 函数文档字符串自动变成工具描述，AI 根据这个选择调用哪个工具</li><li><strong>同步函数支持</strong> - 不用写 async&#x2F;await</li><li><strong>返回值自动序列化</strong> - 直接 return dict，不用手动包装成 JSON</li></ul><h3 id="开始封装"><a href="#开始封装" class="headerlink" title="开始封装"></a>开始封装</h3><h4 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">easysearch-mcp-server/</span><br><span class="line">├── easysearch_mcp.py   # MCP 服务器代码</span><br><span class="line">├── pyproject.toml      # 项目配置</span><br><span class="line">└── README.md</span><br></pre></td></tr></table></figure><h4 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h4><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">pip install mcp httpx</span><br></pre></td></tr></table></figure><h4 id="核心代码"><a href="#核心代码" class="headerlink" title="核心代码"></a>核心代码</h4><p>创建 <code>easysearch_mcp.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">Easysearch MCP Server</span></span><br><span class="line"><span class="string">让 AI Agent 能够操作 Easysearch（兼容 Elasticsearch API）</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="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Any</span></span><br><span class="line"><span class="keyword">import</span> httpx</span><br><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 MCP Server</span></span><br><span class="line">mcp = FastMCP(<span class="string">&quot;easysearch&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置 - 从环境变量读取</span></span><br><span class="line">EASYSEARCH_URL = os.getenv(<span class="string">&quot;EASYSEARCH_URL&quot;</span>, <span class="string">&quot;http://localhost:9200&quot;</span>)</span><br><span class="line">EASYSEARCH_USER = os.getenv(<span class="string">&quot;EASYSEARCH_USER&quot;</span>, <span class="string">&quot;admin&quot;</span>)</span><br><span class="line">EASYSEARCH_PASSWORD = os.getenv(<span class="string">&quot;EASYSEARCH_PASSWORD&quot;</span>, <span class="string">&quot;admin&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_client</span>() -&gt; httpx.Client:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;创建 HTTP 客户端&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> httpx.Client(</span><br><span class="line">        base_url=EASYSEARCH_URL,</span><br><span class="line">        auth=(EASYSEARCH_USER, EASYSEARCH_PASSWORD),</span><br><span class="line">        verify=<span class="literal">False</span>,  <span class="comment"># 如果用 HTTPS 自签名证书</span></span><br><span class="line">        timeout=<span class="number">30.0</span></span><br><span class="line">    )</span><br></pre></td></tr></table></figure><h4 id="封装集群信息工具"><a href="#封装集群信息工具" class="headerlink" title="封装集群信息工具"></a>封装集群信息工具</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cluster_health</span>() -&gt; <span class="built_in">dict</span>:</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">    返回集群名称、状态（green/yellow/red）、节点数、分片数等信息</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cluster/health&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cluster_stats</span>() -&gt; <span class="built_in">dict</span>:</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">    包括文档数、存储大小、索引数量等</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cluster/stats&quot;</span>)</span><br><span class="line">        data = r.json()</span><br><span class="line">        <span class="comment"># 精简返回，避免太长</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;cluster_name&quot;</span>: data.get(<span class="string">&quot;cluster_name&quot;</span>),</span><br><span class="line">            <span class="string">&quot;status&quot;</span>: data.get(<span class="string">&quot;status&quot;</span>),</span><br><span class="line">            <span class="string">&quot;nodes&quot;</span>: data.get(<span class="string">&quot;nodes&quot;</span>, &#123;&#125;).get(<span class="string">&quot;count&quot;</span>, &#123;&#125;),</span><br><span class="line">            <span class="string">&quot;indices&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;count&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;count&quot;</span>),</span><br><span class="line">                <span class="string">&quot;docs&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;docs&quot;</span>, &#123;&#125;),</span><br><span class="line">                <span class="string">&quot;store_size&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;store&quot;</span>, &#123;&#125;).get(<span class="string">&quot;size_in_bytes&quot;</span>)</span><br><span class="line">            &#125;</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 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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">list_indices</span>() -&gt; <span class="built_in">list</span>:</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">    返回索引名称、文档数、存储大小、健康状态</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cat/indices?format=json&quot;</span>)</span><br><span class="line">        indices = r.json()</span><br><span class="line">        <span class="keyword">return</span> [&#123;</span><br><span class="line">            <span class="string">&quot;index&quot;</span>: idx.get(<span class="string">&quot;index&quot;</span>),</span><br><span class="line">            <span class="string">&quot;health&quot;</span>: idx.get(<span class="string">&quot;health&quot;</span>),</span><br><span class="line">            <span class="string">&quot;status&quot;</span>: idx.get(<span class="string">&quot;status&quot;</span>),</span><br><span class="line">            <span class="string">&quot;docs_count&quot;</span>: idx.get(<span class="string">&quot;docs.count&quot;</span>),</span><br><span class="line">            <span class="string">&quot;store_size&quot;</span>: idx.get(<span class="string">&quot;store.size&quot;</span>)</span><br><span class="line">        &#125; <span class="keyword">for</span> idx <span class="keyword">in</span> indices <span class="keyword">if</span> <span class="keyword">not</span> idx.get(<span class="string">&quot;index&quot;</span>, <span class="string">&quot;&quot;</span>).startswith(<span class="string">&quot;.&quot;</span>)]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_index_mapping</span>(<span class="params">index: <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;</span></span><br><span class="line"><span class="string">    获取索引的字段映射（schema）</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">        index: 索引名称</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_mapping&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_index</span>(<span class="params">index: <span class="built_in">str</span>, mappings: <span class="built_in">dict</span> = <span class="literal">None</span>, settings: <span class="built_in">dict</span> = <span class="literal">None</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        mappings: 字段映射定义（可选）</span></span><br><span class="line"><span class="string">        settings: 索引设置如分片数（可选）</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    示例 mappings:</span></span><br><span class="line"><span class="string">        &#123;&quot;properties&quot;: &#123;&quot;title&quot;: &#123;&quot;type&quot;: &quot;text&quot;&#125;, &quot;count&quot;: &#123;&quot;type&quot;: &quot;integer&quot;&#125;&#125;&#125;</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    body = &#123;&#125;</span><br><span class="line">    <span class="keyword">if</span> mappings:</span><br><span class="line">        body[<span class="string">&quot;mappings&quot;</span>] = mappings</span><br><span class="line">    <span class="keyword">if</span> settings:</span><br><span class="line">        body[<span class="string">&quot;settings&quot;</span>] = settings</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.put(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>&quot;</span>, json=body <span class="keyword">if</span> body <span class="keyword">else</span> <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete_index</span>(<span class="params">index: <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;</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">    参数:</span></span><br><span class="line"><span class="string">        index: 要删除的索引名称</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.delete(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br></pre></td></tr></table></figure><h4 id="封装文档操作工具"><a href="#封装文档操作工具" class="headerlink" title="封装文档操作工具"></a>封装文档操作工具</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index_document</span>(<span class="params">index: <span class="built_in">str</span>, document: <span class="built_in">dict</span>, doc_id: <span class="built_in">str</span> = <span class="literal">None</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        document: 文档内容（JSON 对象）</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID（可选，不传则自动生成）</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        <span class="keyword">if</span> doc_id:</span><br><span class="line">            r = client.put(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>, json=document)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc&quot;</span>, json=document)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_document</span>(<span class="params">index: <span class="built_in">str</span>, doc_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;</span></span><br><span class="line"><span class="string">    根据 ID 获取文档</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">        index: 索引名称</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete_document</span>(<span class="params">index: <span class="built_in">str</span>, doc_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;</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">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.delete(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bulk_index</span>(<span class="params">index: <span class="built_in">str</span>, documents: <span class="built_in">list</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        documents: 文档列表</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    lines = []</span><br><span class="line">    <span class="keyword">for</span> doc <span class="keyword">in</span> documents:</span><br><span class="line">        lines.append(json.dumps(&#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: index&#125;&#125;))</span><br><span class="line">        lines.append(json.dumps(doc))</span><br><span class="line">    body = <span class="string">&quot;\n&quot;</span>.join(lines) + <span class="string">&quot;\n&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(</span><br><span class="line">            <span class="string">&quot;/_bulk&quot;</span>,</span><br><span class="line">            content=body,</span><br><span class="line">            headers=&#123;<span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-ndjson&quot;</span>&#125;</span><br><span class="line">        )</span><br><span class="line">        result = r.json()</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;took&quot;</span>: result.get(<span class="string">&quot;took&quot;</span>),</span><br><span class="line">            <span class="string">&quot;errors&quot;</span>: result.get(<span class="string">&quot;errors&quot;</span>),</span><br><span class="line">            <span class="string">&quot;items_count&quot;</span>: <span class="built_in">len</span>(result.get(<span class="string">&quot;items&quot;</span>, []))</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><h4 id="封装搜索工具（重点！）"><a href="#封装搜索工具（重点！）" class="headerlink" title="封装搜索工具（重点！）"></a>封装搜索工具（重点！）</h4><p>这是最有价值的部分，让 AI 帮你写 DSL：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search</span>(<span class="params">index: <span class="built_in">str</span>, query: <span class="built_in">dict</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称（可用 * 搜索所有索引）</span></span><br><span class="line"><span class="string">        query: Elasticsearch DSL 查询</span></span><br><span class="line"><span class="string">        size: 返回结果数量，默认 10</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">        search(&quot;products&quot;, &#123;&quot;match&quot;: &#123;&quot;name&quot;: &quot;iPhone&quot;&#125;&#125;)</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">        search(&quot;products&quot;, &#123;&quot;term&quot;: &#123;&quot;status&quot;: &quot;active&quot;&#125;&#125;)</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">        search(&quot;products&quot;, &#123;&quot;range&quot;: &#123;&quot;price&quot;: &#123;&quot;gte&quot;: 100, &quot;lte&quot;: 500&#125;&#125;&#125;)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;query&quot;</span>: query,</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: size</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_search&quot;</span>, json=body)</span><br><span class="line">        result = r.json()</span><br><span class="line">        </span><br><span class="line">        hits = result.get(<span class="string">&quot;hits&quot;</span>, &#123;&#125;)</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;total&quot;</span>: hits.get(<span class="string">&quot;total&quot;</span>, &#123;&#125;).get(<span class="string">&quot;value&quot;</span>, <span class="number">0</span>),</span><br><span class="line">            <span class="string">&quot;took_ms&quot;</span>: result.get(<span class="string">&quot;took&quot;</span>),</span><br><span class="line">            <span class="string">&quot;hits&quot;</span>: [&#123;</span><br><span class="line">                <span class="string">&quot;_id&quot;</span>: h.get(<span class="string">&quot;_id&quot;</span>),</span><br><span class="line">                <span class="string">&quot;_score&quot;</span>: h.get(<span class="string">&quot;_score&quot;</span>),</span><br><span class="line">                <span class="string">&quot;_source&quot;</span>: h.get(<span class="string">&quot;_source&quot;</span>)</span><br><span class="line">            &#125; <span class="keyword">for</span> h <span class="keyword">in</span> hits.get(<span class="string">&quot;hits&quot;</span>, [])]</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search_simple</span>(<span class="params">index: <span class="built_in">str</span>, keyword: <span class="built_in">str</span>, field: <span class="built_in">str</span> = <span class="string">&quot;_all&quot;</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    简单关键词搜索（适合不熟悉 DSL 的场景）</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">        index: 索引名称</span></span><br><span class="line"><span class="string">        keyword: 搜索关键词</span></span><br><span class="line"><span class="string">        field: 搜索字段，默认全字段</span></span><br><span class="line"><span class="string">        size: 返回数量</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> field == <span class="string">&quot;_all&quot;</span>:</span><br><span class="line">        query = &#123;<span class="string">&quot;query_string&quot;</span>: &#123;<span class="string">&quot;query&quot;</span>: keyword&#125;&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        query = &#123;<span class="string">&quot;match&quot;</span>: &#123;field: keyword&#125;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> search(index, query, size)</span><br></pre></td></tr></table></figure><h4 id="封装聚合统计工具"><a href="#封装聚合统计工具" class="headerlink" title="封装聚合统计工具"></a>封装聚合统计工具</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aggregate</span>(<span class="params">index: <span class="built_in">str</span>, field: <span class="built_in">str</span>, agg_type: <span class="built_in">str</span> = <span class="string">&quot;terms&quot;</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        field: 聚合字段</span></span><br><span class="line"><span class="string">        agg_type: 聚合类型 - terms(分组计数), avg, sum, min, max, cardinality(去重计数)</span></span><br><span class="line"><span class="string">        size: 返回桶数量（仅 terms 有效）</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> agg_type == <span class="string">&quot;terms&quot;</span>:</span><br><span class="line">        agg_body = &#123;<span class="string">&quot;terms&quot;</span>: &#123;<span class="string">&quot;field&quot;</span>: field, <span class="string">&quot;size&quot;</span>: size&#125;&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        agg_body = &#123;agg_type: &#123;<span class="string">&quot;field&quot;</span>: field&#125;&#125;</span><br><span class="line">    </span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="string">&quot;aggs&quot;</span>: &#123;<span class="string">&quot;result&quot;</span>: agg_body&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_search&quot;</span>, json=body)</span><br><span class="line">        result = r.json()</span><br><span class="line">        </span><br><span class="line">        agg_result = result.get(<span class="string">&quot;aggregations&quot;</span>, &#123;&#125;).get(<span class="string">&quot;result&quot;</span>, &#123;&#125;)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> agg_type == <span class="string">&quot;terms&quot;</span>:</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                <span class="string">&quot;buckets&quot;</span>: [&#123;</span><br><span class="line">                    <span class="string">&quot;key&quot;</span>: b.get(<span class="string">&quot;key&quot;</span>),</span><br><span class="line">                    <span class="string">&quot;count&quot;</span>: b.get(<span class="string">&quot;doc_count&quot;</span>)</span><br><span class="line">                &#125; <span class="keyword">for</span> b <span class="keyword">in</span> agg_result.get(<span class="string">&quot;buckets&quot;</span>, [])]</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;value&quot;</span>: agg_result.get(<span class="string">&quot;value&quot;</span>)&#125;</span><br></pre></td></tr></table></figure><h4 id="启动入口"><a href="#启动入口" class="headerlink" title="启动入口"></a>启动入口</h4><figure class="highlight python"><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="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    mcp.run()</span><br></pre></td></tr></table></figure><h3 id="配置-Kiro"><a href="#配置-Kiro" class="headerlink" title="配置 Kiro"></a>配置 Kiro</h3><p>在项目根目录创建 <code>.kiro/settings/mcp.json</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;easysearch&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python&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 class="string">&quot;/path/to/easysearch_mcp.py&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_URL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://localhost:9200&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_USER&quot;</span><span class="punctuation">:</span> <span class="string">&quot;admin&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_PASSWORD&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-password&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;autoApprove&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;cluster_health&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;list_indices&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;cluster_stats&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;create_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;bulk_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;get_index_mapping&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;search_simple&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;search&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;aggregate&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;index_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;get_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;delete_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;delete_index&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><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>配置说明：</p><ul><li><code>command</code>: Python 解释器路径，建议用绝对路径如 <code>/usr/bin/python3</code></li><li><code>args</code>: MCP 服务器脚本的绝对路径</li><li><code>env</code>: 环境变量，配置 Easysearch 连接信息</li><li><code>autoApprove</code>: 自动批准的工具列表。默认情况下 Kiro 调用 MCP 工具前会弹窗让你确认，把工具名加到这个列表里就会自动执行，不用每次点确认，对话更流畅</li></ul><p>重新加载 MCP：<code>Cmd+Shift+P</code> → 搜索 “Reconnect MCP Servers” 执行即可。</p><p>连接成功后，在 Kiro 左侧面板的 “MCP Servers” 视图里可以看到 easysearch 显示为绿色。</p><h3 id="更多对话示例"><a href="#更多对话示例" class="headerlink" title="更多对话示例"></a>更多对话示例</h3><p>继续看几个实际操作：</p><p><strong>我</strong>：有哪些索引？</p><p><strong>Kiro</strong>：（调用 list_indices）</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;products&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;health&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yellow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;open&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;docs_count&quot;</span><span class="punctuation">:</span> <span class="string">&quot;5&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;store_size&quot;</span><span class="punctuation">:</span> <span class="string">&quot;5.7kb&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>目前有 1 个索引 products，包含 5 个文档。</p><p><strong>我</strong>：products 索引有什么字段？</p><p><strong>Kiro</strong>：（调用 get_index_mapping）</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;products&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integer&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>products 索引有 4 个字段：name、price、category、description。</p><p><strong>我</strong>：集群有多少文档？</p><p><strong>Kiro</strong>：（调用 cluster_stats）</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;infinilabs&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;green&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;docs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">13</span><span class="punctuation">,</span> <span class="attr">&quot;deleted&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;store_size&quot;</span><span class="punctuation">:</span> <span class="number">52841</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><p>集群共有 3 个索引，13 个文档，存储大小约 52KB。</p><p><strong>我</strong>：写入单个文档</p><p><strong>Kiro</strong>：（调用 index_document）</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;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;products&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;magsafe-001&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="string">&quot;created&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>我</strong>：查一下 ID 是 magsafe-001 的文档</p><p><strong>Kiro</strong>：（调用 get_document）</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></pre></td><td class="code"><pre><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="string">&quot;magsafe-001&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;found&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;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;配件&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MagSafe 磁吸无线充电器&quot;</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;MagSafe 充电器&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">329</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><p><strong>我</strong>：搜一下手机分类的产品</p><p><strong>Kiro</strong>：（调用 search，生成 term 查询）</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="attr">&quot;_source&quot;</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;iPhone 15 Pro&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="number">8999</span><span class="punctuation">,</span> <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;手机&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;苹果最新旗舰手机，A17芯片&quot;</span><span class="punctuation">&#125;</span><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><strong>我</strong>：价格最高的是哪个？</p><p><strong>Kiro</strong>：（调用 aggregate，agg_type&#x3D;max）</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;value&quot;</span><span class="punctuation">:</span> <span class="number">16999.0</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>最高价格是 ¥16999（MacBook Pro 14）</p><p><strong>我</strong>：价格最低呢？</p><p><strong>Kiro</strong>：（调用 aggregate，agg_type&#x3D;min）</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;value&quot;</span><span class="punctuation">:</span> <span class="number">1899.0</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>最低价格是 ¥1899（AirPods Pro 2）</p><p><strong>我</strong>：算一下总价</p><p><strong>Kiro</strong>：（调用 aggregate，agg_type&#x3D;sum）</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;value&quot;</span><span class="punctuation">:</span> <span class="number">39195.0</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>5 个产品总价 ¥39195</p><p><strong>我</strong>：删掉这个文档</p><p><strong>Kiro</strong>：（调用 delete_document）</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;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;magsafe-001&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deleted&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>我</strong>：删掉 products 索引</p><p><strong>Kiro</strong>：（调用 delete_index）</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;acknowledged&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>索引已删除！</p><h3 id="工具清单"><a href="#工具清单" class="headerlink" title="工具清单"></a>工具清单</h3><p>封装完成后，AI 可以使用这 12 个工具：</p><table><thead><tr><th>工具</th><th>功能</th><th>典型用法</th></tr></thead><tbody><tr><td><code>cluster_health</code></td><td>集群健康状态</td><td>“集群状态怎么样”</td></tr><tr><td><code>cluster_stats</code></td><td>集群统计</td><td>“有多少文档”</td></tr><tr><td><code>list_indices</code></td><td>列出索引</td><td>“有哪些索引”</td></tr><tr><td><code>get_index_mapping</code></td><td>查看字段结构</td><td>“products 索引有什么字段”</td></tr><tr><td><code>create_index</code></td><td>创建索引</td><td>“创建一个用户索引”</td></tr><tr><td><code>delete_index</code></td><td>删除索引</td><td>“删掉 test 索引”</td></tr><tr><td><code>index_document</code></td><td>写入文档</td><td>“添加一个产品”</td></tr><tr><td><code>get_document</code></td><td>获取文档</td><td>“查一下 ID 是 xxx 的文档”</td></tr><tr><td><code>delete_document</code></td><td>删除文档</td><td>“删掉这个文档”</td></tr><tr><td><code>bulk_index</code></td><td>批量写入</td><td>“导入这批数据”</td></tr><tr><td><code>search</code></td><td>DSL 搜索</td><td>“价格 1000-5000 的产品”</td></tr><tr><td><code>search_simple</code></td><td>关键词搜索</td><td>“搜一下 iPhone”</td></tr><tr><td><code>aggregate</code></td><td>聚合统计</td><td>“按分类统计”</td></tr></tbody></table><h3 id="设计要点"><a href="#设计要点" class="headerlink" title="设计要点"></a>设计要点</h3><h4 id="1-工具描述要清晰"><a href="#1-工具描述要清晰" class="headerlink" title="1. 工具描述要清晰"></a>1. 工具描述要清晰</h4><p>AI 根据工具描述选择调用哪个，描述写得好，AI 选得准：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search_simple</span>(<span class="params">index: <span class="built_in">str</span>, keyword: <span class="built_in">str</span>, ...</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    简单关键词搜索（适合不熟悉 DSL 的场景）  # 说明用途</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">        index: 索引名称</span></span><br><span class="line"><span class="string">        keyword: 搜索关键词  # 参数说明</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br></pre></td></tr></table></figure><h4 id="2-返回结果要精简"><a href="#2-返回结果要精简" class="headerlink" title="2. 返回结果要精简"></a>2. 返回结果要精简</h4><p>Easysearch 原始返回包含大量元数据，动辄几百行。直接返回给 AI 会占用太多 token，也会干扰理解。精简后只保留关键信息：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="string">&quot;total&quot;</span>: hits.get(<span class="string">&quot;total&quot;</span>, &#123;&#125;).get(<span class="string">&quot;value&quot;</span>, <span class="number">0</span>),</span><br><span class="line">    <span class="string">&quot;took_ms&quot;</span>: result.get(<span class="string">&quot;took&quot;</span>),</span><br><span class="line">    <span class="string">&quot;hits&quot;</span>: [&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span>: h.get(<span class="string">&quot;_id&quot;</span>),</span><br><span class="line">        <span class="string">&quot;_score&quot;</span>: h.get(<span class="string">&quot;_score&quot;</span>),</span><br><span class="line">        <span class="string">&quot;_source&quot;</span>: h.get(<span class="string">&quot;_source&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">for</span> h <span class="keyword">in</span> hits.get(<span class="string">&quot;hits&quot;</span>, [])]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-提供简化版工具"><a href="#3-提供简化版工具" class="headerlink" title="3. 提供简化版工具"></a>3. 提供简化版工具</h4><p><code>search</code> 需要写 DSL，<code>search_simple</code> 只要关键词。AI 会根据场景选择：</p><ul><li>用户说”搜 iPhone” → 用 <code>search_simple</code></li><li>用户说”价格 1000-5000” → 用 <code>search</code> 生成 range 查询</li></ul><h3 id="扩展思路"><a href="#扩展思路" class="headerlink" title="扩展思路"></a>扩展思路</h3><p>这个 MCP 还可以继续扩展：</p><ol><li><strong>添加更多搜索类型</strong>：bool 组合查询、fuzzy 模糊搜索、highlight 高亮</li><li><strong>索引管理</strong>：reindex、别名管理、模板管理</li><li><strong>集群运维</strong>：节点信息、分片分配、慢查询日志</li><li><strong>数据导入导出</strong>：从 CSV&#x2F;JSON 文件批量导入</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过 MCP 封装 Easysearch：</p><ol><li><strong>告别 DSL 记忆负担</strong> - AI 帮你生成查询语句</li><li><strong>自然语言交互</strong> - 说人话就能操作集群</li><li><strong>降低使用门槛</strong> - 不懂 ES 的人也能用</li><li><strong>提高效率</strong> - 复杂查询秒出结果</li></ol><p>完整代码已开源，拿去用吧！</p><h3 id="附录：完整源码"><a href="#附录：完整源码" class="headerlink" title="附录：完整源码"></a>附录：完整源码</h3><h4 id="Kiro-MCP-配置文件"><a href="#Kiro-MCP-配置文件" class="headerlink" title="Kiro MCP 配置文件"></a>Kiro MCP 配置文件</h4><p><code>.kiro/settings/mcp.json</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;easysearch&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python&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 class="string">&quot;/path/to/easysearch_mcp.py&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_URL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://localhost:9200&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_USER&quot;</span><span class="punctuation">:</span> <span class="string">&quot;admin&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;EASYSEARCH_PASSWORD&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-password&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;autoApprove&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;cluster_health&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;list_indices&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;cluster_stats&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;create_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;bulk_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;get_index_mapping&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;search_simple&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;search&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;aggregate&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;index_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;get_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;delete_document&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;delete_index&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><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="MCP-Server-完整源码"><a href="#MCP-Server-完整源码" class="headerlink" title="MCP Server 完整源码"></a>MCP Server 完整源码</h4><p><code>easysearch_mcp.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">Easysearch MCP Server</span></span><br><span class="line"><span class="string">让 AI Agent 能够操作 Easysearch（兼容 Elasticsearch API）</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="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Any</span></span><br><span class="line"><span class="keyword">import</span> httpx</span><br><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 MCP Server</span></span><br><span class="line">mcp = FastMCP(<span class="string">&quot;easysearch&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置 - 从环境变量读取</span></span><br><span class="line">EASYSEARCH_URL = os.getenv(<span class="string">&quot;EASYSEARCH_URL&quot;</span>, <span class="string">&quot;http://localhost:9200&quot;</span>)</span><br><span class="line">EASYSEARCH_USER = os.getenv(<span class="string">&quot;EASYSEARCH_USER&quot;</span>, <span class="string">&quot;admin&quot;</span>)</span><br><span class="line">EASYSEARCH_PASSWORD = os.getenv(<span class="string">&quot;EASYSEARCH_PASSWORD&quot;</span>, <span class="string">&quot;admin&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_client</span>() -&gt; httpx.Client:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;创建 HTTP 客户端&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> httpx.Client(</span><br><span class="line">        base_url=EASYSEARCH_URL,</span><br><span class="line">        auth=(EASYSEARCH_USER, EASYSEARCH_PASSWORD),</span><br><span class="line">        verify=<span class="literal">False</span>,  <span class="comment"># 如果用 HTTPS 自签名证书</span></span><br><span class="line">        timeout=<span class="number">30.0</span></span><br><span class="line">    )</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="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cluster_health</span>() -&gt; <span class="built_in">dict</span>:</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">    返回集群名称、状态（green/yellow/red）、节点数、分片数等信息</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cluster/health&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cluster_stats</span>() -&gt; <span class="built_in">dict</span>:</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">    包括文档数、存储大小、索引数量等</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cluster/stats&quot;</span>)</span><br><span class="line">        data = r.json()</span><br><span class="line">        <span class="comment"># 精简返回，避免太长</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;cluster_name&quot;</span>: data.get(<span class="string">&quot;cluster_name&quot;</span>),</span><br><span class="line">            <span class="string">&quot;status&quot;</span>: data.get(<span class="string">&quot;status&quot;</span>),</span><br><span class="line">            <span class="string">&quot;nodes&quot;</span>: data.get(<span class="string">&quot;nodes&quot;</span>, &#123;&#125;).get(<span class="string">&quot;count&quot;</span>, &#123;&#125;),</span><br><span class="line">            <span class="string">&quot;indices&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;count&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;count&quot;</span>),</span><br><span class="line">                <span class="string">&quot;docs&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;docs&quot;</span>, &#123;&#125;),</span><br><span class="line">                <span class="string">&quot;store_size&quot;</span>: data.get(<span class="string">&quot;indices&quot;</span>, &#123;&#125;).get(<span class="string">&quot;store&quot;</span>, &#123;&#125;).get(<span class="string">&quot;size_in_bytes&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 class="comment"># ============ 索引操作 ============</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">list_indices</span>() -&gt; <span class="built_in">list</span>:</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">    返回索引名称、文档数、存储大小、健康状态</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">&quot;/_cat/indices?format=json&quot;</span>)</span><br><span class="line">        indices = r.json()</span><br><span class="line">        <span class="keyword">return</span> [&#123;</span><br><span class="line">            <span class="string">&quot;index&quot;</span>: idx.get(<span class="string">&quot;index&quot;</span>),</span><br><span class="line">            <span class="string">&quot;health&quot;</span>: idx.get(<span class="string">&quot;health&quot;</span>),</span><br><span class="line">            <span class="string">&quot;status&quot;</span>: idx.get(<span class="string">&quot;status&quot;</span>),</span><br><span class="line">            <span class="string">&quot;docs_count&quot;</span>: idx.get(<span class="string">&quot;docs.count&quot;</span>),</span><br><span class="line">            <span class="string">&quot;store_size&quot;</span>: idx.get(<span class="string">&quot;store.size&quot;</span>)</span><br><span class="line">        &#125; <span class="keyword">for</span> idx <span class="keyword">in</span> indices <span class="keyword">if</span> <span class="keyword">not</span> idx.get(<span class="string">&quot;index&quot;</span>, <span class="string">&quot;&quot;</span>).startswith(<span class="string">&quot;.&quot;</span>)]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_index_mapping</span>(<span class="params">index: <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;</span></span><br><span class="line"><span class="string">    获取索引的字段映射（schema）</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">        index: 索引名称</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_mapping&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_index</span>(<span class="params">index: <span class="built_in">str</span>, mappings: <span class="built_in">dict</span> = <span class="literal">None</span>, settings: <span class="built_in">dict</span> = <span class="literal">None</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        mappings: 字段映射定义（可选）</span></span><br><span class="line"><span class="string">        settings: 索引设置如分片数（可选）</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    示例 mappings:</span></span><br><span class="line"><span class="string">        &#123;&quot;properties&quot;: &#123;&quot;title&quot;: &#123;&quot;type&quot;: &quot;text&quot;&#125;, &quot;count&quot;: &#123;&quot;type&quot;: &quot;integer&quot;&#125;&#125;&#125;</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    body = &#123;&#125;</span><br><span class="line">    <span class="keyword">if</span> mappings:</span><br><span class="line">        body[<span class="string">&quot;mappings&quot;</span>] = mappings</span><br><span class="line">    <span class="keyword">if</span> settings:</span><br><span class="line">        body[<span class="string">&quot;settings&quot;</span>] = settings</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.put(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>&quot;</span>, json=body <span class="keyword">if</span> body <span class="keyword">else</span> <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete_index</span>(<span class="params">index: <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;</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">    参数:</span></span><br><span class="line"><span class="string">        index: 要删除的索引名称</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.delete(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</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="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index_document</span>(<span class="params">index: <span class="built_in">str</span>, document: <span class="built_in">dict</span>, doc_id: <span class="built_in">str</span> = <span class="literal">None</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        document: 文档内容（JSON 对象）</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID（可选，不传则自动生成）</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">        index_document(&quot;products&quot;, &#123;&quot;name&quot;: &quot;iPhone&quot;, &quot;price&quot;: 999&#125;)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        <span class="keyword">if</span> doc_id:</span><br><span class="line">            r = client.put(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>, json=document)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc&quot;</span>, json=document)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_document</span>(<span class="params">index: <span class="built_in">str</span>, doc_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;</span></span><br><span class="line"><span class="string">    根据 ID 获取文档</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">        index: 索引名称</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.get(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete_document</span>(<span class="params">index: <span class="built_in">str</span>, doc_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;</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">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        doc_id: 文档 ID</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.delete(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_doc/<span class="subst">&#123;doc_id&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bulk_index</span>(<span class="params">index: <span class="built_in">str</span>, documents: <span class="built_in">list</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        documents: 文档列表</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">        bulk_index(&quot;products&quot;, [&#123;&quot;name&quot;: &quot;A&quot;&#125;, &#123;&quot;name&quot;: &quot;B&quot;&#125;])</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># 构建 bulk 请求体</span></span><br><span class="line">    lines = []</span><br><span class="line">    <span class="keyword">for</span> doc <span class="keyword">in</span> documents:</span><br><span class="line">        lines.append(json.dumps(&#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: index&#125;&#125;))</span><br><span class="line">        lines.append(json.dumps(doc))</span><br><span class="line">    body = <span class="string">&quot;\n&quot;</span>.join(lines) + <span class="string">&quot;\n&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(</span><br><span class="line">            <span class="string">&quot;/_bulk&quot;</span>,</span><br><span class="line">            content=body,</span><br><span class="line">            headers=&#123;<span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-ndjson&quot;</span>&#125;</span><br><span class="line">        )</span><br><span class="line">        result = r.json()</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;took&quot;</span>: result.get(<span class="string">&quot;took&quot;</span>),</span><br><span class="line">            <span class="string">&quot;errors&quot;</span>: result.get(<span class="string">&quot;errors&quot;</span>),</span><br><span class="line">            <span class="string">&quot;items_count&quot;</span>: <span class="built_in">len</span>(result.get(<span class="string">&quot;items&quot;</span>, []))</span><br><span class="line">        &#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"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search</span>(<span class="params">index: <span class="built_in">str</span>, query: <span class="built_in">dict</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称（可用 * 搜索所有索引）</span></span><br><span class="line"><span class="string">        query: Elasticsearch DSL 查询</span></span><br><span class="line"><span class="string">        size: 返回结果数量，默认 10</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">        search(&quot;products&quot;, &#123;&quot;match&quot;: &#123;&quot;name&quot;: &quot;iPhone&quot;&#125;&#125;)</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">        search(&quot;products&quot;, &#123;&quot;term&quot;: &#123;&quot;status&quot;: &quot;active&quot;&#125;&#125;)</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">        search(&quot;products&quot;, &#123;&quot;range&quot;: &#123;&quot;price&quot;: &#123;&quot;gte&quot;: 100, &quot;lte&quot;: 500&#125;&#125;&#125;)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;query&quot;</span>: query,</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: size</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_search&quot;</span>, json=body)</span><br><span class="line">        result = r.json()</span><br><span class="line">        </span><br><span class="line">        hits = result.get(<span class="string">&quot;hits&quot;</span>, &#123;&#125;)</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;total&quot;</span>: hits.get(<span class="string">&quot;total&quot;</span>, &#123;&#125;).get(<span class="string">&quot;value&quot;</span>, <span class="number">0</span>),</span><br><span class="line">            <span class="string">&quot;took_ms&quot;</span>: result.get(<span class="string">&quot;took&quot;</span>),</span><br><span class="line">            <span class="string">&quot;hits&quot;</span>: [&#123;</span><br><span class="line">                <span class="string">&quot;_id&quot;</span>: h.get(<span class="string">&quot;_id&quot;</span>),</span><br><span class="line">                <span class="string">&quot;_score&quot;</span>: h.get(<span class="string">&quot;_score&quot;</span>),</span><br><span class="line">                <span class="string">&quot;_source&quot;</span>: h.get(<span class="string">&quot;_source&quot;</span>)</span><br><span class="line">            &#125; <span class="keyword">for</span> h <span class="keyword">in</span> hits.get(<span class="string">&quot;hits&quot;</span>, [])]</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search_simple</span>(<span class="params">index: <span class="built_in">str</span>, keyword: <span class="built_in">str</span>, field: <span class="built_in">str</span> = <span class="string">&quot;_all&quot;</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    简单关键词搜索（适合不熟悉 DSL 的场景）</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">        index: 索引名称</span></span><br><span class="line"><span class="string">        keyword: 搜索关键词</span></span><br><span class="line"><span class="string">        field: 搜索字段，默认全字段</span></span><br><span class="line"><span class="string">        size: 返回数量</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">        search_simple(&quot;products&quot;, &quot;iPhone&quot;)</span></span><br><span class="line"><span class="string">        search_simple(&quot;logs&quot;, &quot;error&quot;, field=&quot;message&quot;)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> field == <span class="string">&quot;_all&quot;</span>:</span><br><span class="line">        query = &#123;<span class="string">&quot;query_string&quot;</span>: &#123;<span class="string">&quot;query&quot;</span>: keyword&#125;&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        query = &#123;<span class="string">&quot;match&quot;</span>: &#123;field: keyword&#125;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> search(index, query, size)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aggregate</span>(<span class="params">index: <span class="built_in">str</span>, field: <span class="built_in">str</span>, agg_type: <span class="built_in">str</span> = <span class="string">&quot;terms&quot;</span>, size: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="built_in">dict</span>:</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">    </span></span><br><span class="line"><span class="string">    参数:</span></span><br><span class="line"><span class="string">        index: 索引名称</span></span><br><span class="line"><span class="string">        field: 聚合字段</span></span><br><span class="line"><span class="string">        agg_type: 聚合类型 - terms(分组计数), avg, sum, min, max, cardinality(去重计数)</span></span><br><span class="line"><span class="string">        size: 返回桶数量（仅 terms 有效）</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">        aggregate(&quot;orders&quot;, &quot;status&quot;, &quot;terms&quot;)  # 按状态分组计数</span></span><br><span class="line"><span class="string">        aggregate(&quot;orders&quot;, &quot;amount&quot;, &quot;avg&quot;)    # 计算平均金额</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> agg_type == <span class="string">&quot;terms&quot;</span>:</span><br><span class="line">        agg_body = &#123;<span class="string">&quot;terms&quot;</span>: &#123;<span class="string">&quot;field&quot;</span>: field, <span class="string">&quot;size&quot;</span>: size&#125;&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        agg_body = &#123;agg_type: &#123;<span class="string">&quot;field&quot;</span>: field&#125;&#125;</span><br><span class="line">    </span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="string">&quot;aggs&quot;</span>: &#123;<span class="string">&quot;result&quot;</span>: agg_body&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">with</span> get_client() <span class="keyword">as</span> client:</span><br><span class="line">        r = client.post(<span class="string">f&quot;/<span class="subst">&#123;index&#125;</span>/_search&quot;</span>, json=body)</span><br><span class="line">        result = r.json()</span><br><span class="line">        </span><br><span class="line">        agg_result = result.get(<span class="string">&quot;aggregations&quot;</span>, &#123;&#125;).get(<span class="string">&quot;result&quot;</span>, &#123;&#125;)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> agg_type == <span class="string">&quot;terms&quot;</span>:</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                <span class="string">&quot;buckets&quot;</span>: [&#123;</span><br><span class="line">                    <span class="string">&quot;key&quot;</span>: b.get(<span class="string">&quot;key&quot;</span>),</span><br><span class="line">                    <span class="string">&quot;count&quot;</span>: b.get(<span class="string">&quot;doc_count&quot;</span>)</span><br><span class="line">                &#125; <span class="keyword">for</span> b <span class="keyword">in</span> agg_result.get(<span class="string">&quot;buckets&quot;</span>, [])]</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;value&quot;</span>: agg_result.get(<span class="string">&quot;value&quot;</span>)&#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"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    mcp.run()</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">通过 MCP 协议让 AI 直接操作 Easysearch，告别复杂的 DSL 查询语法。</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>被天才吧耽误，Apple silicon 死机与恢复的一年</title>
    <link href="https://blog.no-claw.com/posts/d5918d57/"/>
    <id>https://blog.no-claw.com/posts/d5918d57/</id>
    <published>2025-12-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>十年的黑苹果玩家终于入手了Apple silicon，且不说买了半年之后就被新产品的pro背刺的感受。这次我们只聊由于性能问题被耽误的这一年。</p><p>新电子产品的第一年都很稳，各种满核心编译Java都没有问题。不知道是系统更新还是厂家为了迭代产品，基本都是快过保的时候出问题。</p><p>去年冬天突然遇到死机黑屏问题，Typora文档写一半黑屏了，长按电源键无法开机，来回折腾要半个小时以上。去天才吧维修，说硬件测试没问题（其实就是快捷键）， 千篇一律的官僚主义（记住这个词后面要考）拒绝返厂，然后拒绝我的各种专业建议，给我的建议就是备份重装，但是，崩溃依然继续。 于是求助群友，我竟然不是个例。</p><p>半年后，继续找他们掰扯，理由是过了一段时间无法证明我说的重装系统不能复现问题。给的方案还是重装。继续拒绝返厂检测。</p><span id="more"></span><p>期间也陆陆续续和线上沟通，在这期间对这个品牌的售后彻底失望。行政关系团队的陈先生，带着一种高高在上的口吻来“解决问题”。关于我反馈他们的专员一问三不知，完全有失一个技术人应有的技术储备的时候。他轻描淡写谈写，“我不是技术方向的，所以我不对技术层面的事情做评价，我们的专员是经过专业培训的，所以你要相信他们”。然后他们向上反馈，客户不配合，无法继续排查问题，服务被迫终止。留下一句，你要去网上发帖子是你的权利云云。仿佛这一切就是他们的日常。</p><p>收集了日志，和我说是电脑plist文件格式有问题，于是反问哪个软件的？这个你们xcode为什么不校验？对方摆烂。让我做删除某某目录的操作，问这个操作是做什么的，删除有什么后果，对方语塞。还好有备份，这个操作会直接导致系统里的ruby无法使用，而MacOS的一些组件还是依赖这个的。</p><p>不管是电话里还是天才吧，要录音留影的时候，对方理直气壮的拒绝服务，是真的不敢么。</p><p>所以啊，当致电400的时候，apple电话里的“请礼貌对待他们”，有没有可能真的是你们的问题呢？</p><p>如果国产OS做的好一点，不那么好大喜功，可能国产真的很符合国人的使用习惯，赶超欧美也只是时间的问题。</p><p>于是心得：如果去找Apple线上的客服问问题，可能还没有自己查文档来的快。<br>这已经不是，某天才吧店，听着所谓的专员对着客户说，“IOS之所以能成为一个独特的操作系统，原因….” 小伙伴说他是伪科普，我说现在已经不是10年前了。所谓apple专员的话术在当年可能觉得专业，现在只是一种甩锅的手段罢了。</p><p>言归正传，说到Macbook的崩溃，随着时间关系，已经从写写文档直接黑屏无法启动变成了睡眠直接睡死，然后变成了做一些文字工作鼠标卡死，SWAP爆满。虽然我的使用习惯不是特别好，但是这些年的黑苹果，从来没出现过这些问题，所以对Macbook 抱有希望，全家桶是果黑的第一步，没错。苹果生态很好，但不是唯一。</p><p>一次偶然的机会，了解到kernel task。默认的任务管理器是不显示的，也就是我们看系统监控的时候，看到的占用总是和实际不符。</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">活动监视器 - 显示 - 所有进程，分层显示。</span><br></pre></td></tr></table></figure><p>关于kernel task，有说左边插显示器会导致温度异常的，有说系统过热保护的。但，只有跑大模型的时候风扇才会拉起来，但是这不等于机身不烫。通常来说，电脑卡的时候，kernel task的CPU占用率通常拉到100%，极端的时候我的CPU可以拉到接近500。</p><p>说句玩笑话，因为系统的自我保护，包括但不限于温度过高，负载过重等等，这个保护进程会占用大量的CPU和内存资源。从而导致系统卡顿，打字延迟，开会音频撕裂。在那些年的黑苹果生涯，我从未遇到过。</p><p>kernel task这个进程，不能被kill。所以最后的方案就是重启电脑，以及在下次打开的时候要至少停止30秒，或者什么也不干，让CPU当下此刻满载让kernel task充分发挥威力，一直到CPU降下来。</p><p>也许我并不怀念那个需要熬夜改驱动的时代，我怀念的是那个技术至上、凡事求真求实的江湖。</p><p>当一家顶尖科技公司的售后开始用“我不懂技术”当挡箭牌，用“相信培训”来掩盖逻辑缺失时，对于拥有专业技能的发烧友来讲，这是不止是傲慢，更是对技术本身的亵渎与放逐。</p><p>折腾了十年，我躲过了黑苹果遍地的驱动坑，却终究没躲过白苹果这堵官僚墙。</p>]]></content>
    
    
    <summary type="html">十年黑苹果玩家入手 Apple Silicon 后频繁死机，被天才吧耽误一年的维修经历。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>Amazon Bedrock AgentCore 开发实战（一）：本地构建 AI Agent</title>
    <link href="https://blog.no-claw.com/posts/56feb9d1/"/>
    <id>https://blog.no-claw.com/posts/56feb9d1/</id>
    <published>2025-12-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 AI Agent 开发过程中，快速迭代和即时反馈至关重要。Amazon Bedrock AgentCore 提供了完整的本地开发支持，让开发者能够在本地环境中构建、测试和调试 Agent，然后无缝部署到云端。</p><p>本文将带您从零开始，在本地搭建 AgentCore 开发环境，并通过详细的代码解析，帮助您深入理解每一个技术细节。</p><h3 id="Amazon-Bedrock-AgentCore-简介"><a href="#Amazon-Bedrock-AgentCore-简介" class="headerlink" title="Amazon Bedrock AgentCore 简介"></a>Amazon Bedrock AgentCore 简介</h3><p>Amazon Bedrock AgentCore 是一套专为 AI Agent 设计的企业级基础设施服务。它解决了 Agent 从原型到生产过程中的核心挑战：</p><ul><li><strong>AgentCore Runtime</strong>：无服务器运行时环境，支持最长 8 小时的任务执行和 100MB 的请求负载</li><li><strong>AgentCore Memory</strong>：提供短期和长期记忆管理，支持跨会话的上下文保持</li><li><strong>AgentCore Gateway</strong>：统一的工具网关，支持 MCP 协议的工具发现和调用</li><li><strong>AgentCore Identity</strong>：安全的身份认证和授权管理</li></ul><p>本地开发的优势在于：开发者可以在熟悉的环境中快速验证想法，无需等待云端部署，同时保持与生产环境一致的代码结构。</p><h3 id="第一部分：环境准备"><a href="#第一部分：环境准备" class="headerlink" title="第一部分：环境准备"></a>第一部分：环境准备</h3><h4 id="1-1-安装-Python-包管理器"><a href="#1-1-安装-Python-包管理器" class="headerlink" title="1.1 安装 Python 包管理器"></a>1.1 安装 Python 包管理器</h4><p>我们推荐使用 uv 作为 Python 包管理器，它具有更快的依赖解析速度和更可靠的环境隔离能力。</p><p><strong>macOS 和 Linux 系统：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -LsSf https://astral.sh/uv/install.sh | sh</span><br></pre></td></tr></table></figure><p><strong>Windows 系统：</strong></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">powershell <span class="literal">-c</span> <span class="string">&quot;irm https://astral.sh/uv/install.ps1 | iex&quot;</span></span><br></pre></td></tr></table></figure><p>安装完成后，请重新打开终端以使环境变量生效。</p><h4 id="1-2-配置-AWS-凭证"><a href="#1-2-配置-AWS-凭证" class="headerlink" title="1.2 配置 AWS 凭证"></a>1.2 配置 AWS 凭证</h4><p>AgentCore 需要访问 Amazon Bedrock 服务，因此需要配置有效的 AWS 凭证。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 验证当前凭证配置</span></span><br><span class="line">aws sts get-caller-identity</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">aws configure</span><br></pre></td></tr></table></figure><p>系统会提示您输入 Access Key ID、Secret Access Key、默认区域和输出格式。</p><h4 id="1-3-开启模型访问权限"><a href="#1-3-开启模型访问权限" class="headerlink" title="1.3 开启模型访问权限"></a>1.3 开启模型访问权限</h4><p>在 AWS 控制台中，导航至 Amazon Bedrock 服务，在”Model access”页面中启用 Anthropic Claude 系列模型的访问权限。本文示例使用 Claude Sonnet 4 模型。</p><h4 id="1-4-创建项目并安装依赖"><a href="#1-4-创建项目并安装依赖" class="headerlink" title="1.4 创建项目并安装依赖"></a>1.4 创建项目并安装依赖</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建新项目，指定 Python 版本为 3.13</span></span><br><span class="line">uv init my-agent-project --python 3.13</span><br><span class="line"></span><br><span class="line"><span class="comment"># 进入项目目录</span></span><br><span class="line"><span class="built_in">cd</span> my-agent-project</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装核心依赖包</span></span><br><span class="line">uv add bedrock-agentcore strands-agents</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装开发工具包（用于后续部署）</span></span><br><span class="line">uv add --dev bedrock-agentcore-starter-toolkit</span><br></pre></td></tr></table></figure><p><strong>依赖包说明：</strong></p><ul><li><code>bedrock-agentcore</code>：AgentCore 的 Python SDK，提供运行时封装和服务集成</li><li><code>strands-agents</code>：Strands Agent 框架，简化 Agent 的构建过程</li><li><code>bedrock-agentcore-starter-toolkit</code>：部署工具包，提供 CLI 命令和自动化部署能力</li></ul><h3 id="第二部分：构建第一个本地-Agent"><a href="#第二部分：构建第一个本地-Agent" class="headerlink" title="第二部分：构建第一个本地 Agent"></a>第二部分：构建第一个本地 Agent</h3><h4 id="2-1-创建-Agent-入口文件"><a href="#2-1-创建-Agent-入口文件" class="headerlink" title="2.1 创建 Agent 入口文件"></a>2.1 创建 Agent 入口文件</h4><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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="keyword">from</span> bedrock_agentcore <span class="keyword">import</span> BedrockAgentCoreApp</span><br><span class="line"><span class="keyword">from</span> strands <span class="keyword">import</span> Agent</span><br><span class="line"><span class="keyword">from</span> strands.models <span class="keyword">import</span> BedrockModel</span><br><span class="line"></span><br><span class="line">app = BedrockAgentCoreApp(debug=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">model = BedrockModel(</span><br><span class="line">    model_id=<span class="string">&quot;us.anthropic.claude-sonnet-4-20250514-v1:0&quot;</span>,</span><br><span class="line">    temperature=<span class="number">0.7</span>,</span><br><span class="line">    max_tokens=<span class="number">4096</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">agent = Agent(</span><br><span class="line">    model=model,</span><br><span class="line">    system_prompt=<span class="string">&quot;你是一个专业的技术助手，回答问题时准确、简洁。&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.entrypoint</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">invoke</span>(<span class="params">payload</span>):</span><br><span class="line">    user_message = payload.get(<span class="string">&quot;prompt&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> user_message:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;prompt 参数不能为空&quot;</span>&#125;</span><br><span class="line">    result = agent(user_message)</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;response&quot;</span>: result.message[<span class="string">&quot;content&quot;</span>][<span class="number">0</span>][<span class="string">&quot;text&quot;</span>]&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run()</span><br></pre></td></tr></table></figure><h4 id="2-2-代码逐行解析"><a href="#2-2-代码逐行解析" class="headerlink" title="2.2 代码逐行解析"></a>2.2 代码逐行解析</h4><p>让我们详细分析每一部分代码的作用：</p><p><strong>导入模块：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bedrock_agentcore <span class="keyword">import</span> BedrockAgentCoreApp</span><br></pre></td></tr></table></figure><p><code>BedrockAgentCoreApp</code> 是 AgentCore 应用的核心类。它封装了 HTTP 服务器、请求路由和生命周期管理，使您的 Agent 代码能够以标准化的方式运行，无论是在本地还是云端。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> strands <span class="keyword">import</span> Agent</span><br></pre></td></tr></table></figure><p><code>Agent</code> 是 Strands 框架的核心类，负责管理对话流程、工具调用和模型交互。它提供了简洁的 API 来构建具有推理能力的 AI Agent。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> strands.models <span class="keyword">import</span> BedrockModel</span><br></pre></td></tr></table></figure><p><code>BedrockModel</code> 是 Bedrock 模型的封装类，处理与 Amazon Bedrock 服务的通信，包括认证、请求格式化和响应解析。</p><p><strong>创建应用实例：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app = BedrockAgentCoreApp(debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>创建 AgentCore 应用实例。<code>debug=True</code> 参数启用调试模式，会输出详细的请求和响应日志，便于开发阶段排查问题。在生产环境中，建议将此参数设为 <code>False</code>。</p><p><strong>配置模型：</strong></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></pre></td><td class="code"><pre><span class="line">model = BedrockModel(</span><br><span class="line">    model_id=<span class="string">&quot;us.anthropic.claude-sonnet-4-20250514-v1:0&quot;</span>,</span><br><span class="line">    temperature=<span class="number">0.7</span>,</span><br><span class="line">    max_tokens=<span class="number">4096</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li><code>model_id</code>：指定使用的基础模型。这里使用 Claude Sonnet 4，<code>us.</code> 前缀表示使用美国区域的跨区域推理端点</li><li><code>temperature</code>：控制输出的随机性，范围 0-1。较低的值产生更确定性的输出，较高的值增加创造性</li><li><code>max_tokens</code>：限制单次响应的最大 token 数量，防止输出过长</li></ul><p><strong>创建 Agent：</strong></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></pre></td><td class="code"><pre><span class="line">agent = Agent(</span><br><span class="line">    model=model,</span><br><span class="line">    system_prompt=<span class="string">&quot;你是一个专业的技术助手，回答问题时准确、简洁。&quot;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li><code>model</code>：传入上面配置的模型实例</li><li><code>system_prompt</code>：系统提示词，定义 Agent 的角色和行为准则。这段文本会在每次对话开始时发送给模型，引导其行为</li></ul><p><strong>定义入口函数：</strong></p><figure class="highlight python"><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="meta">@app.entrypoint</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">invoke</span>(<span class="params">payload</span>):</span><br></pre></td></tr></table></figure><p><code>@app.entrypoint</code> 装饰器将 <code>invoke</code> 函数注册为 Agent 的入口点。当收到 HTTP 请求时，AgentCore 会自动调用此函数，并将请求体解析为 <code>payload</code> 字典传入。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">user_message = payload.get(<span class="string">&quot;prompt&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br></pre></td></tr></table></figure><p>从请求负载中提取 <code>prompt</code> 字段。使用 <code>get</code> 方法并提供默认值，可以安全地处理字段缺失的情况。</p><figure class="highlight python"><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="keyword">if</span> <span class="keyword">not</span> user_message:</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;prompt 参数不能为空&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>输入验证：确保用户提供了有效的输入。返回的字典会被自动序列化为 JSON 响应。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">result = agent(user_message)</span><br></pre></td></tr></table></figure><p>调用 Agent 处理用户消息。Agent 会将消息发送给模型，获取响应，并在需要时执行工具调用。返回的 <code>result</code> 对象包含完整的对话结果。</p><figure class="highlight python"><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> &#123;<span class="string">&quot;response&quot;</span>: result.message[<span class="string">&quot;content&quot;</span>][<span class="number">0</span>][<span class="string">&quot;text&quot;</span>]&#125;</span><br></pre></td></tr></table></figure><p>从结果中提取文本响应并返回。<code>result.message</code> 是模型返回的消息对象，<code>content</code> 是内容数组，我们取第一个元素的 <code>text</code> 字段。</p><p><strong>启动应用：</strong></p><figure class="highlight python"><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="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run()</span><br></pre></td></tr></table></figure><p>Python 的标准入口点检查。当直接运行此文件时，启动 HTTP 服务器。<code>app.run()</code> 默认在 <code>0.0.0.0:8080</code> 启动服务，这是 AgentCore Runtime 的标准端口。</p><h4 id="2-3-运行和测试"><a href="#2-3-运行和测试" class="headerlink" title="2.3 运行和测试"></a>2.3 运行和测试</h4><p><strong>启动 Agent：</strong></p><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">uv run main.py</span><br></pre></td></tr></table></figure><p>您将看到类似以下的输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">INFO:     Started server process [12345]</span><br><span class="line">INFO:     Waiting for application startup.</span><br><span class="line">INFO:     Application startup complete.</span><br><span class="line">INFO:     Uvicorn running on http://0.0.0.0:8080</span><br></pre></td></tr></table></figure><p><strong>发送测试请求：</strong></p><p>打开另一个终端窗口，使用 curl 发送请求：</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">curl -X POST http://localhost:8080/invocations \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;请解释什么是微服务架构&quot;&#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p><strong>运行结果示例：</strong></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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;微服务架构是一种软件设计模式，它将大型应用程序拆分为多个小型、独立的服务，每个服务负责特定的业务功能。</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"></span></span><br><span class="line"><span class="string">**1. 服务独立性**</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">- 通过 API 进行通信</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">**2. 单一职责**</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"></span></span><br><span class="line"><span class="string">**3. 去中心化**</span></span><br><span class="line"><span class="string">- 没有中央控制器</span></span><br><span class="line"><span class="string">- 服务间通过轻量级协议通信（如 HTTP/REST、消息队列）</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">- **可扩展性**：可针对性地扩展高负载服务</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">- **开发效率**：小团队可独立维护服务</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">### 主要挑战</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">- **性能开销**：服务间调用的网络延迟</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">微服务架构特别适合大型、复杂的企业应用，以及需要快速迭代和高可用性的系统。&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>从响应结果可以看到，Agent 成功接收了请求并返回了结构化的回答。响应时间约为 9 秒，这包括了模型推理的时间。</p><p><strong>请求解析：</strong></p><ul><li><code>-X POST</code>：指定 HTTP 方法为 POST</li><li><code>http://localhost:8080/invocations</code>：AgentCore 的标准调用端点</li><li><code>-H &quot;Content-Type: application/json&quot;</code>：设置请求头，表明请求体为 JSON 格式</li><li><code>-d &#39;{&quot;prompt&quot;: &quot;...&quot;}&#39;</code>：请求体，包含发送给 Agent 的消息</li><li><code>| jq .</code>：将响应通过 jq 工具格式化输出（需要预先安装 jq）</li></ul><h3 id="第三部分：为-Agent-添加工具能力"><a href="#第三部分：为-Agent-添加工具能力" class="headerlink" title="第三部分：为 Agent 添加工具能力"></a>第三部分：为 Agent 添加工具能力</h3><p>Agent 的真正威力在于能够调用外部工具来完成任务。接下来，我们将为 Agent 添加自定义工具。</p><h4 id="3-1-安装工具依赖"><a href="#3-1-安装工具依赖" class="headerlink" title="3.1 安装工具依赖"></a>3.1 安装工具依赖</h4><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">uv add strands-agents-tools</span><br></pre></td></tr></table></figure><p><code>strands-agents-tools</code> 包含了一系列预构建的工具，如计算器、文件操作等。</p><h4 id="3-2-创建带工具的-Agent"><a href="#3-2-创建带工具的-Agent" class="headerlink" title="3.2 创建带工具的 Agent"></a>3.2 创建带工具的 Agent</h4><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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bedrock_agentcore <span class="keyword">import</span> BedrockAgentCoreApp</span><br><span class="line"><span class="keyword">from</span> strands <span class="keyword">import</span> Agent, tool</span><br><span class="line"><span class="keyword">from</span> strands.models <span class="keyword">import</span> BedrockModel</span><br><span class="line"><span class="keyword">from</span> strands_tools <span class="keyword">import</span> calculator</span><br><span class="line"></span><br><span class="line">app = BedrockAgentCoreApp(debug=<span class="literal">True</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_weather</span>(<span class="params">city: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</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">    </span></span><br><span class="line"><span class="string">    Args:</span></span><br><span class="line"><span class="string">        city: 城市名称，例如 &quot;北京&quot;、&quot;上海&quot;、&quot;深圳&quot;</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    Returns:</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">    weather_data = &#123;</span><br><span class="line">        <span class="string">&quot;北京&quot;</span>: <span class="string">&quot;晴天，气温 22°C，湿度 45%&quot;</span>,</span><br><span class="line">        <span class="string">&quot;上海&quot;</span>: <span class="string">&quot;多云，气温 26°C，湿度 65%&quot;</span>,</span><br><span class="line">        <span class="string">&quot;深圳&quot;</span>: <span class="string">&quot;阵雨，气温 28°C，湿度 80%&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> weather_data.get(city, <span class="string">f&quot;暂无 <span class="subst">&#123;city&#125;</span> 的天气数据&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_">search_database</span>(<span class="params">query: <span class="built_in">str</span>, limit: <span class="built_in">int</span> = <span class="number">5</span></span>) -&gt; <span class="built_in">str</span>:</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">    </span></span><br><span class="line"><span class="string">    Args:</span></span><br><span class="line"><span class="string">        query: 搜索关键词</span></span><br><span class="line"><span class="string">        limit: 返回结果的最大数量，默认为 5</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    Returns:</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">return</span> <span class="string">f&quot;找到 <span class="subst">&#123;limit&#125;</span> 条与「<span class="subst">&#123;query&#125;</span>」相关的记录（模拟数据）&quot;</span></span><br><span class="line"></span><br><span class="line">model = BedrockModel(</span><br><span class="line">    model_id=<span class="string">&quot;us.anthropic.claude-sonnet-4-20250514-v1:0&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">agent = Agent(</span><br><span class="line">    model=model,</span><br><span class="line">    tools=[calculator, get_weather, search_database],</span><br><span class="line">    system_prompt=<span class="string">&quot;&quot;&quot;你是一个智能助手，具备以下能力：</span></span><br><span class="line"><span class="string">1. 使用 calculator 工具进行数学计算</span></span><br><span class="line"><span class="string">2. 使用 get_weather 工具查询城市天气</span></span><br><span class="line"><span class="string">3. 使用 search_database 工具搜索数据库</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><br><span class="line"><span class="meta">@app.entrypoint</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">invoke</span>(<span class="params">payload</span>):</span><br><span class="line">    user_message = payload.get(<span class="string">&quot;prompt&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> user_message:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;prompt 参数不能为空&quot;</span>&#125;</span><br><span class="line">    result = agent(user_message)</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;response&quot;</span>: result.message[<span class="string">&quot;content&quot;</span>][<span class="number">0</span>][<span class="string">&quot;text&quot;</span>]&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run()</span><br></pre></td></tr></table></figure><h4 id="3-3-工具定义详解"><a href="#3-3-工具定义详解" class="headerlink" title="3.3 工具定义详解"></a>3.3 工具定义详解</h4><p><strong>@tool 装饰器：</strong></p><figure class="highlight python"><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="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_weather</span>(<span class="params">city: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br></pre></td></tr></table></figure><p><code>@tool</code> 装饰器将普通 Python 函数转换为 Agent 可调用的工具。Strands 框架会自动：</p><ul><li>提取函数签名作为工具的输入参数定义</li><li>解析 docstring 作为工具的描述信息</li><li>处理参数类型转换和验证</li></ul><p><strong>类型注解的重要性：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">get_weather</span>(<span class="params">city: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br></pre></td></tr></table></figure><ul><li><code>city: str</code>：参数类型注解，告诉模型这个参数应该是字符串类型</li><li><code>-&gt; str</code>：返回值类型注解，表明函数返回字符串</li></ul><p>类型注解不仅是代码文档，更是模型理解工具用法的关键信息。模型会根据这些信息决定如何构造工具调用参数。</p><p><strong>Docstring 规范：</strong></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></pre></td><td class="code"><pre><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"></span></span><br><span class="line"><span class="string">Args:</span></span><br><span class="line"><span class="string">    city: 城市名称，例如 &quot;北京&quot;、&quot;上海&quot;、&quot;深圳&quot;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Returns:</span></span><br><span class="line"><span class="string">    包含温度和天气状况的字符串</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br></pre></td></tr></table></figure><p>Docstring 遵循 Google 风格，包含三个部分：</p><ul><li>第一行：工具的简要描述，模型会用它来判断何时使用此工具</li><li><code>Args</code> 部分：每个参数的详细说明，包括示例值</li><li><code>Returns</code> 部分：返回值的描述</li></ul><p>清晰的 docstring 能显著提高模型选择和使用工具的准确性。</p><p><strong>带默认值的参数：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">search_database</span>(<span class="params">query: <span class="built_in">str</span>, limit: <span class="built_in">int</span> = <span class="number">5</span></span>) -&gt; <span class="built_in">str</span>:</span><br></pre></td></tr></table></figure><p><code>limit: int = 5</code> 定义了一个带默认值的可选参数。模型可以选择是否提供此参数，如果不提供则使用默认值 5。</p><p><strong>注册工具到 Agent：</strong></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></pre></td><td class="code"><pre><span class="line">agent = Agent(</span><br><span class="line">    model=model,</span><br><span class="line">    tools=[calculator, get_weather, search_database],</span><br><span class="line">    ...</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><code>tools</code> 参数接受一个工具列表。<code>calculator</code> 是从 <code>strands_tools</code> 导入的内置工具，<code>get_weather</code> 和 <code>search_database</code> 是我们自定义的工具。</p><h4 id="3-4-测试工具调用"><a href="#3-4-测试工具调用" class="headerlink" title="3.4 测试工具调用"></a>3.4 测试工具调用</h4><p>重启 Agent 后，测试不同类型的请求：</p><p><strong>测试计算功能：</strong></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">curl -X POST http://localhost:8080/invocations \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;计算 (125 + 375) * 2.5 的结果&quot;&#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p>Agent 会识别这是一个数学计算问题，自动调用 <code>calculator</code> 工具。</p><p><strong>计算功能运行结果：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;计算结果：(125 + 375) * 2.5 = 1250</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. 先计算括号内：125 + 375 = 500</span></span><br><span class="line"><span class="string">2. 再乘以 2.5：500 * 2.5 = 1250&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>测试天气查询：</strong></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">curl -X POST http://localhost:8080/invocations \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;上海今天天气怎么样？&quot;&#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p>Agent 会调用 <code>get_weather</code> 工具，传入参数 <code>city=&quot;上海&quot;</code>。</p><p><strong>天气查询运行结果：</strong></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;response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;上海今天的天气情况：多云，气温 26°C，湿度 65%。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">温度适中，湿度稍高，建议外出时携带一把伞以防万一。&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>测试复合任务：</strong></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">curl -X POST http://localhost:8080/invocations \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;查一下北京的天气，如果温度超过20度，计算开10小时空调需要多少度电（假设功率1.5千瓦）&quot;&#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p>这个请求需要 Agent 协调多个工具：先查询天气获取温度，然后根据条件决定是否进行计算。</p><p><strong>复合任务运行结果：</strong></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><span class="line">13</span><br><span class="line">14</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;response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;让我先查询北京的天气，然后根据温度情况进行计算。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">**北京天气**：晴天，气温 22°C，湿度 45%</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">由于当前温度 22°C 超过了 20 度，我来计算空调用电量：</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.5 千瓦（kW）</span></span><br><span class="line"><span class="string">- 使用时间：10 小时</span></span><br><span class="line"><span class="string">- 耗电量 = 功率 × 时间 = 1.5 kW × 10 h = 15 度电</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">**结论**：开 10 小时空调大约需要消耗 15 度电。按照一般民用电价（约 0.5 元/度）计算，费用约为 7.5 元。&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>从这个结果可以看到，Agent 成功地：</p><ol><li>调用 <code>get_weather</code> 工具获取北京天气</li><li>判断温度（22°C）超过 20 度</li><li>调用 <code>calculator</code> 工具计算用电量</li><li>综合信息给出完整回答</li></ol><h3 id="第四部分：构建-MCP-Server"><a href="#第四部分：构建-MCP-Server" class="headerlink" title="第四部分：构建 MCP Server"></a>第四部分：构建 MCP Server</h3><p>Model Context Protocol（MCP）是一种标准化协议，允许 Agent 动态发现和调用工具。MCP Server 可以被任何支持 MCP 的客户端（如 Claude Desktop、Cursor、Kiro 等）调用。</p><h4 id="4-1-安装-MCP-依赖"><a href="#4-1-安装-MCP-依赖" class="headerlink" title="4.1 安装 MCP 依赖"></a>4.1 安装 MCP 依赖</h4><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">uv add mcp</span><br></pre></td></tr></table></figure><h4 id="4-2-创建-MCP-Server"><a href="#4-2-创建-MCP-Server" class="headerlink" title="4.2 创建 MCP Server"></a>4.2 创建 MCP Server</h4><p>创建新文件 <code>mcp_server.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line">mcp = FastMCP(</span><br><span class="line">    name=<span class="string">&quot;Financial Tools Server&quot;</span>,</span><br><span class="line">    host=<span class="string">&quot;0.0.0.0&quot;</span>,</span><br><span class="line">    stateless_http=<span class="literal">True</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add_numbers</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Add two numbers together&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">multiply_numbers</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Multiply two numbers together&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a * b</span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_stock_price</span>(<span class="params">symbol: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Get the current stock price for a given symbol.</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    Args:</span></span><br><span class="line"><span class="string">        symbol: Stock ticker symbol like AAPL, GOOGL, AMZN</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    Returns:</span></span><br><span class="line"><span class="string">        Stock symbol and current price</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    prices = &#123;</span><br><span class="line">        <span class="string">&quot;AAPL&quot;</span>: <span class="number">178.50</span>,</span><br><span class="line">        <span class="string">&quot;GOOGL&quot;</span>: <span class="number">141.20</span>,</span><br><span class="line">        <span class="string">&quot;AMZN&quot;</span>: <span class="number">178.90</span>,</span><br><span class="line">        <span class="string">&quot;MSFT&quot;</span>: <span class="number">378.50</span></span><br><span class="line">    &#125;</span><br><span class="line">    price = prices.get(symbol.upper())</span><br><span class="line">    <span class="keyword">if</span> price:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&quot;<span class="subst">&#123;symbol.upper()&#125;</span> current price: $<span class="subst">&#123;price&#125;</span>&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;Price data not found for <span class="subst">&#123;symbol&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">greet_user</span>(<span class="params">name: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Greet a user by name&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;Hello, <span class="subst">&#123;name&#125;</span>! Nice to meet you.&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;MCP Server 启动中...&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;MCP 端点: http://localhost:8000/mcp&quot;</span>)</span><br><span class="line">    mcp.run(transport=<span class="string">&quot;streamable-http&quot;</span>)</span><br></pre></td></tr></table></figure><h4 id="4-3-MCP-Server-代码解析"><a href="#4-3-MCP-Server-代码解析" class="headerlink" title="4.3 MCP Server 代码解析"></a>4.3 MCP Server 代码解析</h4><p><strong>导入 FastMCP：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br></pre></td></tr></table></figure><p><code>FastMCP</code> 是 MCP Python SDK 提供的高级封装类，简化了 MCP Server 的创建过程。</p><p><strong>创建 MCP Server 实例：</strong></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></pre></td><td class="code"><pre><span class="line">mcp = FastMCP(</span><br><span class="line">    name=<span class="string">&quot;Financial Tools Server&quot;</span>,</span><br><span class="line">    host=<span class="string">&quot;0.0.0.0&quot;</span>,</span><br><span class="line">    stateless_http=<span class="literal">True</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li><code>name</code>：Server 的名称，用于标识和日志记录</li><li><code>host</code>：监听地址，<code>0.0.0.0</code> 表示接受所有网络接口的连接</li><li><code>stateless_http=True</code>：启用无状态 HTTP 模式，这是 AgentCore Runtime 要求的配置</li></ul><p><strong>定义工具：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add_numbers</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Add two numbers together&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br></pre></td></tr></table></figure><p><code>@mcp.tool()</code> 装饰器将 Python 函数注册为 MCP 工具：</p><ul><li>函数名成为工具名称</li><li>参数类型注解定义输入参数的类型</li><li>docstring 成为工具的描述信息</li><li>返回值类型注解定义输出类型</li></ul><p><strong>启动服务：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mcp.run(transport=<span class="string">&quot;streamable-http&quot;</span>)</span><br></pre></td></tr></table></figure><p><code>transport=&quot;streamable-http&quot;</code> 指定使用 Streamable HTTP 传输协议，这是 MCP 推荐的生产环境传输方式，支持流式响应。</p><h4 id="4-4-运行和测试-MCP-Server"><a href="#4-4-运行和测试-MCP-Server" class="headerlink" title="4.4 运行和测试 MCP Server"></a>4.4 运行和测试 MCP Server</h4><p><strong>启动服务：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv run mcp_server.py</span><br></pre></td></tr></table></figure><p><strong>运行输出：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">MCP Server 启动中...</span><br><span class="line">MCP 端点: http://localhost:8000/mcp</span><br><span class="line">INFO:     Started server process [12345]</span><br><span class="line">INFO:     Uvicorn running on http://0.0.0.0:8000</span><br></pre></td></tr></table></figure><p><strong>使用 Python 客户端测试：</strong></p><p>首先安装 MCP 客户端依赖（如果尚未安装）：</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">uv add mcp httpx</span><br></pre></td></tr></table></figure><p>创建测试客户端文件 <code>mcp_client.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> mcp <span class="keyword">import</span> ClientSession</span><br><span class="line"><span class="keyword">from</span> mcp.client.streamable_http <span class="keyword">import</span> streamablehttp_client</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;测试 MCP Server 的连接和工具调用&quot;&quot;&quot;</span></span><br><span class="line">    mcp_url = <span class="string">&quot;http://localhost:8000/mcp&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;连接到 MCP Server: <span class="subst">&#123;mcp_url&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 建立连接</span></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> streamablehttp_client(mcp_url) <span class="keyword">as</span> (</span><br><span class="line">        read_stream,</span><br><span class="line">        write_stream,</span><br><span class="line">        _,</span><br><span class="line">    ):</span><br><span class="line">        <span class="keyword">async</span> <span class="keyword">with</span> ClientSession(read_stream, write_stream) <span class="keyword">as</span> session:</span><br><span class="line">            <span class="comment"># 初始化会话</span></span><br><span class="line">            <span class="keyword">await</span> session.initialize()</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;✓ 连接成功\n&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 列出可用工具</span></span><br><span class="line">            tools = <span class="keyword">await</span> session.list_tools()</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;可用工具:&quot;</span>)</span><br><span class="line">            <span class="keyword">for</span> tool <span class="keyword">in</span> tools.tools:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">f&quot;  - <span class="subst">&#123;tool.name&#125;</span>: <span class="subst">&#123;tool.description&#125;</span>&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;\n&quot;</span> + <span class="string">&quot;=&quot;</span> * <span class="number">50</span> + <span class="string">&quot;\n&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 测试 add_numbers 工具</span></span><br><span class="line">            result = <span class="keyword">await</span> session.call_tool(<span class="string">&quot;add_numbers&quot;</span>, &#123;<span class="string">&quot;a&quot;</span>: <span class="number">10</span>, <span class="string">&quot;b&quot;</span>: <span class="number">20</span>&#125;)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;add_numbers(10, 20) = <span class="subst">&#123;result.content[<span class="number">0</span>].text&#125;</span>&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 测试 multiply_numbers 工具</span></span><br><span class="line">            result = <span class="keyword">await</span> session.call_tool(<span class="string">&quot;multiply_numbers&quot;</span>, &#123;<span class="string">&quot;a&quot;</span>: <span class="number">7</span>, <span class="string">&quot;b&quot;</span>: <span class="number">8</span>&#125;)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;multiply_numbers(7, 8) = <span class="subst">&#123;result.content[<span class="number">0</span>].text&#125;</span>&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 测试 get_stock_price 工具</span></span><br><span class="line">            result = <span class="keyword">await</span> session.call_tool(<span class="string">&quot;get_stock_price&quot;</span>, &#123;<span class="string">&quot;symbol&quot;</span>: <span class="string">&quot;AAPL&quot;</span>&#125;)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;get_stock_price(&#x27;AAPL&#x27;) = <span class="subst">&#123;result.content[<span class="number">0</span>].text&#125;</span>&quot;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 测试 greet_user 工具</span></span><br><span class="line">            result = <span class="keyword">await</span> session.call_tool(<span class="string">&quot;greet_user&quot;</span>, &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;开发者&quot;</span>&#125;)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;greet_user(&#x27;开发者&#x27;) = <span class="subst">&#123;result.content[<span class="number">0</span>].text&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    asyncio.run(main())</span><br></pre></td></tr></table></figure><p><strong>代码解析：</strong></p><figure class="highlight python"><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="keyword">from</span> mcp <span class="keyword">import</span> ClientSession</span><br><span class="line"><span class="keyword">from</span> mcp.client.streamable_http <span class="keyword">import</span> streamablehttp_client</span><br></pre></td></tr></table></figure><p>导入 MCP 客户端所需的模块：</p><ul><li><code>ClientSession</code>：MCP 客户端会话管理类</li><li><code>streamablehttp_client</code>：Streamable HTTP 传输客户端，用于连接使用该协议的 MCP Server</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">with</span> streamablehttp_client(mcp_url) <span class="keyword">as</span> (read_stream, write_stream, _):</span><br></pre></td></tr></table></figure><p>建立与 MCP Server 的连接，返回读写流用于双向通信。</p><figure class="highlight python"><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="keyword">async</span> <span class="keyword">with</span> ClientSession(read_stream, write_stream) <span class="keyword">as</span> session:</span><br><span class="line">    <span class="keyword">await</span> session.initialize()</span><br></pre></td></tr></table></figure><p>创建客户端会话并初始化，这会与服务器交换能力信息。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tools = <span class="keyword">await</span> session.list_tools()</span><br></pre></td></tr></table></figure><p>获取服务器提供的所有工具列表。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">result = <span class="keyword">await</span> session.call_tool(<span class="string">&quot;add_numbers&quot;</span>, &#123;<span class="string">&quot;a&quot;</span>: <span class="number">10</span>, <span class="string">&quot;b&quot;</span>: <span class="number">20</span>&#125;)</span><br></pre></td></tr></table></figure><p>调用指定工具，传入参数字典，返回执行结果。</p><p><strong>运行测试：</strong></p><p>确保 MCP Server 正在运行，然后执行：</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">uv run mcp_client.py</span><br></pre></td></tr></table></figure><p><strong>运行结果：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">连接到 MCP Server: http://localhost:8000/mcp</span><br><span class="line">✓ 连接成功</span><br><span class="line"></span><br><span class="line">可用工具:</span><br><span class="line">  - add_numbers: Add two numbers together</span><br><span class="line">  - multiply_numbers: Multiply two numbers together</span><br><span class="line">  - get_stock_price: Get the current stock price for a given symbol.</span><br><span class="line">  - greet_user: Greet a user by name</span><br><span class="line"></span><br><span class="line">==================================================</span><br><span class="line"></span><br><span class="line">add_numbers(10, 20) = 30</span><br><span class="line">multiply_numbers(7, 8) = 56</span><br><span class="line">get_stock_price(&#x27;AAPL&#x27;) = AAPL current price: $178.5</span><br><span class="line">greet_user(&#x27;开发者&#x27;) = Hello, 开发者! Nice to meet you.</span><br></pre></td></tr></table></figure><p><strong>使用 curl 测试（CLI 方式）：</strong></p><p>MCP Server 使用 Streamable HTTP 传输协议，需要设置正确的 Accept header：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 初始化连接</span></span><br><span class="line">curl -X POST http://localhost:8000/mcp \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -H <span class="string">&quot;Accept: application/json, text/event-stream&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">    &quot;jsonrpc&quot;: &quot;2.0&quot;,</span></span><br><span class="line"><span class="string">    &quot;id&quot;: 1,</span></span><br><span class="line"><span class="string">    &quot;method&quot;: &quot;initialize&quot;,</span></span><br><span class="line"><span class="string">    &quot;params&quot;: &#123;</span></span><br><span class="line"><span class="string">      &quot;protocolVersion&quot;: &quot;2024-11-05&quot;,</span></span><br><span class="line"><span class="string">      &quot;capabilities&quot;: &#123;&#125;,</span></span><br><span class="line"><span class="string">      &quot;clientInfo&quot;: &#123;&quot;name&quot;: &quot;curl-client&quot;, &quot;version&quot;: &quot;1.0.0&quot;&#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  &#125;&#x27;</span></span><br></pre></td></tr></table></figure><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"># 列出可用工具</span></span><br><span class="line">curl -X POST http://localhost:8000/mcp \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -H <span class="string">&quot;Accept: application/json, text/event-stream&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;jsonrpc&quot;: &quot;2.0&quot;, &quot;id&quot;: 2, &quot;method&quot;: &quot;tools/list&quot;, &quot;params&quot;: &#123;&#125;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><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"># 调用 add_numbers 工具</span></span><br><span class="line">curl -X POST http://localhost:8000/mcp \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -H <span class="string">&quot;Accept: application/json, text/event-stream&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">    &quot;jsonrpc&quot;: &quot;2.0&quot;,</span></span><br><span class="line"><span class="string">    &quot;id&quot;: 3,</span></span><br><span class="line"><span class="string">    &quot;method&quot;: &quot;tools/call&quot;,</span></span><br><span class="line"><span class="string">    &quot;params&quot;: &#123;&quot;name&quot;: &quot;add_numbers&quot;, &quot;arguments&quot;: &#123;&quot;a&quot;: 10, &quot;b&quot;: 20&#125;&#125;</span></span><br><span class="line"><span class="string">  &#125;&#x27;</span></span><br></pre></td></tr></table></figure><p><strong>curl 响应示例：</strong></p><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;jsonrpc&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.0&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">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;tools&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;add_numbers&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Add two numbers together&quot;</span><span class="punctuation">,</span> ...<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;multiply_numbers&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Multiply two numbers together&quot;</span><span class="punctuation">,</span> ...<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;get_stock_price&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Get the current stock price for a given symbol.&quot;</span><span class="punctuation">,</span> ...<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;greet_user&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Greet a user by name&quot;</span><span class="punctuation">,</span> ...<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><span class="line"><span class="punctuation">&#125;</span></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;jsonrpc&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.0&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span> <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;30&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：curl 命令必须包含 <code>-H &quot;Accept: application/json, text/event-stream&quot;</code> header，否则会收到 “Not Acceptable” 错误。</p></blockquote><p><strong>使用 MCP Inspector 测试（可选）：</strong></p><p>MCP Inspector 是一个可视化测试工具，提供图形界面来测试 MCP Server：</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">npx @modelcontextprotocol/inspector</span><br></pre></td></tr></table></figure><p>在浏览器中打开 <code>http://localhost:6274</code>，选择 “Streamable HTTP” 传输类型，输入 MCP Server 地址 <code>http://localhost:8000/mcp</code>，即可进行交互式测试。您可以：</p><ul><li>查看所有可用工具及其参数定义</li><li>手动调用工具并查看返回结果</li><li>调试工具执行过程</li></ul><h3 id="第五部分：构建-A2A-Server"><a href="#第五部分：构建-A2A-Server" class="headerlink" title="第五部分：构建 A2A Server"></a>第五部分：构建 A2A Server</h3><p>Agent-to-Agent（A2A）协议专为多 Agent 系统设计，允许 Agent 之间相互发现和通信。A2A 使用 JSON-RPC 格式进行通信，并通过 Agent Card 描述 Agent 的能力。</p><h4 id="5-1-安装-A2A-依赖"><a href="#5-1-安装-A2A-依赖" class="headerlink" title="5.1 安装 A2A 依赖"></a>5.1 安装 A2A 依赖</h4><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">uv add <span class="string">&#x27;strands-agents[a2a]&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="5-2-创建-A2A-Server"><a href="#5-2-创建-A2A-Server" class="headerlink" title="5.2 创建 A2A Server"></a>5.2 创建 A2A Server</h4><p>创建新文件 <code>a2a_server.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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">from</span> strands <span class="keyword">import</span> Agent</span><br><span class="line"><span class="keyword">from</span> strands.multiagent.a2a <span class="keyword">import</span> A2AServer</span><br><span class="line"><span class="keyword">from</span> strands.models <span class="keyword">import</span> BedrockModel</span><br><span class="line"><span class="keyword">from</span> strands_tools <span class="keyword">import</span> calculator</span><br><span class="line"><span class="keyword">import</span> uvicorn</span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">logging.basicConfig(level=logging.INFO)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从环境变量获取运行时 URL，本地开发时使用默认值</span></span><br><span class="line">runtime_url = os.environ.get(</span><br><span class="line">    <span class="string">&#x27;AGENTCORE_RUNTIME_URL&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;http://127.0.0.1:9000/&#x27;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">f&quot;Runtime URL: <span class="subst">&#123;runtime_url&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建模型</span></span><br><span class="line">model = BedrockModel(</span><br><span class="line">    model_id=<span class="string">&quot;us.anthropic.claude-sonnet-4-20250514-v1:0&quot;</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">strands_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Calculator Agent&quot;</span>,</span><br><span class="line">    description=<span class="string">&quot;A calculator agent that can perform basic arithmetic operations.&quot;</span>,</span><br><span class="line">    model=model,</span><br><span class="line">    tools=[calculator],</span><br><span class="line">    callback_handler=<span class="literal">None</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 A2A Server</span></span><br><span class="line">a2a_server = A2AServer(</span><br><span class="line">    agent=strands_agent,</span><br><span class="line">    http_url=runtime_url,</span><br><span class="line">    serve_at_root=<span class="literal">True</span>  <span class="comment"># 在根路径提供服务</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 FastAPI 应用</span></span><br><span class="line">app = FastAPI(title=<span class="string">&quot;Calculator A2A Server&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/ping&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">health_check</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;健康检查端点&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;status&quot;</span>: <span class="string">&quot;healthy&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 挂载 A2A Server</span></span><br><span class="line">app.mount(<span class="string">&quot;/&quot;</span>, a2a_server.to_fastapi_app())</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;A2A Server 启动中...&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Agent Card: http://localhost:9000/.well-known/agent-card.json&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;健康检查: http://localhost:9000/ping&quot;</span>)</span><br><span class="line">    uvicorn.run(app, host=<span class="string">&quot;0.0.0.0&quot;</span>, port=<span class="number">9000</span>)</span><br></pre></td></tr></table></figure><h4 id="5-3-A2A-Server-代码解析"><a href="#5-3-A2A-Server-代码解析" class="headerlink" title="5.3 A2A Server 代码解析"></a>5.3 A2A Server 代码解析</h4><p><strong>导入 A2A 模块：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> strands.multiagent.a2a <span class="keyword">import</span> A2AServer</span><br></pre></td></tr></table></figure><p><code>A2AServer</code> 是 Strands SDK 提供的 A2A 协议实现，需要安装 <code>strands-agents[a2a]</code> 扩展包。</p><p><strong>环境变量配置：</strong></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></pre></td><td class="code"><pre><span class="line">runtime_url = os.environ.get(</span><br><span class="line">    <span class="string">&#x27;AGENTCORE_RUNTIME_URL&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;http://127.0.0.1:9000/&#x27;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>从环境变量读取运行时 URL，如果未设置则使用本地默认值。这种设计使同一份代码可以在本地和云端运行，只需通过环境变量切换配置。部署到 AgentCore Runtime 时，该环境变量会自动设置。</p><p><strong>创建 A2A Server：</strong></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></pre></td><td class="code"><pre><span class="line">a2a_server = A2AServer(</span><br><span class="line">    agent=strands_agent,</span><br><span class="line">    http_url=runtime_url,</span><br><span class="line">    serve_at_root=<span class="literal">True</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li><code>agent</code>：要暴露的 Strands Agent 实例</li><li><code>http_url</code>：Agent 的可访问 URL，用于生成 Agent Card 中的端点信息</li><li><code>serve_at_root=True</code>：在根路径（<code>/</code>）提供服务，这是 AgentCore Runtime 要求的配置</li></ul><p><strong>组合 FastAPI 应用：</strong></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">app = FastAPI(title=<span class="string">&quot;Calculator A2A Server&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/ping&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">health_check</span>():</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;status&quot;</span>: <span class="string">&quot;healthy&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">app.mount(<span class="string">&quot;/&quot;</span>, a2a_server.to_fastapi_app())</span><br></pre></td></tr></table></figure><p>我们创建一个 FastAPI 应用作为容器，添加健康检查端点，然后将 A2A Server 挂载到根路径。A2A Server 默认使用 9000 端口，与 MCP Server（8000）和 AgentCore Runtime（8080）区分。</p><h4 id="5-3-运行和测试-A2A-Server"><a href="#5-3-运行和测试-A2A-Server" class="headerlink" title="5.3 运行和测试 A2A Server"></a>5.3 运行和测试 A2A Server</h4><p><strong>启动服务：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uv run a2a_server.py</span><br></pre></td></tr></table></figure><p><strong>获取 Agent Card：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://localhost:9000/.well-known/agent-card.json | jq .</span><br></pre></td></tr></table></figure><p>Agent Card 是 A2A 协议的核心概念，它描述了 Agent 的能力、支持的协议和调用方式。其他 Agent 可以通过读取 Agent Card 来了解如何与此 Agent 交互。</p><p><strong>调用 Agent（使用 JSON-RPC 格式）：</strong></p><p>由于我们创建的是 Calculator Agent（计算器 Agent），测试请求应该是数学计算相关的问题：</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></pre></td><td class="code"><pre><span class="line">curl -X POST http://localhost:9000/ \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">    &quot;jsonrpc&quot;: &quot;2.0&quot;,</span></span><br><span class="line"><span class="string">    &quot;id&quot;: &quot;request-001&quot;,</span></span><br><span class="line"><span class="string">    &quot;method&quot;: &quot;message/send&quot;,</span></span><br><span class="line"><span class="string">    &quot;params&quot;: &#123;</span></span><br><span class="line"><span class="string">      &quot;message&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;role&quot;: &quot;user&quot;,</span></span><br><span class="line"><span class="string">        &quot;parts&quot;: [</span></span><br><span class="line"><span class="string">          &#123;</span></span><br><span class="line"><span class="string">            &quot;kind&quot;: &quot;text&quot;,</span></span><br><span class="line"><span class="string">            &quot;text&quot;: &quot;请计算 (256 + 128) * 3 的结果&quot;</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">        ],</span></span><br><span class="line"><span class="string">        &quot;messageId&quot;: &quot;msg-001&quot;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  &#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p><strong>请求格式解析：</strong></p><ul><li><code>jsonrpc</code>：JSON-RPC 协议版本</li><li><code>id</code>：请求标识符，用于匹配响应</li><li><code>method</code>：调用的方法，<code>message/send</code> 是发送消息的标准方法</li><li><code>params.message</code>：消息内容<ul><li><code>role</code>：消息角色，<code>user</code> 表示用户消息</li><li><code>parts</code>：消息内容数组，支持多模态（文本、图片等）</li><li><code>messageId</code>：消息唯一标识</li></ul></li></ul><p><strong>运行结果示例：</strong></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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;jsonrpc&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;request-001&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;artifacts&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;artifactId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;a1b2c3d4-e5f6-7890-abcd-ef1234567890&quot;</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;agent_response&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;parts&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;kind&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;让我来计算这个表达式。\n\n**计算过程：**\n1. 先计算括号内：256 + 128 = 384\n2. 再乘以 3：384 × 3 = 1152\n\n**结果：(256 + 128) × 3 = 1152**&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><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;contextId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;65c4af3b-ef98-4c3a-9433-1287936d9703&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><p>从响应可以看到，Calculator Agent 成功调用了 calculator 工具完成了数学计算，并返回了详细的计算过程。</p><h3 id="第六部分：本地容器化部署"><a href="#第六部分：本地容器化部署" class="headerlink" title="第六部分：本地容器化部署"></a>第六部分：本地容器化部署</h3><p>在部署到云端之前，我们可以先在本地使用 Docker 容器运行 Agent。AgentCore Starter Toolkit 提供了便捷的 CLI 命令，可以自动生成 Dockerfile 并在本地构建运行容器，无需手动编写配置文件。</p><h4 id="7-1-配置-Agent"><a href="#7-1-配置-Agent" class="headerlink" title="7.1 配置 Agent"></a>7.1 配置 Agent</h4><p>首先使用 <code>agentcore configure</code> 命令配置 Agent：</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">agentcore configure -e main.py</span><br></pre></td></tr></table></figure><p><strong>参数说明：</strong></p><ul><li><code>-e main.py</code>：指定 Agent 的入口文件</li></ul><p>如果是首次运行，CLI 会以交互式方式引导您完成配置，包括设置 Agent 名称、选择区域等。您也可以使用 <code>-ni</code>（non-interactive）参数跳过交互式提示，使用默认值：</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">agentcore configure -e main.py -ni</span><br></pre></td></tr></table></figure><p>执行后，CLI 会在项目目录下生成 <code>.agentcore/</code> 配置目录，包含配置文件和 Dockerfile。</p><h4 id="7-2-本地容器运行"><a href="#7-2-本地容器运行" class="headerlink" title="7.2 本地容器运行"></a>7.2 本地容器运行</h4><p>使用 <code>agentcore launch --local</code> 命令在本地 Docker 容器中构建并运行 Agent：</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">agentcore launch --<span class="built_in">local</span></span><br></pre></td></tr></table></figure><p>此命令会自动：</p><ol><li>生成优化的 Dockerfile</li><li>构建容器镜像</li><li>启动容器并映射端口 8080</li><li>挂载本地 AWS 凭证到容器中</li><li>输出容器日志到终端</li></ol><p><strong>运行输出示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">🏠 Launching Bedrock AgentCore (local mode)...</span><br><span class="line">   • Build and run container locally</span><br><span class="line">   • Requires Docker/Finch/Podman to be installed</span><br><span class="line">   • Perfect for development and testing</span><br><span class="line"></span><br><span class="line">Launching Bedrock AgentCore agent &#x27;main&#x27; locally</span><br><span class="line">⠏ Launching Bedrock AgentCore...Docker image built: bedrock_agentcore-main:latest</span><br><span class="line">✓ Docker image built: bedrock_agentcore-main:latest</span><br><span class="line">✓ Ready to run locally</span><br><span class="line">Starting server at http://localhost:8080</span><br><span class="line">Press Ctrl+C to stop</span><br><span class="line"></span><br><span class="line">Configuration of aws_configurator not loaded, configurator already loaded</span><br><span class="line">Attempting to instrument while already instrumented</span><br><span class="line">INFO:     Started server process [1]</span><br><span class="line">INFO:     Waiting for application startup.</span><br><span class="line">INFO:     Application startup complete.</span><br><span class="line">INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)</span><br></pre></td></tr></table></figure><p>按 <code>Ctrl+C</code> 可以停止容器。</p><blockquote><p><strong>注意</strong>：<code>agentcore launch --local</code> 需要本地安装 Docker、Finch 或 Podman。</p></blockquote><h4 id="7-3-测试容器化-Agent"><a href="#7-3-测试容器化-Agent" class="headerlink" title="7.3 测试容器化 Agent"></a>7.3 测试容器化 Agent</h4><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">curl -X POST http://localhost:8080/invocations \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;用 Python 写一个快速排序算法&quot;&#125;&#x27;</span> | jq .</span><br></pre></td></tr></table></figure><p><strong>运行结果：</strong></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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;下面是 Python 实现的快速排序算法：</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def quicksort(arr):</span></span><br><span class="line"><span class="string">    # 基准情况：空列表或单元素列表已经有序</span></span><br><span class="line"><span class="string">    if len(arr) &lt;= 1:</span></span><br><span class="line"><span class="string">        return arr</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">    pivot = arr[len(arr) // 2]</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">    left = [x for x in arr if x &lt; pivot]</span></span><br><span class="line"><span class="string">    middle = [x for x in arr if x == pivot]</span></span><br><span class="line"><span class="string">    right = [x for x in arr if x &gt; pivot]</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">    return quicksort(left) + middle + quicksort(right)</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">- 时间复杂度：平均 O(n log n)，最坏 O(n²)</span></span><br><span class="line"><span class="string">- 空间复杂度：O(n)（使用了额外的列表）&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="7-4-本地容器运行的优势"><a href="#7-4-本地容器运行的优势" class="headerlink" title="7.4 本地容器运行的优势"></a>7.4 本地容器运行的优势</h4><p>使用 <code>agentcore launch --local</code> 相比直接 <code>uv run main.py</code> 有以下优势：</p><ol><li><strong>环境一致性</strong>：容器环境与云端 AgentCore Runtime 一致，避免”本地能跑，云端报错”的问题</li><li><strong>依赖隔离</strong>：所有依赖都在容器内，不会污染本地 Python 环境</li><li><strong>真实模拟</strong>：模拟生产环境的资源限制和网络配置</li><li><strong>快速验证</strong>：在部署到云端前，先在本地验证容器化后的行为</li></ol><h4 id="7-5-查看生成的-Dockerfile"><a href="#7-5-查看生成的-Dockerfile" class="headerlink" title="7.5 查看生成的 Dockerfile"></a>7.5 查看生成的 Dockerfile</h4><p>如果您想了解 CLI 生成的 Dockerfile 内容，可以查看：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> .agentcore/Dockerfile</span><br></pre></td></tr></table></figure><p><strong>生成的 Dockerfile 示例：</strong></p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="keyword">FROM</span> public.ecr.aws/docker/library/python:<span class="number">3.13</span>-slim</span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装系统依赖</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y --no-install-recommends \</span></span><br><span class="line"><span class="language-bash">    curl \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 uv 包管理器</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> curl -LsSf https://astral.sh/uv/install.sh | sh</span></span><br><span class="line"><span class="keyword">ENV</span> PATH=<span class="string">&quot;/root/.local/bin:$PATH&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制依赖文件并安装</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> pyproject.toml uv.lock ./</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> uv <span class="built_in">sync</span> --frozen</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制应用代码</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 暴露端口</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动命令</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;uv&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;main.py&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>CLI 自动生成的 Dockerfile 遵循最佳实践，包括多阶段构建优化、依赖缓存等。</p><h3 id="第七部分：部署到云端"><a href="#第七部分：部署到云端" class="headerlink" title="第七部分：部署到云端"></a>第七部分：部署到云端</h3><p>本地开发和容器化测试完成后，可以使用 AgentCore Starter Toolkit 将 Agent 部署到 AWS。</p><h4 id="7-1-使用-CLI-部署"><a href="#7-1-使用-CLI-部署" class="headerlink" title="7.1 使用 CLI 部署"></a>7.1 使用 CLI 部署</h4><p>如果您在第六部分已经执行过 <code>agentcore configure</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">agentcore configure -e main.py</span><br></pre></td></tr></table></figure><p><strong>部署 Agent：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">agentcore launch</span><br></pre></td></tr></table></figure><p>此命令会（默认使用 CodeBuild 云端构建）：</p><ol><li>使用 AWS CodeBuild 构建 ARM64 容器镜像</li><li>将镜像推送到 ECR</li><li>创建 AgentCore Runtime</li><li>等待 Runtime 就绪</li></ol><p><strong>部署模式说明：</strong></p><table><thead><tr><th>命令</th><th>构建位置</th><th>运行位置</th><th>说明</th></tr></thead><tbody><tr><td><code>agentcore launch</code></td><td>云端 (CodeBuild)</td><td>云端 (AgentCore Runtime)</td><td>推荐，无需本地 Docker</td></tr><tr><td><code>agentcore launch --local</code></td><td>本地</td><td>本地</td><td>本地开发测试</td></tr><tr><td><code>agentcore launch --local-build</code></td><td>本地</td><td>云端</td><td>需要自定义构建时使用</td></tr></tbody></table><p><strong>测试云端 Agent：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">agentcore invoke <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;你好，这是来自云端的测试&quot;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p><strong>查看状态：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">agentcore status</span><br></pre></td></tr></table></figure><h4 id="7-2-使用-Python-SDK-部署"><a href="#7-2-使用-Python-SDK-部署" class="headerlink" title="7.2 使用 Python SDK 部署"></a>7.2 使用 Python SDK 部署</h4><p>对于需要在 Jupyter Notebook 或 Python 脚本中进行更多控制的场景，可以使用 Python SDK：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bedrock_agentcore_starter_toolkit <span class="keyword">import</span> Runtime</span><br><span class="line"><span class="keyword">from</span> boto3.session <span class="keyword">import</span> Session</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前区域</span></span><br><span class="line">session = Session()</span><br><span class="line">region = session.region_name</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 Runtime 实例</span></span><br><span class="line">runtime = Runtime()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置部署参数</span></span><br><span class="line">runtime.configure(</span><br><span class="line">    entrypoint=<span class="string">&quot;main.py&quot;</span>,</span><br><span class="line">    auto_create_execution_role=<span class="literal">True</span>,</span><br><span class="line">    auto_create_ecr=<span class="literal">True</span>,</span><br><span class="line">    region=region,</span><br><span class="line">    agent_name=<span class="string">&quot;my_production_agent&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行部署（默认使用 CodeBuild 云端构建）</span></span><br><span class="line">runtime.launch()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;部署成功！&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用云端 Agent</span></span><br><span class="line">response = runtime.invoke(&#123;</span><br><span class="line">    <span class="string">&quot;prompt&quot;</span>: <span class="string">&quot;请介绍一下 Amazon Bedrock AgentCore&quot;</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;响应: <span class="subst">&#123;response&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理资源请使用 CLI: agentcore destroy</span></span><br></pre></td></tr></table></figure><p><strong>配置输出示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Bedrock AgentCore configured: /path/to/project/.bedrock_agentcore.yaml</span><br><span class="line">🚀 CodeBuild mode: building in cloud (RECOMMENDED - DEFAULT)</span><br><span class="line">• Build ARM64 containers in the cloud with CodeBuild</span><br><span class="line">• No local Docker required</span><br></pre></td></tr></table></figure><p><strong>部署模式说明：</strong></p><p>Python SDK 支持与 CLI 相同的三种部署模式：</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="comment"># 默认模式：CodeBuild 云端构建 + 云端部署（推荐）</span></span><br><span class="line">runtime.launch()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 本地开发模式：本地构建 + 本地运行</span></span><br><span class="line">runtime.launch(local=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 混合模式：本地构建 + 云端部署</span></span><br><span class="line">runtime.launch(local_build=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><h4 id="7-3-清理资源"><a href="#7-3-清理资源" class="headerlink" title="7.3 清理资源"></a>7.3 清理资源</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 预览将要删除的资源（不实际删除）</span></span><br><span class="line">agentcore destroy --dry-run</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除所有资源（保留 ECR 仓库，只删除镜像）</span></span><br><span class="line">agentcore destroy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 同时删除 ECR 仓库</span></span><br><span class="line">agentcore destroy --delete-ecr-repo</span><br><span class="line"></span><br><span class="line"><span class="comment"># 跳过确认提示，直接删除</span></span><br><span class="line">agentcore destroy --force</span><br></pre></td></tr></table></figure><h3 id="第八部分：常见问题与解决方案"><a href="#第八部分：常见问题与解决方案" class="headerlink" title="第八部分：常见问题与解决方案"></a>第八部分：常见问题与解决方案</h3><h4 id="8-1-端口占用"><a href="#8-1-端口占用" class="headerlink" title="8.1 端口占用"></a>8.1 端口占用</h4><p><strong>问题：</strong> 启动时提示端口 8080 已被占用</p><p><strong>解决方案：</strong></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"># 查找占用端口的进程</span></span><br><span class="line">lsof -i :8080</span><br><span class="line"></span><br><span class="line"><span class="comment"># 终止进程（将 PID 替换为实际进程 ID）</span></span><br><span class="line"><span class="built_in">kill</span> -9 &lt;PID&gt;</span><br></pre></td></tr></table></figure><h4 id="8-2-模型访问权限"><a href="#8-2-模型访问权限" class="headerlink" title="8.2 模型访问权限"></a>8.2 模型访问权限</h4><p><strong>问题：</strong> 调用时报 AccessDeniedException</p><p><strong>解决方案：</strong></p><ol><li>确认 AWS 凭证有效：<code>aws sts get-caller-identity</code></li><li>确认已在 Bedrock 控制台开启模型访问权限</li><li>确认使用的区域支持所选模型</li></ol><h4 id="8-3-工具未被调用"><a href="#8-3-工具未被调用" class="headerlink" title="8.3 工具未被调用"></a>8.3 工具未被调用</h4><p><strong>问题：</strong> Agent 没有使用定义的工具</p><p><strong>解决方案：</strong></p><ol><li>检查 <code>@tool</code> 装饰器是否正确应用</li><li>确保 docstring 清晰描述了工具的用途</li><li>在 system_prompt 中明确说明可用的工具</li><li>检查工具是否已添加到 Agent 的 <code>tools</code> 列表</li></ol><h4 id="8-4-部署超时"><a href="#8-4-部署超时" class="headerlink" title="8.4 部署超时"></a>8.4 部署超时</h4><p><strong>问题：</strong> <code>agentcore launch</code> 执行时间过长</p><p><strong>解决方案：</strong></p><ol><li>检查网络连接是否稳定</li><li>确认 IAM 权限包含 CodeBuild、ECR、Bedrock 相关权限</li><li>查看 CodeBuild 控制台中的构建日志</li></ol><h4 id="8-5-本地容器构建失败：Multiple-top-level-modules"><a href="#8-5-本地容器构建失败：Multiple-top-level-modules" class="headerlink" title="8.5 本地容器构建失败：Multiple top-level modules"></a>8.5 本地容器构建失败：Multiple top-level modules</h4><p><strong>问题：</strong> 执行 <code>agentcore launch --local</code> 时报错：</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">Multiple top-level modules discovered in a flat-layout: [&#x27;main&#x27;, &#x27;mcp_server&#x27;, &#x27;a2a_server&#x27;, ...]</span><br></pre></td></tr></table></figure><p><strong>原因：</strong> 项目目录中有多个 Python 文件（如 <code>main.py</code>、<code>mcp_server.py</code>、<code>a2a_server.py</code> 等），setuptools 无法确定哪个是主模块。</p><p><strong>解决方案：</strong></p><p>在 <code>pyproject.toml</code> 中添加 <code>[tool.setuptools]</code> 配置，明确指定要打包的模块：</p><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><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="section">[project]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;my-agent-project&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">description</span> = <span class="string">&quot;Add your description here&quot;</span></span><br><span class="line"><span class="attr">readme</span> = <span class="string">&quot;README.md&quot;</span></span><br><span class="line"><span class="attr">requires-python</span> = <span class="string">&quot;&gt;=3.13&quot;</span></span><br><span class="line"><span class="attr">dependencies</span> = [</span><br><span class="line">    <span class="string">&quot;bedrock-agentcore&gt;=1.1.1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;strands-agents&gt;=1.19.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;strands-agents-tools&gt;=0.2.17&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[dependency-groups]</span></span><br><span class="line"><span class="attr">dev</span> = [</span><br><span class="line">    <span class="string">&quot;bedrock-agentcore-starter-toolkit&gt;=0.2.4&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="section">[tool.setuptools]</span></span><br><span class="line"><span class="attr">py-modules</span> = [<span class="string">&quot;main&quot;</span>]  <span class="comment"># 只打包 main.py 作为主模块</span></span><br></pre></td></tr></table></figure><p>添加 <code>[tool.setuptools]</code> 部分后，重新运行 <code>agentcore launch --local</code> 即可。</p><h4 id="8-6-本地容器运行时-OTLP-导出错误"><a href="#8-6-本地容器运行时-OTLP-导出错误" class="headerlink" title="8.6 本地容器运行时 OTLP 导出错误"></a>8.6 本地容器运行时 OTLP 导出错误</h4><p><strong>问题：</strong> 执行 <code>agentcore launch --local</code> 后，日志中出现大量重试警告：</p><figure class="highlight plaintext"><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">Transient error StatusCode.UNAVAILABLE encountered while exporting traces to localhost:4317, retrying in 1s.</span><br><span class="line">Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 2s.</span><br></pre></td></tr></table></figure><p><strong>原因：</strong> 容器内的 OpenTelemetry 尝试将追踪数据发送到 <code>localhost:4317</code>（默认 OTLP 端点），但本地没有运行 OTLP 接收服务。</p><p><strong>解决方案：</strong></p><p>这个警告不影响 Agent 正常运行，可以忽略。如果想消除警告，可以禁用 OTLP 导出：</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">agentcore launch --<span class="built_in">local</span> \</span><br><span class="line">  --<span class="built_in">env</span> OTEL_TRACES_EXPORTER=none \</span><br><span class="line">  --<span class="built_in">env</span> OTEL_LOGS_EXPORTER=none</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>本文详细介绍了 Amazon Bedrock AgentCore 的本地开发流程：</p><ol><li><strong>环境搭建</strong>：安装 uv、配置 AWS 凭证、创建项目</li><li><strong>基础 Agent</strong>：使用 BedrockAgentCoreApp 和 Strands Agent 构建简单的对话 Agent</li><li><strong>工具集成</strong>：通过 @tool 装饰器定义自定义工具，扩展 Agent 能力</li><li><strong>MCP Server</strong>：构建 MCP 服务，支持工具发现和调用</li><li><strong>A2A Server</strong>：构建支持 Agent 间通信的服务</li><li><strong>本地容器化</strong>：使用 <code>agentcore launch --local</code> 在本地模拟生产环境</li><li><strong>云端部署</strong>：使用 <code>agentcore launch</code> 一键部署到 AWS</li></ol><p>通过本文的学习，您已经掌握了从本地开发到容器化测试，再到云端部署的完整流程。这种渐进式的开发方式能够帮助您：</p><ul><li>在本地快速验证 Agent 逻辑</li><li>通过容器化确保环境一致性</li><li>无缝迁移到 AgentCore Runtime 生产环境</li></ul><p>本地开发与云端部署的无缝衔接，使开发者能够在保持高效迭代的同时，确保代码在生产环境中的可靠运行。</p><h3 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h3><ul><li>Amazon Bedrock AgentCore 官方文档：<a href="https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html">https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html</a></li><li>Strands Agents SDK 文档：<a href="https://strandsagents.com/">https://strandsagents.com/</a></li><li>MCP 协议规范：<a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io/</a></li><li>A2A 协议规范：<a href="https://google.github.io/A2A/">https://google.github.io/A2A/</a></li><li>AgentCore Starter Toolkit：<a href="https://github.com/aws/bedrock-agentcore-starter-toolkit">https://github.com/aws/bedrock-agentcore-starter-toolkit</a></li><li>AgentCore Python SDK：<a href="https://github.com/aws/bedrock-agentcore-sdk-python">https://github.com/aws/bedrock-agentcore-sdk-python</a></li><li>A2A 协议规范：<a href="https://a2a-protocol.org/">https://a2a-protocol.org/</a></li></ul>]]></content>
    
    
    <summary type="html">使用 Amazon Bedrock AgentCore 在本地构建和调试 AI Agent 的实战教程</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>使用agentcore部署MCP server</title>
    <link href="https://blog.no-claw.com/posts/56feb9d1/"/>
    <id>https://blog.no-claw.com/posts/56feb9d1/</id>
    <published>2025-12-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>agentcore除了可以部署agentcore应用程序之外，还可以部署MCP server。<br>换句话说，只要提供了容器镜像，ping和，那么就可以部署任意的应用程序，上面两个是比较典型的例子。</p><p>常规的MCP server 长这样，</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"></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"></span><br></pre></td></tr></table></figure><p>默认会使用Agentcore runtime来启动，和sagemaker一样，需要有 &#x2F;ping 和 &#x2F;invokation的路由，并且容器host在8080端口，启动之后服务就会自动加一个网关来转发到这个应用的8080端口。</p><p>你可能还听见过Agentcore Gateway，这个是给一些其他的MCP server，Rest api或者。<br>甚至是apigateway和lambda。</p><p>启动之后，我们就会得到一个URL。</p><p>agentcore runtime的是</p><p>由于在url有arn，所以需要做一个http的转译，否则有些MCP的客户端可能无法处理识别这个。</p><p>agentcore gateway的是。</p><p>然后就是认证，Agentcore runtime接受JWT和IAM两种认证方式</p><p>而gateway除了这几种之外还可以接受无认证的方式。</p><p>这里我们重点介绍JWT的认证，一般是用于和SSO的集成，而这里的JWT通常指的是access token。</p><p>对于登陆系统而言，一开始使用用户名和密码登录之后，后端会返回一个JWT来代替用户的身份，然后我们把这个加在请求头里</p><p>请求头通常会这样写：</p><p><code>Authorization: Bearer &lt;token&gt;</code></p><ul><li><p><code>Authorization</code> 是 HTTP 标头中的一个字段。</p></li><li><p><code>Bearer</code> 表示这是一个 Bearer token 类型。</p></li><li><p><code>&lt;token&gt;</code> 是实际的访问令牌。</p></li></ul><p>例如，假设你获得了一个 <code>access token</code>，它可能长成这个样子：</p><p><code>Authorization: Bearer abcdefghijklmnopqrstuvwxyz1234567890</code></p><h3 id="具体流程："><a href="#具体流程：" class="headerlink" title="具体流程："></a>具体流程：</h3><ol><li><p><strong>登录</strong>：用户通过用户名和密码登录后，身份提供者（如身份认证服务器）生成一个 <code>access token</code>，并将其返回给客户端（例如，浏览器、移动应用）。</p></li><li><p><strong>访问资源</strong>：客户端在需要访问受保护资源时（例如，API），会在请求中添加 <code>Authorization: Bearer &lt;token&gt;</code> 头部。这样，服务器就能知道这个请求背后是一个已经认证并授权的用户。</p></li><li><p><strong>服务器验证</strong>：服务器接收到请求后，验证 <code>Bearer token</code> 是否有效。如果有效，服务器根据 token 中的权限信息返回相应的资源；如果无效或已过期，则拒绝访问。</p></li></ol>]]></content>
    
    
    <summary type="html">使用agentcore部署MCP server</summary>
    
    
    
    <category term="MCP" scheme="https://blog.no-claw.com/categories/MCP/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/MCP/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>Milvus Workshop Web 版上线：4 万 Star 项目的官方实战教程，在手机上也能直接学了</title>
    <link href="https://blog.no-claw.com/posts/581baa02/"/>
    <id>https://blog.no-claw.com/posts/581baa02/</id>
    <published>2025-12-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Milvus 作为全球最受欢迎的开源向量数据库，GitHub Star 数已突破 <strong>4 万</strong>。</p><p>向量数据库已经成为 AI 应用的核心基础设施。RAG 需要它存储知识库，Agent 需要它实现记忆，推荐系统需要它计算相似度，多模态搜索需要它做特征检索。从实验室到生产环境，向量检索已经是 AI 应用的标配能力。</p><p>但从入门到真正用好 Milvus，这条路并不短。</p><p>分布式架构需要理解——Proxy、Coord、Node 各司其职，一条查询请求在系统内部如何流转？Schema 怎么设计、Chunk 策略怎么选、混合搜索怎么配？与 LangChain、LangGraph 怎么集成？生产环境更是另一个战场——内存优化、写入调优、慢查询排查，每一个都是实打实的工程问题。</p><p>为了系统性地解决这些问题，我们整理了这份 <strong>《Milvus Workshop：从入门到应用》</strong>。</p><p>这份教程已经在多场线下 Workshop 中经过验证，帮助数百位开发者快速上手 Milvus。现在，我们上线了网页版——打开浏览器就能学，手机、平板、电脑无缝切换。</p><span id="more"></span><h3 id="这份教程适合谁？"><a href="#这份教程适合谁？" class="headerlink" title="这份教程适合谁？"></a>这份教程适合谁？</h3><ul><li><strong>向量数据库新手</strong>：想系统入门，建立完整的知识体系</li><li><strong>RAG&#x2F;Agent 开发者</strong>：需要将 Milvus 集成到项目中，希望少走弯路</li><li><strong>准备上生产的团队</strong>：开发环境已经跑通，需要了解生产环境的注意事项</li><li><strong>想深入理解 Milvus 底层的工程师</strong>：不满足于会用 API，想搞清楚底层原理</li></ul><h3 id="Web-版上线：降低学习门槛"><a href="#Web-版上线：降低学习门槛" class="headerlink" title="Web 版上线：降低学习门槛"></a>Web 版上线：降低学习门槛</h3><p>之前这份教程只有 GitHub 仓库版本，需要 clone 下来用 Jupyter 打开。代码能跑，但阅读体验受限，尤其在移动端几乎无法使用。</p><p>现在我们用 GitHub Actions 自动构建了 Web 版本：</p><ul><li><strong>零配置访问</strong>：无需 clone 仓库，无需配置 Python&#x2F;Jupyter 环境，浏览器直接阅读</li><li><strong>移动端适配</strong>：响应式设计，手机、平板、电脑无缝切换</li><li><strong>自动同步更新</strong>：仓库更新后网站自动重新构建</li></ul><p>通勤时间看架构原理，工作时间跑代码实操，学习效率显著提升。</p><h3 id="教程结构"><a href="#教程结构" class="headerlink" title="教程结构"></a>教程结构</h3><p>整个 Workshop 分为四大模块，每章都以 Jupyter Notebook 格式呈现，所有代码可直接运行。同时也有对应的 Web 版本。</p><p>👉 <strong>想直接开始？</strong> <a href="https://airag.click/milvus-workshop/">点击这里打开教程</a></p><h3 id="Part-1：核心概念与架构原理"><a href="#Part-1：核心概念与架构原理" class="headerlink" title="Part 1：核心概念与架构原理"></a>Part 1：核心概念与架构原理</h3><h4 id="1-1-向量数据库的本质"><a href="#1-1-向量数据库的本质" class="headerlink" title="1.1 向量数据库的本质"></a>1.1 向量数据库的本质</h4><p>传统数据库擅长精确匹配：给定 ID 查记录，给定条件筛数据。但面对”哪些数据与这个最相似”这类问题，传统方案力不从心。</p><p>向量嵌入（Vector Embedding）将语义转化为数学表示。通过 Embedding 模型（如 BGE、E5、OpenAI text-embedding-3 等），文本、图片、音频等非结构化数据被映射为高维向量。语义相近的内容，向量空间中的距离也相近。</p><p>向量数据库的核心任务：<strong>在海量向量中高效检索最相似的 Top-K 结果</strong>。</p><p>精确的最近邻搜索需要遍历所有向量，数据量越大查询越慢，无法满足大规模数据的性能要求。ANN（Approximate Nearest Neighbor）通过构建索引结构，以可接受的精度损失换取数量级的性能提升。</p><h4 id="1-2-索引类型对比"><a href="#1-2-索引类型对比" class="headerlink" title="1.2 索引类型对比"></a>1.2 索引类型对比</h4><table><thead><tr><th>索引类型</th><th>技术特点</th><th>适用场景</th></tr></thead><tbody><tr><td>FLAT（暴力搜索）</td><td>精度 100%，无近似误差</td><td>小数据量、精度要求极高</td></tr><tr><td>IVF 系列（IVF_FLAT &#x2F; IVF_SQ8 &#x2F; IVF_PQ）</td><td>聚类分桶，量化压缩</td><td>中等精度、中等吞吐</td></tr><tr><td>HNSW</td><td>多层图结构，低延迟</td><td>高吞吐 + 高召回场景，适合推荐、搜索</td></tr><tr><td>DiskANN</td><td>基于图的磁盘索引</td><td>大数据量，有限内存场景，牺牲部分延迟</td></tr><tr><td>GPU_IVF 系列</td><td>GPU 加速版 IVF</td><td>高吞吐、高并发场景，极致性能优化</td></tr><tr><td>BIN_FLAT &#x2F; BIN_IVF_FLAT</td><td>二进制向量支持</td><td>文本、音频哈希类场景</td></tr></tbody></table><p>IVF 系列是经典的聚类索引，通过将向量分桶实现快速检索，配合量化压缩可显著降低内存占用。HNSW 基于 Hierarchical Navigable Small World 图结构，查询性能优异，是当前最流行的索引类型之一。DiskANN 则适合内存受限但数据量大的场景，是性价比之选。</p><h4 id="1-3-Milvus-分布式架构"><a href="#1-3-Milvus-分布式架构" class="headerlink" title="1.3 Milvus 分布式架构"></a>1.3 Milvus 分布式架构</h4><p>Milvus 采用存储计算分离的云原生架构，分为四层：</p><p><strong>Access Layer</strong>：Proxy 节点负责接收请求、参数校验、路由分发。无状态设计，支持水平扩展。</p><p><strong>Coordinator Layer</strong>：集群协调中心，包含四个组件：</p><ul><li>Root Coord：管理集群拓扑、DDL 操作、时间戳分配</li><li>Data Coord：协调数据写入流程，管理 Segment 分配</li><li>Query Coord：管理查询负载均衡，决定数据加载策略</li><li>Index Coord：调度索引构建任务</li></ul><p><strong>Worker Layer</strong>：执行层节点：</p><ul><li>Data Node：处理数据写入和持久化</li><li>Query Node：加载数据和索引，执行搜索查询</li><li>Index Node：执行索引构建任务</li></ul><p><strong>Storage Layer</strong>：</p><ul><li>Meta Storage：etcd 存储元数据</li><li>Log Broker：Pulsar&#x2F;Kafka 作为 WAL</li><li>Object Storage：MinIO&#x2F;S3 存储数据文件</li></ul><p>Milvus 2.6 引入了 Streaming Node，专门处理实时数据流，进一步优化写入性能。</p><p>理解这套架构，对后续的问题排查和性能调优至关重要。</p><h3 id="Part-2：Python-SDK-实战"><a href="#Part-2：Python-SDK-实战" class="headerlink" title="Part 2：Python SDK 实战"></a>Part 2：Python SDK 实战</h3><h4 id="2-1-Schema-设计要点"><a href="#2-1-Schema-设计要点" class="headerlink" title="2.1 Schema 设计要点"></a>2.1 Schema 设计要点</h4><p>创建 Collection 时的关键决策：</p><ul><li><strong>主键类型</strong>：Int64 或 VarChar，是否启用 auto_id</li><li><strong>向量维度</strong>：取决于 Embedding 模型（BGE-M3 1024，OpenAI text-embedding-3-small 1536，E5-large 1024）</li><li><strong>标量字段</strong>：确定哪些属性需要用于过滤查询</li></ul><p>Schema 设计直接影响后续的查询效率和灵活性，建议在编码前充分规划。</p><h4 id="2-2-HNSW-索引参数"><a href="#2-2-HNSW-索引参数" class="headerlink" title="2.2 HNSW 索引参数"></a>2.2 HNSW 索引参数</h4><p>三个关键参数：</p><ul><li><strong>M</strong>：每个节点的最大连接数，通常取 8-64，越大召回率越高但内存占用也越大</li><li><strong>efConstruction</strong>：构建时的搜索宽度，通常取 100-500，越大索引质量越好但构建时间越长</li><li><strong>ef</strong>：查询时的候选集大小，越大召回率越高但查询越慢</li></ul><p>参数配置对性能影响显著，建议通过基准测试确定最优值。</p><h4 id="2-3-搜索方式"><a href="#2-3-搜索方式" class="headerlink" title="2.3 搜索方式"></a>2.3 搜索方式</h4><p><strong>向量搜索</strong>：</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">results = collection.search(</span><br><span class="line">    data=[query_vector],</span><br><span class="line">    anns_field=<span class="string">&quot;embedding&quot;</span>,</span><br><span class="line">    search_params=&#123;<span class="string">&quot;metric_type&quot;</span>: <span class="string">&quot;COSINE&quot;</span>, <span class="string">&quot;params&quot;</span>: &#123;<span class="string">&quot;ef&quot;</span>: <span class="number">64</span>&#125;&#125;,</span><br><span class="line">    limit=<span class="number">10</span>,</span><br><span class="line">    output_fields=[<span class="string">&quot;title&quot;</span>, <span class="string">&quot;content&quot;</span>]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>带标量过滤的搜索</strong>：</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">results = collection.search(</span><br><span class="line">    data=[query_vector],</span><br><span class="line">    anns_field=<span class="string">&quot;embedding&quot;</span>,</span><br><span class="line">    search_params=&#123;<span class="string">&quot;metric_type&quot;</span>: <span class="string">&quot;COSINE&quot;</span>, <span class="string">&quot;params&quot;</span>: &#123;<span class="string">&quot;ef&quot;</span>: <span class="number">64</span>&#125;&#125;,</span><br><span class="line">    limit=<span class="number">10</span>,</span><br><span class="line">    <span class="built_in">filter</span>=<span class="string">&quot;category == &#x27;tech&#x27; and publish_date &gt; &#x27;2024-01-01&#x27;&quot;</span>,</span><br><span class="line">    output_fields=[<span class="string">&quot;title&quot;</span>, <span class="string">&quot;content&quot;</span>]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>混合搜索</strong>：Milvus 2.4+ 支持同时使用稠密向量和稀疏向量（BM25），结合语义相似性和关键词匹配，通常能获得更好的检索效果。</p><h3 id="Part-3：应用场景实战"><a href="#Part-3：应用场景实战" class="headerlink" title="Part 3：应用场景实战"></a>Part 3：应用场景实战</h3><h4 id="3-1-跨模态图片搜索"><a href="#3-1-跨模态图片搜索" class="headerlink" title="3.1 跨模态图片搜索"></a>3.1 跨模态图片搜索</h4><p>CLIP 模型能够将图片和文本映射到同一向量空间，实现跨模态检索。</p><p>实现流程：</p><ol><li>离线阶段：使用 CLIP 图像编码器将图片库转换为向量，存入 Milvus</li><li>在线阶段：用户输入文本描述，使用 CLIP 文本编码器生成向量，检索最相似的图片</li></ol><p>教程提供完整代码，涵盖模型加载、批量处理、检索实现。</p><h4 id="3-2-RAG-检索增强生成"><a href="#3-2-RAG-检索增强生成" class="headerlink" title="3.2 RAG 检索增强生成"></a>3.2 RAG 检索增强生成</h4><p>RAG 是当前最主流的 LLM 应用模式，核心思路：<strong>先检索相关知识，再基于检索结果生成回答</strong>。</p><p>完整流程：</p><ol><li><strong>文档分块</strong>：将长文档切分为 500-1000 字符的片段</li><li><strong>向量化</strong>：使用 Embedding 模型将每个片段转换为向量</li><li><strong>存储</strong>：将向量和原文存入 Milvus</li><li><strong>检索</strong>：用户提问时，将问题向量化，检索最相关的片段</li><li><strong>生成</strong>：将检索结果作为上下文，由 LLM 生成回答</li></ol><p>以下是 LangChain 集成示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 检索相关文档</span></span><br><span class="line">docs = vectorstore.similarity_search(query, k=<span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建上下文</span></span><br><span class="line">context = <span class="string">&quot;\n&quot;</span>.join([doc.page_content <span class="keyword">for</span> doc <span class="keyword">in</span> docs])</span><br><span class="line">prompt = <span class="string">f&quot;基于以下内容回答问题：\n<span class="subst">&#123;context&#125;</span>\n\n问题：<span class="subst">&#123;query&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成回答</span></span><br><span class="line">response = llm.invoke(prompt)</span><br></pre></td></tr></table></figure><p>关键优化点：</p><ul><li><strong>Chunk 策略</strong>：片段过小会丢失上下文，过大会引入噪音</li><li><strong>Embedding 模型选择</strong>：BGE、E5、OpenAI text-embedding-3 各有特点，需根据场景选择</li><li><strong>混合搜索</strong>：结合稠密向量和 BM25 稀疏向量，提升专有名词和关键词的检索效果</li><li><strong>Rerank</strong>：使用 Cross-Encoder 对初筛结果重排序，进一步提升相关性</li></ul><p>教程使用 LangChain 实现完整流程，并详细讨论这些优化策略。</p><h4 id="3-3-AI-Agent-记忆系统"><a href="#3-3-AI-Agent-记忆系统" class="headerlink" title="3.3 AI Agent 记忆系统"></a>3.3 AI Agent 记忆系统</h4><p>Agent 的三大核心能力：规划（Planning）、记忆（Memory）、工具（Tools）。Milvus 在记忆系统中发挥关键作用。</p><p><strong>短期记忆</strong>：当前会话的上下文信息，支持对话过程中的信息检索。</p><p><strong>长期记忆</strong>：历史对话、执行经验、学习成果的持久化存储。遇到相似场景时，Agent 可以检索相关记忆，做出更优决策。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 存储对话记忆</span></span><br><span class="line">memory_vector = embedding_model.encode(conversation_summary)</span><br><span class="line">collection.insert(&#123;</span><br><span class="line">    <span class="string">&quot;vector&quot;</span>: memory_vector,</span><br><span class="line">    <span class="string">&quot;content&quot;</span>: conversation_summary,</span><br><span class="line">    <span class="string">&quot;timestamp&quot;</span>: datetime.now().isoformat(),</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;conversation&quot;</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">relevant_memories = collection.search(</span><br><span class="line">    data=[current_query_vector],</span><br><span class="line">    anns_field=<span class="string">&quot;vector&quot;</span>,</span><br><span class="line">    limit=<span class="number">5</span>,</span><br><span class="line">    <span class="built_in">filter</span>=<span class="string">&quot;type == &#x27;conversation&#x27;&quot;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>教程使用 LangGraph 实现带记忆功能的 Agent，演示记忆的存储、检索和应用。</p><h3 id="Part-4：生产环境运维"><a href="#Part-4：生产环境运维" class="headerlink" title="Part 4：生产环境运维"></a>Part 4：生产环境运维</h3><h4 id="4-1-可观测性方案"><a href="#4-1-可观测性方案" class="headerlink" title="4.1 可观测性方案"></a>4.1 可观测性方案</h4><p>Milvus 原生支持 Prometheus 指标暴露。教程提供完整的可观测性方案：</p><ul><li>Prometheus：采集各组件指标</li><li>Loki：日志收集</li><li>Jaeger：分布式链路追踪</li><li>Grafana：可视化仪表盘</li></ul><p>提供开箱即用的 Dashboard JSON，涵盖 QPS、延迟、内存使用、Segment 状态等关键指标。</p><h4 id="4-2-性能基准测试"><a href="#4-2-性能基准测试" class="headerlink" title="4.2 性能基准测试"></a>4.2 性能基准测试</h4><p>VectorDBBench 是 Zilliz 开源的向量数据库基准测试工具。上线前建议进行完整的性能测试，了解系统的 QPS 上限和延迟分布。</p><h4 id="4-3-性能调优指南"><a href="#4-3-性能调优指南" class="headerlink" title="4.3 性能调优指南"></a>4.3 性能调优指南</h4><p><strong>内存优化</strong>：</p><ul><li>调整 <code>dataNode.segment.maxSize</code> 控制 Segment 大小</li><li>使用 IVF_PQ 等压缩索引减少内存占用</li><li>按需 Load Partition，避免加载不必要的数据</li></ul><p><strong>写入优化</strong>：</p><ul><li>批量插入，每批 1000-10000 条</li><li>大数据量场景使用 Bulk Import</li><li>合理配置 <code>dataNode.flush.insertBufSize</code></li></ul><p><strong>查询优化</strong>：</p><ul><li>根据数据规模选择合适的索引类型和参数</li><li>优化标量过滤条件，避免全表扫描</li><li>合理使用 Partition 缩小查询范围</li></ul><h4 id="4-4-版本升级"><a href="#4-4-版本升级" class="headerlink" title="4.4 版本升级"></a>4.4 版本升级</h4><p>教程包含 Milvus 2.5 到 2.6 的升级指南，涵盖升级步骤和注意事项。</p><h3 id="获取方式"><a href="#获取方式" class="headerlink" title="获取方式"></a>获取方式</h3><table><thead><tr><th></th><th>传统方式</th><th>Web 版</th></tr></thead><tbody><tr><td>环境要求</td><td>Git + Python + Jupyter</td><td>浏览器</td></tr><tr><td>移动端体验</td><td>受限</td><td>完整支持</td></tr><tr><td>内容更新</td><td>手动 pull</td><td>自动同步</td></tr></tbody></table><p>📖 <strong>在线阅读</strong>：<a href="https://airag.click/milvus-workshop/">https://airag.click/milvus-workshop/</a></p><p>💻 <strong>GitHub 仓库</strong>：<a href="https://github.com/richzw/milvus-workshop">https://github.com/richzw/milvus-workshop</a></p><p>教程提供中英双语版本，欢迎 Star ⭐ 支持。我们将持续更新更多实战案例和最佳实践。</p><p>向量数据库的时代已经到来。无论是入门学习还是生产实践，这份教程都能提供有价值的参考。</p><p>Happy hacking 🚀</p>]]></content>
    
    
    <summary type="html">Milvus 官方实战教程 Web 版上线，4 万 Star 向量数据库从入门到生产环境的完整学习路径。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="向量数据库" scheme="https://blog.no-claw.com/tags/%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十三）：网络设置中不显示我的WIFI怎么办?</title>
    <link href="https://blog.no-claw.com/posts/ff23bf7c/"/>
    <id>https://blog.no-claw.com/posts/ff23bf7c/</id>
    <published>2025-11-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>折腾网络这件事，有时候比修电脑还玄学。</p><p>前几天测试家里的网络，顺手重置了一下路由器。结果没想到，懒猫微服的 Wi-Fi 设置也跟着丢了。</p><p>打开网络设置一看，满屏都是邻居家的热点，就是没有我自己的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bda486f6b774171f01e24490a13e58b3.png" alt="bda486f6b774171f01e24490a13e58b3"></p><span id="more"></span><p>没关系，懒猫毕竟是 Linux 系统，只要能够 开启 SSH，命令行就是万能钥匙。反正有分层文件系统，真要捣鼓坏了，重启也能恢复。</p><p>这次的主角是 <code>nmcli</code>，全称 <strong>NetworkManager Command Line Interface</strong>，它是 Linux 自带的网络管理命令行工具。<br>通俗点说，它是前端界面的“幕后操控者”——我们在图形界面上点击的“网络连接”“Wi-Fi 设置”，其实都在底层调用它。</p><p>有了 <code>nmcli</code>，我们几乎可以用命令完成所有网络操作：</p><table><thead><tr><th>功能</th><th>命令示例</th></tr></thead><tbody><tr><td>列出可用 Wi-Fi 热点</td><td><code>nmcli device wifi list</code></td></tr><tr><td>连接 Wi-Fi</td><td><code>nmcli device wifi connect &quot;SSID&quot; password &quot;12345678&quot;</code></td></tr><tr><td>查看当前网络状态</td><td><code>nmcli connection show --active</code></td></tr><tr><td>启用&#x2F;禁用网卡</td><td><code>nmcli device set wlan0 managed yes/no</code></td></tr><tr><td>断开网络连接</td><td><code>nmcli connection down id &quot;MyWiFi&quot;</code></td></tr></tbody></table><h3 id="扫描-Wi-Fi"><a href="#扫描-Wi-Fi" class="headerlink" title="扫描 Wi-Fi"></a>扫描 Wi-Fi</h3><p>先 SSH 登录懒猫微服，直接使用 <code>nmcli</code> 扫描周边的 Wi-Fi：</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">nmcli device wifi list</span><br></pre></td></tr></table></figure><p>这条命令会列出当前设备能检测到的所有 Wi-Fi 热点。<br>果然，我的 Wi-Fi 就静静地躺在输出列表里，只是前端页面没显示出来而已。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cb38944751cdb5fe1c553c464e608ec9.png" alt="cb38944751cdb5fe1c553c464e608ec9"></p><h3 id="手动连接-Wi-Fi"><a href="#手动连接-Wi-Fi" class="headerlink" title="手动连接 Wi-Fi"></a>手动连接 Wi-Fi</h3><p>既然能看到 SSID，那就直接连接：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nmcli device wifi connect <span class="string">&quot;MyWiFi&quot;</span> password <span class="string">&quot;12345678&quot;</span></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">Device &#x27;wlp129s0&#x27; successfully activated with &#x27;xxxx-xxxx-xxxx&#x27;</span><br></pre></td></tr></table></figure><p>说明 Wi-Fi 已成功连接，懒猫顺利回到网络世界。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5e9b1f293b62ac92a6f440a9b64e5ec1.png" alt="5e9b1f293b62ac92a6f440a9b64e5ec1"></p><h3 id="隐藏密码的连接方式"><a href="#隐藏密码的连接方式" class="headerlink" title="隐藏密码的连接方式"></a>隐藏密码的连接方式</h3><p>前面的命令会在命令行里明文显示密码。<br>其实 <code>nmcli</code> 也支持交互式连接，输入命令后系统会自动提示输入密码：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nmcli --ask device wifi connect <span class="string">&quot;MyWiFi&quot;</span></span><br></pre></td></tr></table></figure><p>这种方式既安全又方便，命令行历史不会留下明文密码。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b206ea2903667c423391fe27466e2843.png" alt="b206ea2903667c423391fe27466e2843"></p><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></pre></td><td class="code"><pre><span class="line">nmcli connection show --active</span><br></pre></td></tr></table></figure><p>输出会显示所有活跃连接，包括 Wi-Fi、以太网、Docker 桥接等：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NAME                  UUID                                  TYPE      DEVICE</span><br><span class="line">Wired connection 1    14c5aa14-82e5-33f6-be2b-04c6de5bfe58  ethernet  enp2s0</span><br><span class="line">GL-MT3600BE-236-5G 1  a2b7d70d-09f5-459f-9903-dd140b1e0d33  wifi      wlp129s0</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251114212547502.png" alt="image-20251114212547502"></p><p>回到懒猫网络设置页面，就能看到熟悉的 Wi-Fi 已经连接上啦。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/93a9d7b39562233d38747bf4148f8a33.png" alt="93a9d7b39562233d38747bf4148f8a33"></p><h3 id="一键断开-Wi-Fi（真·命令行版）"><a href="#一键断开-Wi-Fi（真·命令行版）" class="headerlink" title="一键断开 Wi-Fi（真·命令行版）"></a>一键断开 Wi-Fi（真·命令行版）</h3><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">nmcli connection down <span class="built_in">id</span> <span class="string">&quot;GL-MT3600BE-236-5G 1&quot;</span></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">Connection &#x27;GL-MT3600BE-236-5G 1&#x27; successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/17)</span><br></pre></td></tr></table></figure><p>轻松优雅，不需要点来点去。</p>]]></content>
    
    
    <summary type="html">懒猫微服网络设置中 WiFi 不显示的排查与解决方法。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>零代码改动！用 Docker 将 Flask 应用部署到 AWS Lambda</title>
    <link href="https://blog.no-claw.com/posts/79854e67/"/>
    <id>https://blog.no-claw.com/posts/79854e67/</id>
    <published>2025-11-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>你有一个现成的 Flask API，想部署到 AWS Lambda 享受 Serverless 的好处，但又不想改代码？AWS Lambda Web Adapter 可以帮你实现。</p><p>本文将手把手教你如何使用 Docker + Gunicorn + Lambda Web Adapter，将 Flask 应用部署到 Lambda，并通过 API Gateway 对外提供服务。</p><h3 id="为什么选择这个方案？"><a href="#为什么选择这个方案？" class="headerlink" title="为什么选择这个方案？"></a>为什么选择这个方案？</h3><p>传统方式部署 Flask 到 Lambda 需要使用 Mangum、aws-wsgi 等第三方库，需要修改代码添加 handler。而 Lambda Web Adapter 是 AWS 官方方案，有以下优势：</p><ul><li><strong>零代码改动</strong>：Flask 代码完全不用改</li><li><strong>生产级配置</strong>：可以使用 Gunicorn 作为 WSGI 服务器</li><li><strong>本地开发友好</strong>：同一个 Docker 镜像本地和 Lambda 都能跑</li><li><strong>框架无关</strong>：Flask、Django、FastAPI 都支持</li></ul><h3 id="架构概览"><a href="#架构概览" class="headerlink" title="架构概览"></a>架构概览</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">客户端 → API Gateway → Lambda (Docker 容器)</span><br><span class="line">                            ↓</span><br><span class="line">                    Lambda Web Adapter</span><br><span class="line">                            ↓</span><br><span class="line">                    Gunicorn + Flask</span><br></pre></td></tr></table></figure><p>Lambda Web Adapter 作为 Lambda Extension 运行，负责将 API Gateway 事件转换为标准 HTTP 请求，Flask 应用完全感知不到自己运行在 Lambda 上。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/01/06/1767705903558-de062089-29f0-48d4-becd-ec0759aac653.png"></p><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>确保你已安装：</p><ul><li>Docker</li><li>AWS CLI（已配置凭证）</li><li>Python 3.11+</li></ul><h3 id="第一步：创建-Flask-应用"><a href="#第一步：创建-Flask-应用" class="headerlink" title="第一步：创建 Flask 应用"></a>第一步：创建 Flask 应用</h3><p>创建项目目录和文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> flask-lambda &amp;&amp; <span class="built_in">cd</span> flask-lambda</span><br></pre></td></tr></table></figure><p><strong>app.py</strong> - 一个简单的 Flask API：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, jsonify, request</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">health</span>():</span><br><span class="line">    <span class="keyword">return</span> jsonify(status=<span class="string">&#x27;healthy&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/api/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    name = request.args.get(<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;World&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> jsonify(message=<span class="string">f&#x27;Hello, <span class="subst">&#123;name&#125;</span>!&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/api/echo&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">echo</span>():</span><br><span class="line">    data = request.get_json()</span><br><span class="line">    <span class="keyword">return</span> jsonify(received=data)</span><br></pre></td></tr></table></figure><p><strong>requirements.txt</strong>：</p><figure class="highlight plaintext"><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">flask==3.0.0</span><br><span class="line">gunicorn==21.2.0</span><br></pre></td></tr></table></figure><h3 id="第二步：配置-Gunicorn"><a href="#第二步：配置-Gunicorn" class="headerlink" title="第二步：配置 Gunicorn"></a>第二步：配置 Gunicorn</h3><p><strong>gunicorn.conf.py</strong> - 针对 Lambda 优化的配置：</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">bind = <span class="string">&#x27;0.0.0.0:8080&#x27;</span></span><br><span class="line">workers = <span class="number">1</span>        <span class="comment"># Lambda 单实例，1 个 worker 足够</span></span><br><span class="line">threads = <span class="number">4</span>        <span class="comment"># 多线程处理并发</span></span><br><span class="line">timeout = <span class="number">30</span></span><br><span class="line">keepalive = <span class="number">2</span></span><br><span class="line">accesslog = <span class="string">&#x27;-&#x27;</span>    <span class="comment"># 输出到 stdout，方便 CloudWatch 收集</span></span><br><span class="line">errorlog = <span class="string">&#x27;-&#x27;</span></span><br><span class="line">loglevel = <span class="string">&#x27;info&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="第三步：编写-Dockerfile"><a href="#第三步：编写-Dockerfile" class="headerlink" title="第三步：编写 Dockerfile"></a>第三步：编写 Dockerfile</h3><p><strong>Dockerfile</strong>：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">FROM</span> python:<span class="number">3.11</span>-slim</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加 Lambda Web Adapter</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> requirements.txt .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip install --no-cache-dir -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;gunicorn&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;gunicorn.conf.py&quot;</span>, <span class="string">&quot;app:app&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>核心就是这一行 <code>COPY --from=...</code>，它从 AWS 公共 ECR 仓库拉取 Lambda Web Adapter 二进制文件，放到 <code>/opt/extensions/</code> 目录。Lambda 启动时会自动加载这个 Extension。</p><h3 id="第四步：本地测试"><a href="#第四步：本地测试" class="headerlink" title="第四步：本地测试"></a>第四步：本地测试</h3><p>构建前先登录 ECR Public：</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">aws ecr-public get-login-password --region us-east-1 | \</span><br><span class="line">  docker login --username AWS --password-stdin public.ecr.aws</span><br></pre></td></tr></table></figure><p>构建并运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build --platform linux/amd64 -t flask-lambda .</span><br><span class="line">docker run -p 8080:8080 flask-lambda</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">curl http://localhost:8080/</span><br><span class="line"><span class="comment"># &#123;&quot;status&quot;:&quot;healthy&quot;&#125;</span></span><br><span class="line"></span><br><span class="line">curl <span class="string">&quot;http://localhost:8080/api/hello?name=Lambda&quot;</span></span><br><span class="line"><span class="comment"># &#123;&quot;message&quot;:&quot;Hello, Lambda!&quot;&#125;</span></span><br><span class="line"></span><br><span class="line">curl -X POST http://localhost:8080/api/echo \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;msg&quot;:&quot;hello&quot;&#125;&#x27;</span></span><br><span class="line"><span class="comment"># &#123;&quot;received&quot;:&#123;&quot;msg&quot;:&quot;hello&quot;&#125;&#125;</span></span><br></pre></td></tr></table></figure><p>本地没问题，接下来部署到 AWS。</p><h3 id="第五步：推送镜像到-ECR"><a href="#第五步：推送镜像到-ECR" class="headerlink" title="第五步：推送镜像到 ECR"></a>第五步：推送镜像到 ECR</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><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="comment"># 设置变量</span></span><br><span class="line">AWS_REGION=<span class="string">&quot;ap-northeast-1&quot;</span></span><br><span class="line">AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)</span><br><span class="line">ECR_REPO=<span class="string">&quot;flask-lambda&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 ECR 仓库</span></span><br><span class="line">aws ecr create-repository --repository-name <span class="variable">$ECR_REPO</span> --region <span class="variable">$AWS_REGION</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 登录 ECR</span></span><br><span class="line">aws ecr get-login-password --region <span class="variable">$AWS_REGION</span> | \</span><br><span class="line">  docker login --username AWS --password-stdin \</span><br><span class="line">  <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打标签并推送</span></span><br><span class="line">docker tag flask-lambda:latest \</span><br><span class="line">  <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest</span><br><span class="line"></span><br><span class="line">docker push <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest</span><br></pre></td></tr></table></figure><h3 id="第六步：创建-Lambda-函数"><a href="#第六步：创建-Lambda-函数" class="headerlink" title="第六步：创建 Lambda 函数"></a>第六步：创建 Lambda 函数</h3><p>首先创建 IAM 角色：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建信任策略</span></span><br><span class="line"><span class="built_in">cat</span> &gt; trust-policy.json &lt;&lt; <span class="string">&#x27;EOF&#x27;</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Principal&quot;</span>: &#123;<span class="string">&quot;Service&quot;</span>: <span class="string">&quot;lambda.amazonaws.com&quot;</span>&#125;,</span><br><span class="line">    <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;sts:AssumeRole&quot;</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建角色并附加权限</span></span><br><span class="line">aws iam create-role \</span><br><span class="line">  --role-name flask-lambda-role \</span><br><span class="line">  --assume-role-policy-document file://trust-policy.json</span><br><span class="line"></span><br><span class="line">aws iam attach-role-policy \</span><br><span class="line">  --role-name flask-lambda-role \</span><br><span class="line">  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole</span><br><span class="line"></span><br><span class="line"><span class="built_in">sleep</span> 10  <span class="comment"># 等待角色生效</span></span><br></pre></td></tr></table></figure><p>创建 Lambda 函数：</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">aws lambda create-function \</span><br><span class="line">  --function-name flask-api \</span><br><span class="line">  --package-type Image \</span><br><span class="line">  --code ImageUri=<span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest \</span><br><span class="line">  --role arn:aws:iam::<span class="variable">$AWS_ACCOUNT_ID</span>:role/flask-lambda-role \</span><br><span class="line">  --<span class="built_in">timeout</span> 30 \</span><br><span class="line">  --memory-size 512 \</span><br><span class="line">  --region <span class="variable">$AWS_REGION</span></span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2026/01/06/1767705779656-ac8e8cd9-c854-4aed-a0e5-280ee9e8641d.png"></p><h3 id="第七步：配置-API-Gateway"><a href="#第七步：配置-API-Gateway" class="headerlink" title="第七步：配置 API Gateway"></a>第七步：配置 API Gateway</h3><p>打开 API Gateway 控制台，创建 REST API：</p><ol><li>点击「创建 API」→ 选择「REST API」→「构建」</li><li>输入 API 名称，如 <code>flask-api</code></li><li>创建资源：<ul><li>点击「创建资源」</li><li>勾选「代理资源」</li><li>资源路径填 <code>{proxy+}</code></li><li>点击「创建资源」</li></ul></li><li>设置集成：<ul><li>集成类型选「Lambda 函数」</li><li>勾选「Lambda 代理集成」</li><li>选择你的 Lambda 函数 <code>flask-api</code></li></ul></li><li>同样为根路径 <code>/</code> 创建 <code>ANY</code> 方法，集成到同一个 Lambda</li><li>点击「部署 API」，阶段名填 <code>v1</code></li></ol><p>部署完成后，你会得到一个调用 URL，类似：</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">https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img2@main/2026/01/06/1767705857469-577fb5e8-660b-4678-86d4-fbfa20f87a65.png"></p><h3 id="第八步：测试-API"><a href="#第八步：测试-API" class="headerlink" title="第八步：测试 API"></a>第八步：测试 API</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></pre></td><td class="code"><pre><span class="line">API_URL=<span class="string">&quot;https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1&quot;</span></span><br><span class="line"></span><br><span class="line">curl <span class="variable">$API_URL</span>/</span><br><span class="line"><span class="comment"># &#123;&quot;status&quot;:&quot;healthy&quot;&#125;</span></span><br><span class="line"></span><br><span class="line">curl <span class="string">&quot;<span class="variable">$API_URL</span>/api/hello?name=Serverless&quot;</span></span><br><span class="line"><span class="comment"># &#123;&quot;message&quot;:&quot;Hello, Serverless!&quot;&#125;</span></span><br></pre></td></tr></table></figure><p>🎉 大功告成！你的 Flask API 已经运行在 Lambda 上了。</p><h3 id="进阶：IAM-认证"><a href="#进阶：IAM-认证" class="headerlink" title="进阶：IAM 认证"></a>进阶：IAM 认证</h3><p>如果你的 API 需要认证，可以在 API Gateway 中启用 IAM 认证。调用时需要对请求进行 SigV4 签名：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> boto3</span><br><span class="line"><span class="keyword">from</span> botocore.auth <span class="keyword">import</span> SigV4Auth</span><br><span class="line"><span class="keyword">from</span> botocore.awsrequest <span class="keyword">import</span> AWSRequest</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">signed_request</span>(<span class="params">method, url, data=<span class="literal">None</span>, headers=<span class="literal">None</span></span>):</span><br><span class="line">    session = boto3.Session()</span><br><span class="line">    credentials = session.get_credentials()</span><br><span class="line">    </span><br><span class="line">    headers = headers <span class="keyword">or</span> &#123;&#125;</span><br><span class="line">    request = AWSRequest(method=method, url=url, data=data, headers=headers)</span><br><span class="line">    <span class="comment"># 注意：API Gateway 用 execute-api，Function URL 用 lambda</span></span><br><span class="line">    SigV4Auth(credentials, <span class="string">&quot;execute-api&quot;</span>, <span class="string">&quot;ap-northeast-1&quot;</span>).add_auth(request)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> requests.request(</span><br><span class="line">        method=method,</span><br><span class="line">        url=url,</span><br><span class="line">        headers=<span class="built_in">dict</span>(request.headers),</span><br><span class="line">        data=data</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">resp = signed_request(<span class="string">&quot;GET&quot;</span>, <span class="string">f&quot;<span class="subst">&#123;API_URL&#125;</span>/api/hello&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(resp.json())</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2026/01/06/1767705880721-92b4ad4e-da74-45af-92d5-bfb4b3994eb1.png"></p><h3 id="更新部署"><a href="#更新部署" class="headerlink" title="更新部署"></a>更新部署</h3><p>代码更新后，只需重新构建镜像并更新 Lambda：</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 build --platform linux/amd64 -t flask-lambda .</span><br><span class="line">docker tag flask-lambda:latest <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest</span><br><span class="line">docker push <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest</span><br><span class="line"></span><br><span class="line">aws lambda update-function-code \</span><br><span class="line">  --function-name flask-api \</span><br><span class="line">  --image-uri <span class="variable">$AWS_ACCOUNT_ID</span>.dkr.ecr.<span class="variable">$AWS_REGION</span>.amazonaws.com/<span class="variable">$ECR_REPO</span>:latest</span><br></pre></td></tr></table></figure><h3 id="冷启动优化"><a href="#冷启动优化" class="headerlink" title="冷启动优化"></a>冷启动优化</h3><p>首次请求可能需要 1-2 秒（冷启动），可以通过以下方式优化：</p><ol><li><strong>启用 Provisioned Concurrency</strong>：预热实例，消除冷启动</li><li><strong>减小镜像体积</strong>：使用 slim 基础镜像，减少依赖</li><li><strong>启用异步初始化</strong>：设置环境变量 <code>AWS_LWA_ASYNC_INIT=true</code></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>使用 Lambda Web Adapter，你可以：</p><ul><li>保持 Flask 代码不变</li><li>使用熟悉的 Gunicorn 生产配置</li><li>同一镜像本地和云端都能运行</li><li>享受 Serverless 的弹性伸缩和按需付费</li></ul><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://github.com/awslabs/aws-lambda-web-adapter">AWS Lambda Web Adapter GitHub</a></li><li><a href="https://docs.aws.amazon.com/lambda/latest/dg/images-create.html">Lambda 容器镜像支持</a></li><li><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html">API Gateway Lambda 代理集成</a></li></ul>]]></content>
    
    
    <summary type="html">用 Docker 和 Lambda Web Adapter 零改动部署 Flask 到 AWS Lambda</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十一）：百度网盘迁移到懒猫网盘</title>
    <link href="https://blog.no-claw.com/posts/5332ecc4/"/>
    <id>https://blog.no-claw.com/posts/5332ecc4/</id>
    <published>2025-11-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>拿到一台新的 NAS，大家往往第一件事不是装 Docker、也不是跑容器，而是——<strong>把原先公有云上的数据，迁回到自己的私有空间里。</strong></p><p>这是“云端回家”的第一步。对很多人来说，NAS 不仅是一个硬盘，更是重新掌握数据主权的起点。</p><p>这篇文章就以最常见的百度网盘为例，演示如何将文件迁移到懒猫网盘中。<br>我们会介绍两种方式：</p><ol><li>使用自带的<strong>百度网盘 NAS 版本套件</strong>（最直接）；</li><li>使用<strong>SMB 网络共享挂载</strong>的方式实现中转下载（更灵活）。</li></ol><h3 id="一、为什么迁移？"><a href="#一、为什么迁移？" class="headerlink" title="一、为什么迁移？"></a>一、为什么迁移？</h3><p>很多人习惯把资料放在百度网盘、腾讯微云、甚至 iCloud 上，但这些服务有一个共同点：<strong>你的数据都在别人的服务器上。</strong><br>无论是隐私、速度，还是访问的自由度，都无法与自建 NAS 相比。</p><p>而懒猫网盘正好提供了一个简洁稳定的私有云方案：支持 SMB、WebDAV、HTTP 访问，同时能在内网高速读写，甚至还能开放公网远程访问。</p><p>因此，拿到 NAS 后，把百度网盘里的资料迁移过来，就成了多数用户的首要任务。</p><span id="more"></span><h3 id="方法一：使用百度网盘-NAS-版套件"><a href="#方法一：使用百度网盘-NAS-版套件" class="headerlink" title="方法一：使用百度网盘 NAS 版套件"></a>方法一：使用百度网盘 NAS 版套件</h3><p>懒猫商店中自带的百度网盘 NAS 版本，其实是从群晖 Synology 移植过来的。<br>它提供了基础的上传、下载、同步功能，对于多数人来说已经足够。也无需再购买百度网盘 NAS 版本会员。</p><p>安装完成后，你会在 NAS 的根目录下看到一个名为 <strong>BaiduNetDiskNas</strong> 的文件夹。</p><p>这是百度网盘 NAS 套件默认的数据目录，所有通过该套件下载的文件都会存放在这里。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e7efd49063f38d41fce71408ad9a08a1.png" alt="e7efd49063f38d41fce71408ad9a08a1"></p><p>和其他套件不同，百度网盘的数据不会混进应用路径，而是直接落在 NAS 的根目录下，路径清晰，也方便后续迁移或备份。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251102113343984.png" alt="image-20251102113343984"></p><p>使用方式很简单：</p><ul><li>打开百度网盘 NAS 应用，登录账号；</li><li>选择要下载的文件；</li><li>点击下载即可。</li></ul><p>这种方式不需要额外配置，也不用动网络端口，适合初次上手懒猫网盘的用户。</p><p>因此，如果你希望速度更快、结构更灵活，可以继续看下面的第二种方案。</p><h3 id="方法二：通过-SMB-挂载中转迁移"><a href="#方法二：通过-SMB-挂载中转迁移" class="headerlink" title="方法二：通过 SMB 挂载中转迁移"></a>方法二：通过 SMB 挂载中转迁移</h3><p>另一种更通用的方案，是使用 SMB（Samba）共享的方式。</p><p>简单来说，就是让你的电脑直接访问懒猫网盘的共享目录，把它当成本地磁盘使用。</p><p>这样一来，在百度网盘客户端中，就可以<strong>直接把下载路径设为 NAS 上的 SMB 共享文件夹</strong>，下载完成即同步到 NAS。</p><p>具体步骤如下：</p><h4 id="1-启用-NAS-的-SMB-服务"><a href="#1-启用-NAS-的-SMB-服务" class="headerlink" title="1. 启用 NAS 的 SMB 服务"></a>1. 启用 NAS 的 SMB 服务</h4><p>在懒猫网盘中，进入【网盘 → 网络服务】，找到 <strong>SMB 服务内网开关</strong>，将其打开。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251102114436257.png" alt="image-20251102114436257"></p><p>如果你有公网访问的需求，也可以使用懒猫提供的默认域名来挂载 SMB。</p><h4 id="2-在电脑上连接-NAS-共享目录"><a href="#2-在电脑上连接-NAS-共享目录" class="headerlink" title="2. 在电脑上连接 NAS 共享目录"></a>2. 在电脑上连接 NAS 共享目录</h4><p>以 macOS 为例：<br>打开 Finder（访达） → 顶部菜单栏点击「前往」→「连接服务器…」<br>输入地址，例如：</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">smb://192.168.1.4/用户名</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">smb://192.168.1.4/用户名/文件夹的名字</span><br></pre></td></tr></table></figure><p>点击连接后，系统会提示输入用户名和密码。输入你在懒猫网盘中提示的账户信息即可。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251102113650989.png" alt="image-20251102113650989"></p><p>连接成功后，你会在 Finder 的侧边栏看到 NAS 的共享文件夹。</p><h4 id="3-在百度网盘中选择下载路径"><a href="#3-在百度网盘中选择下载路径" class="headerlink" title="3. 在百度网盘中选择下载路径"></a>3. 在百度网盘中选择下载路径</h4><p>打开百度网盘客户端 → 设置 → 下载路径 → 手动选择<br>此时选择你刚刚挂载的 NAS 共享目录。</p><p>我这里建了一个名为 <strong>“百度”</strong> 的文件夹，用于专门存放网盘迁移文件。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251102113837772.png" alt="image-20251102113837772"></p><p>这样，百度网盘在下载文件时，实际上是直接把数据写进懒猫网盘中。</p><p>整个过程既不占用本机空间，也不需要二次传输，非常高效。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6302780922a2f684b6c6334980eb2e71.png" alt="6302780922a2f684b6c6334980eb2e71"></p><h3 id="四、两种方式的对比"><a href="#四、两种方式的对比" class="headerlink" title="四、两种方式的对比"></a>四、两种方式的对比</h3><table><thead><tr><th>对比维度</th><th>百度网盘 NAS 套件</th><th>SMB 中转迁移</th></tr></thead><tbody><tr><td>安装难度</td><td>一键安装</td><td>需配置 SMB 服务</td></tr><tr><td>下载速度</td><td>一般（受限于百度接口）</td><td>取决于本地带宽，通常更快</td></tr><tr><td>文件路径</td><td>固定在 BaiduNetDiskNas</td><td>可自定义结构</td></tr><tr><td>适合人群</td><td>新手用户</td><td>进阶&#x2F;高需求用户</td></tr><tr><td>依赖环境</td><td>NAS 端运行</td><td>NAS + 本地 PC 配合</td></tr></tbody></table><p>总结一下：<br>如果你只想简单备份数据，<strong>百度网盘 NAS 套件</strong>足够；<br>如果你希望文件即时同步、可控性更强，<strong>SMB 中转方式</strong>更值得使用。</p><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>公有云的便利和便宜，确实让很多人习惯把所有资料交给它。<br>但当你真正拥有一台 NAS、部署起懒猫网盘那一刻，就会发现——<strong>数据掌握在自己手里的安全感，是任何会员都买不到的。</strong></p><p>迁移数据只是第一步，之后你还可以：</p><ul><li>开启内网同步，让所有设备自动备份；</li><li>部署媒体服务器，打造自己的影音库；</li><li>甚至用 Docker 搭建 Git、PhotoPrism、Plex 等完整生态。</li></ul><p>无论你是第一次接触 NAS，还是从群晖、威联通迁移过来的老用户，希望这篇教程能帮你顺利完成“云端回家”的第一步。</p>]]></content>
    
    
    <summary type="html">从百度网盘迁移数据到懒猫网盘，把公有云数据搬回私有空间。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十二）：让懒猫微服替你看家，摄像头接入篇（上）</title>
    <link href="https://blog.no-claw.com/posts/88c99791/"/>
    <id>https://blog.no-claw.com/posts/88c99791/</id>
    <published>2025-11-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>江湖上有句话——“能被自己掌控的数据，才算真正的安全”。<br>很多人买了摄像头，却不知录像都在谁手里。</p><p>我倒宁可折腾一点，也要让录像回到自己掌管的地盘里。</p><p>毕竟人在江湖漂，出门在外，男孩子也得懂得保护好自己。</p><p>所以这次，我干脆把家里的摄像头，直接接入了懒猫微服。从此录像不走云，不怕会员断。</p><p>——这是懒猫微服的又一次实战升级。</p><h3 id="为什么要接入懒猫微服"><a href="#为什么要接入懒猫微服" class="headerlink" title="为什么要接入懒猫微服"></a>为什么要接入懒猫微服</h3><span id="more"></span><p>如今的摄像头几乎都自带云存储功能，但云端录像的痛点大家都懂：</p><ol><li><strong>要充会员</strong>，不然只能存储存卡，导出来很麻烦；</li><li><strong>无法掌控数据</strong>，云厂商想删你一点办法都没有；</li><li><strong>延迟与限速</strong>，下载一段视频比登天还慢。</li></ol><p>而懒猫微服就像一个沉稳的大侠，<strong>守得住隐私，也能接得住设备</strong>。它本质是一台私人云服务器，只要设备支持主流协议（SMB、RTSP 等），都能被纳入懒猫的怀抱——我今天接入的是一款中兴的摄像头。</p><h3 id="开启懒猫网盘-SMB-共享"><a href="#开启懒猫网盘-SMB-共享" class="headerlink" title="开启懒猫网盘 SMB 共享"></a>开启懒猫网盘 SMB 共享</h3><p>摄像头原生支持 SMB 协议，这意味着它能直接把录像写进 NAS。<br>我们只需在懒猫微服的后台中，开启对应的服务即可：</p><p>路径：【网盘 → 网络服务 → SMB 服务内网开关】</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251102114436257.png" alt="image-20251102114436257"></p><p>懒猫的 SMB 模块非常稳定，能同时兼容 Windows、macOS、摄像头、甚至路由器的共享写入。</p><p>只要设好账号密码、IP 授权范围，就能实现局域网高速传输。</p><h3 id="摄像头接入-SMB"><a href="#摄像头接入-SMB" class="headerlink" title="摄像头接入 SMB"></a>摄像头接入 SMB</h3><p>打开摄像头 App（我的是中兴智慧生活），<br>选择 <strong>“NAS 网络存储”</strong> 功能，并点击“手动添加”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251103090357578.png" alt="image-20251103090357578"></p><p>在地址栏中输入懒猫网盘的 IP 和账号凭证即可。一顿折腾之后终于可以了，中兴的程序员要上点心，抛异常要写具体，不管是密码不对还是啥错都报错不能匿名登陆。这样很误导人，真的有很多开发连 401 和 403 都傻傻分不清。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251103090232589.png" alt="image-20251103090232589"></p><h3 id="懒猫网盘开始接管录像"><a href="#懒猫网盘开始接管录像" class="headerlink" title="懒猫网盘开始接管录像"></a>懒猫网盘开始接管录像</h3><p>当摄像头成功连接后，你会在懒猫网盘里看到一个新文件夹——录像开始一段段被写入 NAS 中。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251103090627173.png" alt="image-20251103090627173"></p><p>我这个摄像头录出来的文件是 MP4 格式，这点非常友好。懒猫网盘甚至能直接在线播放，也支持直接下载离线观看。如果想做 AI 识别或画面检索，还能直接喂进懒猫商店里的其他应用做视频分析。</p><p>由于我开了全天录制，记录保持一年，这个算是摄像头能够支持最大的限度了。可以看到懒猫网盘中一天的录像占用 7G 多一点，反正都有 NAS 了，算下来一年也就占用 2.6T，完全放得下，这可是用内存卡办不到的，借助懒猫微服的大容量存储，我们可以随时回滚到一年前的记忆。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251103090557460.png" alt="image-20251103090557460"></p><p>从此我不必担心内存卡满、云端过期、账号异常。<br>所有录像都牢牢存在我自己的懒猫微服里，随时取、随时删、随时回看。</p><h3 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h3><p>懒猫微服能接入的不止摄像头，它能接入的，是生活的秩序、家的安全感，以及我们对数字世界一点点的掌控力。</p><p>江湖很大，懒猫很静。</p><p>懒猫微服，不只是安静地趴在角落里。它能听、能看、能存、能守——是我出门在外最可靠的“家伙”。</p><p>折腾这些配置的过程，就像练武：一开始总会走火入魔，但掌握之后，心中自有一份安稳。</p><p>懒猫微服接入摄像头这件事，也许不是什么大事，但它让我更笃定——折腾，是生活的一种浪漫。</p>]]></content>
    
    
    <summary type="html">懒猫微服接入摄像头实现家庭监控，数据掌控在自己手中。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 更新后日志中看不到密码？一文带你搞清楚原因与解决方案</title>
    <link href="https://blog.no-claw.com/posts/dba72588/"/>
    <id>https://blog.no-claw.com/posts/dba72588/</id>
    <published>2025-10-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在群里看到不少朋友反馈，Easysearch 升级到某个版本之后，<strong>日志文件里不再能看到初始化密码</strong>了。以前版本我们可以轻松在 <code>/app/easysearch/logs/initialize.log</code> 中找到，比如 exec 进入容器后直接 <code>grep curl</code> 搜索 Easysearch URI 字段，就能定位密码所在行。但现在——无论是 grep 还是手动翻，都空空如也。</p><h3 id="问题现象：日志里“密码不见了”"><a href="#问题现象：日志里“密码不见了”" class="headerlink" title="问题现象：日志里“密码不见了”"></a>问题现象：日志里“密码不见了”</h3><p>过去版本，Easysearch 初始化时会将自动生成的默认密码打印到日志文件中。<br>如下图所示，这样的日志路径在老版本中非常常见：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/acb195d6923a17d3f8714735fad78f9a.png" alt="acb195d6923a17d3f8714735fad78f9a"></p>  <span id="more"></span><p>但在新版中，这条日志记录已经消失。<br>我平常习惯用 Dockage 来拉起 docker-compose，但由于日志滚动过快，输出信息一多也容易被覆盖。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1fbd31bb9e12c944744725b465f679f2.png" alt="1fbd31bb9e12c944744725b465f679f2"></p><h3 id="02-官方确认：这是出于安全考虑"><a href="#02-官方确认：这是出于安全考虑" class="headerlink" title="02. 官方确认：这是出于安全考虑"></a>02. 官方确认：这是出于安全考虑</h3><p>在 Easysearch 的官方交流群中咨询后，得到了 CEO 本人的亲自回复：<br>新版之所以不再在日志中输出密码，是为了<strong>提高安全性</strong>，防止明文凭证泄露。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251020062450719.png" alt="image-20251020062450719"></p><p>换句话说，密码仍然会在启动过程中生成，只是<strong>不再被重定向到容器内部的日志文件</strong>。</p><p>这意味着我们要换个思路，从 <strong>Docker 运行日志</strong> 中找密码。</p><h3 id="查看启动日志中的密码"><a href="#查看启动日志中的密码" class="headerlink" title="查看启动日志中的密码"></a>查看启动日志中的密码</h3><p>新版的密码信息仍会在启动时输出，只是没落地到文件。<br>所以我们可以直接用 Docker 命令查看：</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">docker logs &lt;easysearch_container_name&gt; | grep password</span><br></pre></td></tr></table></figure><p>在 macOS 上，用 <strong>Obstack</strong> 或 <strong>Dozzle</strong> 这样的图形化 Docker 日志工具也能非常直观地查看输出：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251020062149897.png" alt="image-20251020062149897"></p><p>不过要注意：如果容器重启过多次，历史日志会被覆盖，因此建议在第一次启动时就及时复制密码。</p><h3 id="更稳妥的方案：自定义初始化密码"><a href="#更稳妥的方案：自定义初始化密码" class="headerlink" title="更稳妥的方案：自定义初始化密码"></a>更稳妥的方案：自定义初始化密码</h3><p>其实完全可以避免手动找密码的麻烦——直接在启动 Easysearch 时<strong>自定义密码</strong>。</p><p>参考我之前的文章：<br>👉 <a href="https://mp.weixin.qq.com/s/8YM87CJJAnUme65V93TY5w">启动 Easysearch 时自定义密码的操作方法</a></p><p>这样在 CI&#x2F;CD 或本地部署时都能保持一致的密码配置，避免每次重启都要重新查找。</p><h3 id="忘记密码？还可以重置！"><a href="#忘记密码？还可以重置！" class="headerlink" title="忘记密码？还可以重置！"></a>忘记密码？还可以重置！</h3><p>如果你已经错过了初始化日志，又没配置自定义密码，也不用慌。<br>热心群友提供了铭毅天下发布的官方重置方法，实测可行：</p><p>👉 <a href="https://mp.weixin.qq.com/s/t-VyJWDzXKLLbeP1JVgRuw">Easysearch 重置密码的办法</a></p><p>重置完成后系统会生成一个新的密码。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251020063254779.png" alt="image-20251020063254779"></p><p>执行验证命令时，如果密码里有 <code>!</code>，要注意 <code>zsh</code> 的特殊行为。<br><code>zsh</code> 会把 <code>!</code> 当作“历史命令展开符”，不转义会直接报错。<br>正确的写法如下：</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 -ku admin:<span class="string">&#x27;!8We9L6@g6!NMZpEDx2Apn6U&#x27;</span> https://localhost:9200</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 -ku admin:\!8We9L6@g6\!NMZpEDx2Apn6U https://localhost:9200</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>新版 Easysearch 不再在日志文件中明文输出密码，是出于<strong>安全强化</strong>的考虑。<br>要找密码，可以：</p><ol><li>用 <code>docker logs</code> 查看启动日志；</li><li>启动时通过环境变量自定义密码；</li><li>若遗忘，可使用官方 reset 方法重置。</li></ol><p>虽然麻烦了一点，但这确实是更安全、更企业化的做法。</p>]]></content>
    
    
    <summary type="html">解析 Easysearch 更新后日志中不再显示密码的原因及解决方案</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（八）：反向代理与自签名 SSL 的优雅共存之道</title>
    <link href="https://blog.no-claw.com/posts/990eab20/"/>
    <id>https://blog.no-claw.com/posts/990eab20/</id>
    <published>2025-10-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在分布式系统中，<strong>HTTPS 是默认的通信规范</strong>。但当你在内部环境中部署服务时，特别是很多以容器方式运行的组件，但是很多时候<br>它们往往只默认开启了 HTTPS 接口，却附带了一个 <strong>自签名证书（Self-signed certificate）</strong>。</p><p>这意味着，只要一层反向代理（reverse proxy）去转发 HTTPS 请求，<strong>TLS 校验就会报错</strong>。<br>这不是配置错误，而是 SSL 的“安全特性”在起作用。</p> <span id="more"></span><p>本文就带大家看看如何在懒猫微服上架过程中如何优雅地解决这一问题。</p><h3 id="问题背景：HTTPS-转发失败"><a href="#问题背景：HTTPS-转发失败" class="headerlink" title="问题背景：HTTPS 转发失败"></a>问题背景：HTTPS 转发失败</h3><p>假设我们要在懒猫微服上架一个 App，用来转发请求到后端的 Easysearch 搜索引擎。</p><p>项目的 manifest（<code>lzc.yaml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">proxy</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.proxy</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">proxy</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=https://easysearch:9200/</span></span><br></pre></td></tr></table></figure><p>这段配置的意思很简单：<br>当访问 <code>proxy.lzcapp.xxx</code> 时，请将请求转发到容器内的 <code>https://easysearch:9200/</code>。</p><p>然而在 <strong>Dozzle</strong> 日志中，你会看到熟悉的报错信息：</p><figure class="highlight plaintext"><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">2025/10/08 10:20:15 http: proxy error: tls: failed to verify certificate: x509: certificate is valid for infini.cloud, *.infini.cloud, not easysearch</span><br><span class="line">2025/10/08 10:20:17 http: proxy error: tls: failed to verify certificate: x509: certificate is valid for infini.cloud, *.infini.cloud, not easysearch</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251008102358716.png" alt="image-20251008102358716"></p><p>报错很直白：</p><blockquote><p>证书仅对 <code>infini.cloud</code> 和 <code>*.infini.cloud</code> 有效，而不是 <code>easysearch</code>。</p></blockquote><p>这正是 <strong>证书域名不匹配（SNI mismatch）</strong> 的典型例子。</p><h3 id="原因分析：为什么-HTTPS-会报证书错"><a href="#原因分析：为什么-HTTPS-会报证书错" class="headerlink" title="原因分析：为什么 HTTPS 会报证书错"></a>原因分析：为什么 HTTPS 会报证书错</h3><p>在 HTTPS 连接建立过程中，客户端（此处是反向代理）会验证服务器返回的证书：</p><ul><li>证书是否过期；</li><li>是否由受信任的 CA 签发；</li><li><strong>证书上的主机名是否与请求的主机名匹配。</strong></li></ul><p>而我们的容器内部通常使用服务名（例如 <code>easysearch</code>）访问，不是证书上写的正式域名（<code>infini.cloud</code>）。<br>这就造成了验证失败。</p><p>在浏览器中，如果你访问一个自签名 HTTPS，会看到类似提示：“连接不安全”。<br>在反向代理中，它不会弹窗，而是直接抛出异常中断。</p><h3 id="Nginx-的特性：默认不验证上游证书"><a href="#Nginx-的特性：默认不验证上游证书" class="headerlink" title="Nginx 的特性：默认不验证上游证书"></a>Nginx 的特性：默认不验证上游证书</h3><p>在以前的实验中，我用过 Nginx 来转发自签名 HTTPS 的 Easysearch，发现并没有报错。<br>这是因为——Nginx 默认不会验证上游 HTTPS 证书。</p><p>在官方文档 <a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_verify">ngx_http_proxy_module</a> 中明确写道：</p><blockquote><p><strong>Syntax:</strong> <code>proxy_ssl_verify on | off;</code> &gt; <strong>Default:</strong> <code>proxy_ssl_verify off;</code> &gt; <strong>Context:</strong> http, server, location</p><p>This directive sets whether to verify the SSL certificate of the proxied server.</p></blockquote><p>也就是说：</p><ul><li>默认情况下，<code>proxy_ssl_verify</code> 是 <strong>off</strong>；</li><li>Nginx 不会校验后端证书；</li><li>只有你显式开启 <code>proxy_ssl_verify on;</code> 时，它才会去核对证书域名、签发方、有效期等信息。</li></ul><p>这点在内部网络部署中非常有用 —— 因为大部分内网服务使用的都是自签名或临时证书。</p><h3 id="典型案例：Docker-Compose-Nginx-反代-Easysearch"><a href="#典型案例：Docker-Compose-Nginx-反代-Easysearch" class="headerlink" title="典型案例：Docker Compose + Nginx 反代 Easysearch"></a>典型案例：Docker Compose + Nginx 反代 Easysearch</h3><p>要更直观理解，我们先构造一个简单的实验场景。<br>下面是一个 Docker Compose 配置，启动一个 Easysearch 容器和一个 Nginx 容器：</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></pre></td><td class="code"><pre><span class="line">version: <span class="string">&quot;3.8&quot;</span></span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  easysearch:</span><br><span class="line">    image: infinilabs/easysearch:1.15.3</span><br><span class="line">    container_name: es-single</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;9200:9200&quot;</span>   <span class="comment"># 本地直连（可选）</span></span><br><span class="line"></span><br><span class="line">  nginx:</span><br><span class="line">    image: nginx:latest</span><br><span class="line">    container_name: nginx-proxy</span><br><span class="line">    depends_on:</span><br><span class="line">      - easysearch</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;80:80&quot;</span>       <span class="comment"># 对外访问端口</span></span><br><span class="line">    volumes:</span><br><span class="line">      - ./nginx.conf:/etc/nginx/nginx.conf:ro</span><br></pre></td></tr></table></figure><p>Nginx 配置如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="section">events</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">http</span> &#123;</span><br><span class="line">    <span class="section">server</span> &#123;</span><br><span class="line">        <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">        <span class="attribute">server_name</span> localhost;</span><br><span class="line"></span><br><span class="line">        <span class="section">location</span> / &#123;</span><br><span class="line">            <span class="attribute">proxy_pass</span> https://easysearch:9200;</span><br><span class="line">            <span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line">            <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">            <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</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><p>这个配置里没有加任何 SSL 校验相关指令。<br>Nginx 在请求 <code>https://easysearch:9200</code> 时会直接忽略自签名问题，代理层不会抛错。</p><h3 id="懒猫微服中的-OpenResty"><a href="#懒猫微服中的-OpenResty" class="headerlink" title="懒猫微服中的 OpenResty"></a>懒猫微服中的 OpenResty</h3><p>懒猫微服默认 app.proxy 是 <strong>OpenResty（Nginx 增强版）</strong> 。<br>它天然具备和 Nginx 一样的 SSL 默认策略 —— 即忽略内网自签名的证书验证。</p><p>外挂的配置文件很麻烦，我们可以使用 setup_script 来执行 shell 命令来动态写入。效果和前面是一样的。</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></pre></td><td class="code"><pre><span class="line"><span class="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">proxy</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.proxy</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">proxy</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy:80/</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">setup_script:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      cat &lt;&lt;&#x27;EOF&#x27; &gt; /etc/nginx/conf.d/default.conf</span></span><br><span class="line"><span class="string">      server &#123;</span></span><br><span class="line"><span class="string">        listen 80;</span></span><br><span class="line"><span class="string">        server_name _;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">        <span class="string">location</span> <span class="string">/</span> &#123;</span><br><span class="line">          <span class="string">proxy_pass</span> <span class="string">https://easysearch:9200/;</span></span><br><span class="line">          <span class="string">proxy_set_header</span> <span class="string">Host</span> <span class="string">$host;</span></span><br><span class="line">          <span class="string">proxy_set_header</span> <span class="string">X-Real-IP</span> <span class="string">$remote_addr;</span></span><br><span class="line">          <span class="string">proxy_set_header</span> <span class="string">X-Forwarded-For</span> <span class="string">$proxy_add_x_forwarded_for;</span></span><br><span class="line">        &#125;</span><br><span class="line">      <span class="string">&#125;</span></span><br><span class="line">      <span class="string">EOF</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">easysearch:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/infinilabs/easysearch:5dc6296f5370aacc</span></span><br></pre></td></tr></table></figure><p>这段配置中，<code>setup_script</code> 会在容器启动时执行，会动态写入 Nginx 配置文件。<br>容器之间的通信使用内网域名，不校验证书。这样代理转发就能成功，日志也不会再出现 <code>x509</code> 错误。</p><h3 id="再优化：使用官方环境变量"><a href="#再优化：使用官方环境变量" class="headerlink" title="再优化：使用官方环境变量"></a>再优化：使用官方环境变量</h3><p>如果你不想写配置文件，懒猫官方提供的 <code>app-proxy</code> 镜像还支持 <strong>UPSTREAM 环境变量</strong> 来简化操作：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">proxy</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.proxy</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">proxy</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy:80/</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">UPSTREAM=&quot;https://easysearch:9200&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">easysearch:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/infinilabs/easysearch:5dc6296f5370aacc</span></span><br></pre></td></tr></table></figure><p>这种方式更轻量，也便于在应用市场中复用。<br>UPSTREAM 会自动注入到镜像配置中，无需手动挂载文件。</p><h3 id="进阶用法：新版本的-upstreams-支持-SSL-开关"><a href="#进阶用法：新版本的-upstreams-支持-SSL-开关" class="headerlink" title="进阶用法：新版本的 upstreams 支持 SSL 开关"></a>进阶用法：新版本的 <code>upstreams</code> 支持 SSL 开关</h3><p>懒猫微服新版本的 SDK 进一步增强了反向代理的灵活性。<br>你可以直接在 <code>application</code> 部分的 <code>upstreams</code> 中声明多个后端服务，并显式配置 SSL 行为：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">proxy</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.proxy</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">proxy</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">upstreams:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">location:</span> <span class="string">/</span></span><br><span class="line">      <span class="attr">backend:</span> <span class="string">https://easysearch:9200</span></span><br><span class="line">      <span class="attr">disable_backend_ssl_verify:</span> <span class="literal">true</span> <span class="comment"># 关键参数：禁用 SSL 校验</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">easysearch:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/infinilabs/easysearch:5dc6296f5370aacc</span></span><br></pre></td></tr></table></figure><p>当 <code>disable_backend_ssl_verify: true</code> 设置后，平台在代理时会自动跳过证书校验步骤。<br>这相当于在 Nginx 中使用了：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">proxy_ssl_verify</span> <span class="literal">off</span>;</span><br></pre></td></tr></table></figure><p>部署后日志如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">PATH:&quot;/&quot; is served by &#123;/ false  https://easysearch:9200 false  true false  false [] false false []&#125;</span><br><span class="line">✨ Internal health check successful.</span><br><span class="line">Standing by until other services are healthy.  ⌛</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251008103902543.png" alt="image-20251008103902543"></p><p>代理成功生效。</p><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>通过本文的两种方法，我们可以：</p><ul><li>安全地代理 HTTPS 服务；</li><li>解决自签名证书报错；</li><li>保持部署流程一致、代码简洁；</li><li>同时兼顾上架懒猫商店的兼容性。</li></ul><p>未来如果要接入多后端服务的多路复用机制也能轻松应对。</p><blockquote><p>反向代理不仅是“流量的中转站”，更是“安全与兼容的缓冲层”。<br>理解 SSL 校验机制后，才能写出既安全又高可用的微服务架构。</p></blockquote>]]></content>
    
    
    <summary type="html">探讨懒猫微服中反向代理与自签名 SSL 证书共存的配置方案</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>铁威马：被忽视的中坚NAS玩家，为何在群晖阴影下沉默？</title>
    <link href="https://blog.no-claw.com/posts/1690f93c/"/>
    <id>https://blog.no-claw.com/posts/1690f93c/</id>
    <published>2025-10-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在个人与中小企业存储市场中，<strong>群晖（Synology）与威联通（QNAP）</strong> 几乎垄断了“NAS”的代名词。但如果你仔细观察京东、亚马逊或 B 站开箱视频，就会发现一个存在感极弱、但出货量其实不低的品牌——<strong>铁威马（TerraMaster）</strong>。</p><p>它产品线齐全、价格诱人、在亚马逊上好评不少，却始终无法在玩家社区中形成话题。为什么？本文试图从定位、生态、产品、传播与竞争格局五个维度剖析这家“沉默的 NAS 厂商”。</p> <span id="more"></span><h3 id="一、从硬盘盒到-NAS：铁威马的“工程师路线”"><a href="#一、从硬盘盒到-NAS：铁威马的“工程师路线”" class="headerlink" title="一、从硬盘盒到 NAS：铁威马的“工程师路线”"></a>一、从硬盘盒到 NAS：铁威马的“工程师路线”</h3><p>铁威马成立于 2002 年，最初做的是<strong>DAS（Direct Attached Storage）和 RAID 阵列柜</strong>，在 DIY 和视频剪辑圈里有一定口碑。</p><p>但它与群晖、威联通最大的区别是：</p><blockquote><p>铁威马一直是一个“<strong>硬件思维主导的软件厂商</strong>”。</p></blockquote><p>对比来看：</p><table><thead><tr><th>品牌</th><th>产品核心</th><th>技术策略</th><th>典型用户群</th></tr></thead><tbody><tr><td>群晖 Synology</td><td>软件（DSM 系统）</td><td>打造生态闭环、重体验</td><td>家庭用户、中小企业</td></tr><tr><td>威联通 QNAP</td><td>软件 + 硬件多元化</td><td>丰富功能与性能路线</td><td>技术型企业用户</td></tr><tr><td>铁威马 TerraMaster</td><td>硬件性价比</td><td>以成本与兼容性为卖点</td><td>入门级、预算型用户</td></tr></tbody></table><p>铁威马的 T 系列 NAS 硬件性能并不差，甚至同价位下 CPU、内存、网口规格往往更高，但它<strong>缺少软件体验层面的“惊喜”</strong>——这让它看起来更像“带系统的硬盘盒”，而不是一个“私有云平台”。</p><h3 id="二、生态短板：TOS-系统的“孤岛效应”"><a href="#二、生态短板：TOS-系统的“孤岛效应”" class="headerlink" title="二、生态短板：TOS 系统的“孤岛效应”"></a>二、生态短板：TOS 系统的“孤岛效应”</h3><p>铁威马自研的操作系统 TOS（TerraMaster OS）目前发展到 <strong>TOS 6.x</strong>，在 UI 设计与易用性上已有改进，但生态仍是最大痛点。</p><table><thead><tr><th>对比项目</th><th>群晖 DSM 7.x</th><th>威联通 QTS &#x2F; QuTS</th><th>铁威马 TOS 6</th></tr></thead><tbody><tr><td>系统界面</td><td>精致、流畅、统一</td><td>丰富但复杂</td><td>简单但略显老旧</td></tr><tr><td>应用中心</td><td>200+ 官方&#x2F;第三方</td><td>300+ 插件生态</td><td>40+ 应用，更新慢</td></tr><tr><td>虚拟化支持</td><td>Docker、VM、Kubernetes</td><td>虚拟机中心、容器站</td><td>仅部分机型支持 Docker</td></tr><tr><td>安全更新</td><td>定期推送、活跃社区</td><td>补丁频繁</td><td>更新周期长、文档少</td></tr><tr><td>云服务集成</td><td>Synology Drive、Photos、C2</td><td>myQNAPCloud</td><td>CloudSync</td></tr></tbody></table><p>结果是，TOS 虽然能满足基础文件共享、RAID 管理、Time Machine、备份任务等需求，但缺乏高级功能——例如群晖的 Active Backup、威联通的 QuMagie AI 照片识别、或多云同步整合。</p><h4 id=""><a href="#" class="headerlink" title=""></a></h4><p>这导致许多中高端用户在体验后觉得：</p><blockquote><p>“功能够用，但系统太单调。”</p></blockquote><h3 id="三、品牌存在感弱：铁威马几乎不讲故事"><a href="#三、品牌存在感弱：铁威马几乎不讲故事" class="headerlink" title="三、品牌存在感弱：铁威马几乎不讲故事"></a>三、品牌存在感弱：铁威马几乎不讲故事</h3><p>群晖有论坛、有教程、有博主内容；威联通有 NAS 玩家社区、有 QTS 更新直播；<br>而铁威马呢？<br>除了官网更新日志和亚马逊评论，几乎<strong>没有声音</strong>。</p><p>缺乏社区意味着：</p><ul><li>没有 UGC 教程或经验贴；</li><li>很少有 YouTube &#x2F; B 站教学视频；</li><li>没有第三方开发者生态；</li><li>没有社群反馈机制。</li></ul><p>这让铁威马在舆论层面处于“隐身”状态——普通用户不知道它，玩家不讨论它，企业客户不了解它。</p><p>甚至有种“用过就忘”的品牌感。</p><h3 id="四、被市场挤压：上有群晖，下有自建-NAS"><a href="#四、被市场挤压：上有群晖，下有自建-NAS" class="headerlink" title="四、被市场挤压：上有群晖，下有自建 NAS"></a>四、被市场挤压：上有群晖，下有自建 NAS</h3><p>铁威马所处的价格区间（千元～三千元）正好是最卷的 NAS 市场。<br>但它的两侧都被强敌包夹：</p><ol><li><strong>上层被群晖压制</strong><br>群晖以系统稳定、生态强著称，哪怕贵一倍，很多人依然选择它。</li><li><strong>下层被自建 NAS 侵蚀</strong><br>Unraid、TrueNAS、CasaOS 等方案越来越成熟，旧主机重生、N100 工控机当 NAS 成为趋势。铁威马的价格优势就不再突出。</li></ol><p>于是，铁威马成了“想省钱又不想折腾”的那群人唯一的选项——但这群人，数量其实有限。</p><h3 id="五、国际传播弱：营销与渠道的缺位"><a href="#五、国际传播弱：营销与渠道的缺位" class="headerlink" title="五、国际传播弱：营销与渠道的缺位"></a>五、国际传播弱：营销与渠道的缺位</h3><p>在全球市场上，铁威马的销售策略更接近<strong>传统 OEM 模式</strong>：靠亚马逊、电商分销、批发代理出货。<br>它几乎没有参与 Reddit、Discord、YouTube 技术社区的长期运营，也少有海外测评博主合作。</p><p>搜索“TerraMaster NAS”时，能看到的都是散乱的用户测评，而非系统的品牌叙事或技术展示。<br>而群晖、威联通早已形成“生态势能”：更新一个版本，都能被科技媒体报道。</p><h3 id="六、但铁威马真的没有优点吗？"><a href="#六、但铁威马真的没有优点吗？" class="headerlink" title="六、但铁威马真的没有优点吗？"></a>六、但铁威马真的没有优点吗？</h3><p>恰恰相反，它有几个被低估的优势：</p><ul><li>💰 <strong>性价比极高</strong>：同价性能普遍优于群晖。</li><li>🧩 <strong>硬件开放度高</strong>：支持自装内存、第三方硬盘。</li><li>🛠 <strong>可玩性适中</strong>：支持 SSH、Docker、命令行操作。</li><li>🌐 <strong>企业存储产品线丰富</strong>：RAID 阵列柜、Thunderbolt DAS 方案。</li></ul><p>如果你只是想：</p><ul><li>备份电脑、相册；</li><li>做个小型文件服务器；</li><li>体验轻量 NAS；</li></ul><p>铁威马依然是“买了不亏”的选择。</p><h3 id="七、结语：被忽视的“安静力量”"><a href="#七、结语：被忽视的“安静力量”" class="headerlink" title="七、结语：被忽视的“安静力量”"></a>七、结语：被忽视的“安静力量”</h3><p>铁威马的问题，不在产品，而在<strong>定位和传播</strong>。<br>它不是“做不好”，而是“不被看到”。</p><p>在一个重体验、重生态的市场中，仅靠“硬件性价比”已经难以立足。<br>铁威马需要做的不仅是提升性能，而是讲清楚自己是谁——<br>是要做“平价 NAS”，还是“轻企业私有云”？<br>是要成为入门市场的守护者，还是中小企业的替代方案？</p><p>否则，它可能继续像现在这样：</p><blockquote><p><strong>默默出货，默默被遗忘。</strong></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;在个人与中小企业存储市场中，&lt;strong&gt;群晖（Synology）与威联通（QNAP）&lt;/strong&gt; 几乎垄断了“NAS”的代名词。但如果你仔细观察京东、亚马逊或 B 站开箱视频，就会发现一个存在感极弱、但出货量其实不低的品牌——&lt;strong&gt;铁威马（TerraMaster）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它产品线齐全、价格诱人、在亚马逊上好评不少，却始终无法在玩家社区中形成话题。为什么？本文试图从定位、生态、产品、传播与竞争格局五个维度剖析这家“沉默的 NAS 厂商”。&lt;/p&gt;</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>Coco AI 插件商店很多功能，还能玩 2048</title>
    <link href="https://blog.no-claw.com/posts/76058f6a/"/>
    <id>https://blog.no-claw.com/posts/76058f6a/</id>
    <published>2025-10-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日常使用电脑的过程中，很多人习惯安装各种效率工具、启动器、播放器或日程管理应用。但这些功能其实都能被一个智能平台集中完成——这就是 <strong>Coco AI</strong>。</p><p>除了搜索和信息整理之外，Coco AI 还提供了一个完善的 <strong>插件系统</strong>。通过插件，它几乎可以控制电脑的方方面面：</p><p>操作本地备忘录、播放音乐、查找应用，甚至取代 macOS 的导航栏。更令人惊喜的是，它还内置小游戏，比如大家熟悉的 2048。</p>  <span id="more"></span><h3 id="Coco-AI-的系统整合能力"><a href="#Coco-AI-的系统整合能力" class="headerlink" title="Coco AI 的系统整合能力"></a>Coco AI 的系统整合能力</h3><p>Coco AI 的插件系统不是简单的第三方扩展，而是深度整合在 macOS 桌面环境中的轻量级功能模块。</p><p>例如下图中的界面，插件可以直接访问系统级服务：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251007074812886.png" alt="image-20251007074812886"></p><p>通过插件，Coco 能够直接读取或编辑本地备忘录、打开 Finder 文件夹、启动已安装的应用，甚至接管顶部导航栏，让桌面控制更加一体化。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251007074739583.png" alt="image-20251007074739583"></p><p>在使用体验上，这些功能完全不需要额外配置。插件启用后，会自动加载到 Coco 的主控制界面，执行速度快，界面响应流畅。</p><h3 id="二、音乐与媒体控制"><a href="#二、音乐与媒体控制" class="headerlink" title="二、音乐与媒体控制"></a>二、音乐与媒体控制</h3><p>音乐爱好者会发现，Coco 的插件生态对媒体控制支持得非常完善。<br>它不仅支持系统自带的音乐播放器，还能直接控制第三方软件，例如网易云音乐、QQ 音乐等。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251007074825231.png" alt="image-20251007074825231"></p><p>在开启相关插件后，你可以在 Coco 面板中完成播放、暂停、切歌、调节音量等操作。</p><p>对于喜欢边工作边听歌的人来说，这种“中控台式”的体验比单独切换应用更加高效，也更美观。</p><h3 id="三、桌面导航栏替代方案"><a href="#三、桌面导航栏替代方案" class="headerlink" title="三、桌面导航栏替代方案"></a>三、桌面导航栏替代方案</h3><p>除了控制音乐和笔记，Coco 还有一些插件能替代 macOS 顶部的系统导航栏。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006203825881.png" alt="image-20251006203825881"></p><p>得益于 Coco 的统一框架，导航栏可以和其他插件联动，这种深度整合的插件体验，使得 Coco AI 更像是一个“桌面操作系统的延伸层”，而不只是一个 AI 应用。</p><h3 id="插件商店入口"><a href="#插件商店入口" class="headerlink" title="插件商店入口"></a>插件商店入口</h3><p>在主界面右上角点击“加号”，就能打开 <strong>插件商店（Plugin Store）</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006203923230.png" alt="image-20251006203923230"></p><p>每个插件都有简介、开发者信息、版本号和一键安装按钮。安装后插件会立即生效，无需重启。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006203915518.png" alt="image-20251006203915518"></p><p>商店的搜索栏也很实用，可以直接输入关键词查找插件。例如输入 “2048”，就能找到一款完整的小游戏插件。</p><h3 id="娱乐类插件：在-Coco-里玩-2048"><a href="#娱乐类插件：在-Coco-里玩-2048" class="headerlink" title="娱乐类插件：在 Coco 里玩 2048"></a>娱乐类插件：在 Coco 里玩 2048</h3><p>工作之余，Coco 还可以成为一个放松工具。<br>在插件商店中搜索 <strong>“2048”</strong>，即可找到一款轻量化的本地小游戏。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251007073414948.png" alt="image-20251007073414948"></p><p>安装完成后，打开 Coco 的插件区即可直接启动游戏。<br>整个界面与经典的 2048 完全一致，采用极简配色，操作流畅，不需要联网，也没有广告。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/555411dc2398e74bcc41694a326583ac.png" alt="555411dc2398e74bcc41694a326583ac"></p><p>使用方向键控制方块合并，当数字达到 2048 时即可获胜。<br>游戏支持自动保存分数，下次打开可以继续上局，非常适合在短暂休息时消遣。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Coco AI 的插件商店展示了一个轻量、开放、可扩展的桌面生态。<br>它将搜索、系统操作、音乐控制、任务管理、小游戏等多种功能融合在一个统一界面中。</p><p>对用户而言，Coco 的价值不仅在于“能干什么”，而在于“减少多少步骤”。</p><p>从打开备忘录、播放音乐，到临时玩一局 2048，所有操作都能在一个窗口内完成。</p><p>这种无缝衔接的体验，正在重新定义智能工具的形态——Coco 不再只是一个 AI 程序，而更像是 macOS 的「智能插件中心」。</p>]]></content>
    
    
    <summary type="html">探索 Coco AI 插件商店的丰富功能，还内置了 2048 小游戏</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>摘抄：不要呆在没有话语权的小团体里</title>
    <link href="https://blog.no-claw.com/posts/8f725a10/"/>
    <id>https://blog.no-claw.com/posts/8f725a10/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV1EQxKzAEtG/?spm_id_from=333.1391.0.0&vd_source=455daf3f8c90b0a5bd4160e8292c05f9">https://www.bilibili.com/video/BV1EQxKzAEtG/?spm_id_from=333.1391.0.0&amp;vd_source=455daf3f8c90b0a5bd4160e8292c05f9</a></p><p>本视频探讨了人们在生活和工作中常常陷入缺乏话语权的小团体中的现象。这些小团体包括项目组、朋友聚会、家庭关系网等，表面上看似平等，但实际上存在权力关系，导致部分人的声音被忽视、观点不被重视。</p><h3 id="关键点总结"><a href="#关键点总结" class="headerlink" title="关键点总结"></a>关键点总结</h3><span id="more"></span><ul><li><strong>话语权与权力关系</strong>：法国哲学家福柯指出，权力关系无处不在，即使在最微小的人际互动中也存在话语权，这是权力关系的体现。</li><li><strong>边缘化的影响</strong>：长期处于缺乏话语权的环境会导致个体自我怀疑、认知扭曲，甚至形成“我的意见不重要”的错误认知。</li><li><strong>温水煮青蛙效应</strong>：习惯于在团体中保持沉默，会导致思维肌肉萎缩，忘记如何为自己发声。</li><li><strong>自我审查与内化沉默</strong>：在没有话语权的环境中，人们会不自觉地开始自我审查，开口前就先否定自己，这是对自我价值的背叛。</li><li><strong>真实表达的重要性</strong>：一个不允许你真实表达的团体，不值得你付出宝贵的时间和情感。不平等的关系即使表面和谐，也难以长久。</li><li><strong>尊重需求与自尊</strong>：心理学家马斯洛指出，尊重需求是人类的基本需求之一。在团体中被倾听和认可，是尊重的具体体现。长期得不到满足，将侵蚀自尊和自信。</li><li><strong>寻找真正能让你发声的环境</strong>：离开那些让你失声的环境，不是逃避，而是对自我价值的坚守。要勇敢寻找能让你发声的场域，即使意见相左也能得到尊重。</li><li><strong>自我价值与尊严</strong>：内心强大的人明白，与其在压抑的环境中获得表面接纳，不如在开放的氛围中享受真实对话。</li><li><strong>声音的价值</strong>：西蒙娜·德·波伏娃指出，不要把你的声音囚禁在别人设定的框架里。每个人都有表达的权利，都值得被倾听。</li><li><strong>人生选择</strong>：人生短暂而宝贵，不要把时间浪费在那些让你窒息的关系中。找到你的“部落”，在那里你的声音不仅能被听见，还能引起共鸣，甚至激发更多可能性。</li><li><strong>社会性动物的定义</strong>：亚里士多德指出，人是社会性动物，但这并不意味着要忍受任何形式的社交关系，而是要寻找那些能让我们成长、让我们发光的连接。</li></ul><h2 id="核心信息"><a href="#核心信息" class="headerlink" title="核心信息"></a>核心信息</h2><ul><li><strong>不要呆在没有话语权的小团体里</strong>，因为这会损害你的自我价值和尊严。</li><li><strong>寻找能让你发声的环境</strong>，在那里你的声音会被听见，你的观点会被尊重。</li><li><strong>人生短暂，不要浪费在让你窒息的关系中</strong>，找到真正能让你成长和发光的连接。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本视频强调了在缺乏话语权的环境中长期停留的负面影响，并鼓励人们勇敢寻找能让自己发声、被尊重的环境。通过真实表达和被倾听，实现自我价值和内心的平静。</p>]]></content>
    
    
    <summary type="html">B站大脑成长计划摘抄，识别缺乏话语权的小团体，避免被隐性权力关系消耗。</summary>
    
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/"/>
    
    <category term="B站大脑成长计划" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/B%E7%AB%99%E5%A4%A7%E8%84%91%E6%88%90%E9%95%BF%E8%AE%A1%E5%88%92/"/>
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/tags/%E6%91%98%E6%8A%84/"/>
    
  </entry>
  
  <entry>
    <title>摘抄：我发现有个输出型爱好真的很重要</title>
    <link href="https://blog.no-claw.com/posts/e7ef5dac/"/>
    <id>https://blog.no-claw.com/posts/e7ef5dac/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="核心观点"><a href="#核心观点" class="headerlink" title="核心观点"></a>核心观点</h2><p>视频强调了拥有一个<strong>输出型爱好</strong>对个人成长和心理健康的重要性。在信息爆炸的时代，人们往往陷入被动消费的循环，而忽视了主动创造的价值。通过输出，我们不仅能够将知识转化为自己的智慧，还能在创作过程中获得成就感和满足感，从而对抗现代生活的空虚与疲惫。</p><h2 id="关键内容总结"><a href="#关键内容总结" class="headerlink" title="关键内容总结"></a>关键内容总结</h2><span id="more"></span><h3 id="1-被动消费-vs-主动创造"><a href="#1-被动消费-vs-主动创造" class="headerlink" title="1. 被动消费 vs 主动创造"></a>1. <strong>被动消费 vs 主动创造</strong></h3><ul><li>现代人每天都在消费信息、娱乐和他人生活片段，却很少留下自己的痕迹。</li><li>信息洪流正在侵蚀我们的创造力和表达能力，使灵魂变得空洞和疲惫。</li></ul><h3 id="2-输出型爱好的意义"><a href="#2-输出型爱好的意义" class="headerlink" title="2. 输出型爱好的意义"></a>2. <strong>输出型爱好的意义</strong></h3><ul><li>输出型爱好（如写作、绘画、音乐、手工艺等）是将内心世界具象化的过程。</li><li>它不仅是技能的展示，更是思想的体现和情感的沉淀。</li></ul><h3 id="3-输出带来的心理与认知益处"><a href="#3-输出带来的心理与认知益处" class="headerlink" title="3. 输出带来的心理与认知益处"></a>3. <strong>输出带来的心理与认知益处</strong></h3><ul><li>创造性输出可以激活大脑多个区域，促进认知整合，增强自我效能感。</li><li>完成作品后，大脑会释放多巴胺，带来成就感和满足感。</li><li>这种正向反馈机制有助于对抗现代生活的空虚与倦怠。</li></ul><h3 id="4-输出型爱好与自我成长"><a href="#4-输出型爱好与自我成长" class="headerlink" title="4. 输出型爱好与自我成长"></a>4. <strong>输出型爱好与自我成长</strong></h3><ul><li>输出型爱好不是专业技能，而是表达的真诚与坚持。</li><li>它帮助我们从被动消费转向主动创造，成为生活的导演而非观众。</li><li>通过持续的创作，我们不仅在塑造作品，也在塑造自己。</li></ul><h3 id="5-历史与名人的启示"><a href="#5-历史与名人的启示" class="headerlink" title="5. 历史与名人的启示"></a>5. <strong>历史与名人的启示</strong></h3><ul><li>尼采说：“一个人必须拥有内心的混乱，才能孕育出一颗跳舞的星星。”</li><li>罗曼·罗兰说：“世界上只有一种英雄主义，就是认清生活真相后依然热爱生活。”</li><li>惠特曼说：“从今以后，我不再问‘我得到了什么’，而是问‘我能给予什么’。”</li></ul><h3 id="6-如何开始输出型爱好"><a href="#6-如何开始输出型爱好" class="headerlink" title="6. 如何开始输出型爱好"></a>6. <strong>如何开始输出型爱好</strong></h3><ul><li>不必追求宏大，可以从每天写三句话、周末做蛋糕、为家人唱一首歌开始。</li><li>通过实践，我们会发现被动消费的时间变得乏味，而创造的时刻则更加生动和珍贵。</li></ul><h2 id="重要引用"><a href="#重要引用" class="headerlink" title="重要引用"></a>重要引用</h2><ul><li>“<strong>想象力比知识更重要</strong>。” —— 爱因斯坦</li><li>“<strong>我们通过实践获得我们所学的东西</strong>。” —— 亚里士多德</li><li>“<strong>如果一个人坚定地走向自己的梦想，努力活出他想象中的生活，他一定会取得意想不到的成功</strong>。” —— 梭罗</li><li>“<strong>我们不是世界的旁观者，而是自己故事的叙述者和创造者</strong>。” —— 惠特曼</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>拥有一个输出型爱好，不仅是一种生活方式的转变，更是一种自我实现的途径。它让我们在信息洪流中保持清醒，在被动消费中找回主动创造的力量。从今天开始，尝试找到属于你的输出方式，让生活因创造而更加丰富多彩。</p>]]></content>
    
    
    <summary type="html">B站大脑成长计划摘抄，输出型爱好对个人成长和心理健康的重要性。</summary>
    
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/"/>
    
    <category term="B站大脑成长计划" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/B%E7%AB%99%E5%A4%A7%E8%84%91%E6%88%90%E9%95%BF%E8%AE%A1%E5%88%92/"/>
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/tags/%E6%91%98%E6%8A%84/"/>
    
  </entry>
  
  <entry>
    <title>摘抄：改变自己最快的方式：早起+密集做事</title>
    <link href="https://blog.no-claw.com/posts/806d38a3/"/>
    <id>https://blog.no-claw.com/posts/806d38a3/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV1mhTezyEhP/?spm_id_from=333.788.videopod.sections&vd_source=455daf3f8c90b0a5bd4160e8292c05f9">https://www.bilibili.com/video/BV1mhTezyEhP/?spm_id_from=333.788.videopod.sections&amp;vd_source=455daf3f8c90b0a5bd4160e8292c05f9</a></p><h2 id="核心观点"><a href="#核心观点" class="headerlink" title="核心观点"></a>核心观点</h2><p>改变自己的最快方式是<strong>早起</strong>并进行<strong>密集做事</strong>。通过早起，我们能够抓住一天中大脑最清醒的黄金时间，进行高效、专注的行动，从而实现自我提升和人生转变。</p><h2 id="关键信息"><a href="#关键信息" class="headerlink" title="关键信息"></a>关键信息</h2><span id="more"></span><h3 id="1-早起的意义"><a href="#1-早起的意义" class="headerlink" title="1. 早起的意义"></a>1. 早起的意义</h3><ul><li><strong>大脑最清醒</strong>：清晨是大脑最清醒、专注力最强的时刻，科学研究表明，此时的<strong>前额叶皮层</strong>最为活跃，意志力也达到一天的高峰。</li><li><strong>掌控人生</strong>：早起意味着你选择掌控自己的生活，而不是被生活所控制。</li><li><strong>时间价值</strong>：早起后的时间可以用于高效工作，使时间的价值成倍增长。</li></ul><h3 id="2-密集做事的定义"><a href="#2-密集做事的定义" class="headerlink" title="2. 密集做事的定义"></a>2. 密集做事的定义</h3><ul><li><strong>高效专注</strong>：密集做事不是盲目忙碌，而是<strong>有目的、有计划</strong>地进行高强度工作。</li><li><strong>多巴胺释放</strong>：专注工作时，大脑会释放<strong>多巴胺</strong>，提升学习效率并产生成就感，形成正向循环。</li><li><strong>成功案例</strong>：一位从普通大学毕业的年轻人，通过每天早起并进行三小时的高强度工作，最终成功进入顶级投行。</li></ul><h3 id="3-实践方法"><a href="#3-实践方法" class="headerlink" title="3. 实践方法"></a>3. 实践方法</h3><ul><li><strong>建立早起习惯</strong>：<ul><li><strong>固定作息</strong>：提前设定睡觉时间，确保有足够睡眠。</li><li><strong>设置闹钟位置</strong>：将闹钟放在床的另一端，迫使自己起床。</li><li><strong>设计早晨仪式</strong>：如喝咖啡、听音乐、阅读等，让早起变得愉快。</li></ul></li><li><strong>使用番茄工作法</strong>：<ul><li>每 25 分钟专注工作，休息 5 分钟，减少干扰。</li><li>研究表明，这种方法可提升工作效率至少 43%。</li></ul></li><li><strong>聚焦三件最重要的事</strong>：<ul><li>每晚列出<strong>三件最重要的任务</strong>，确保在早晨高效完成。</li><li>管理大师彼得·德鲁克强调：<strong>效率是做对的事，效果是做重要的事</strong>。</li></ul></li></ul><h3 id="4-应对挑战"><a href="#4-应对挑战" class="headerlink" title="4. 应对挑战"></a>4. 应对挑战</h3><ul><li><strong>坚持策略</strong>：<ul><li><strong>小进步优于完美计划</strong>：即使只早起 10 分钟，也比放弃更好。</li><li><strong>寻找同伴</strong>：与志同道合的人一起实践，互相支持和激励。</li><li><strong>记录与反思</strong>：通过日记记录早起时间和完成任务，定期回顾进步与不足。</li></ul></li><li><strong>习惯的力量</strong>：<ul><li>康德曾说：“<strong>习惯是人的第二本性</strong>”，一旦早起和高效工作成为习惯，改变将变得自然。</li></ul></li></ul><h2 id="重要引用"><a href="#重要引用" class="headerlink" title="重要引用"></a>重要引用</h2><ul><li>罗曼·罗兰：“世界上只有一种英雄主义，就是在认清生活真相之后依然热爱生活。”</li><li>塞涅卡：“生命的长度不是由时间决定的，而是由你如何使用时间决定的。”</li><li>康德：“<strong>习惯是人的第二本性</strong>，它比自然更强大。”</li><li>管理大师彼得·德鲁克：“<strong>效率是做对的事，效果是做重要的事</strong>。”</li><li>爱因斯坦：“<strong>我们的行为塑造了我们的性格，而性格决定了我们的命运</strong>。”</li><li>卡耐基：“<strong>如果你想拥有从未拥有过的东西，你必须去做从未做过的事</strong>。”</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>早起和密集做事是一种<strong>生活方式</strong>，也是一种<strong>对自我的承诺</strong>。它不仅改变了你的生活，也在重塑你的灵魂。从今天开始，让早起和高效工作成为你改变自己的力量，让每一个清晨都见证你迈向更好的自己。</p>]]></content>
    
    
    <summary type="html">B站大脑成长计划摘抄，早起抓住大脑黄金时间，密集做事加速自我改变。</summary>
    
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/"/>
    
    <category term="B站大脑成长计划" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/B%E7%AB%99%E5%A4%A7%E8%84%91%E6%88%90%E9%95%BF%E8%AE%A1%E5%88%92/"/>
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/tags/%E6%91%98%E6%8A%84/"/>
    
  </entry>
  
  <entry>
    <title>摘抄：无事不要讲话，讲话只有三个目的</title>
    <link href="https://blog.no-claw.com/posts/78d33cb9/"/>
    <id>https://blog.no-claw.com/posts/78d33cb9/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="核心观点"><a href="#核心观点" class="headerlink" title="核心观点"></a>核心观点</h2><ul><li><strong>语言如刀</strong>：言语一旦说出，便难以收回，因此要谨慎使用。</li><li><strong>沉默的力量</strong>：在信息爆炸的时代，沉默往往比言语更有价值。</li><li><strong>有效言语的三大目的</strong>：传达信息、建立关系、推动行动。<span id="more"></span></li></ul><h2 id="关键信息"><a href="#关键信息" class="headerlink" title="关键信息"></a>关键信息</h2><h3 id="一、无效言语的危害"><a href="#一、无效言语的危害" class="headerlink" title="一、无效言语的危害"></a>一、无效言语的危害</h3><ul><li><strong>消耗精力</strong>：言语会占用有限的认知资源，使人疲惫。</li><li><strong>损害信誉</strong>：过多言语让人产生免疫力，降低他人对你的信任。</li><li><strong>暴露弱点</strong>：言语失控会暴露个人的弱点和秘密，影响人际关系和职场表现。</li></ul><h3 id="二、有效言语的三大目的"><a href="#二、有效言语的三大目的" class="headerlink" title="二、有效言语的三大目的"></a>二、有效言语的三大目的</h3><ol><li><p><strong>传达信息</strong></p><ul><li>语言最基本的功能是传递信息，但要精准传达关键内容。</li><li>优秀沟通者能用最简洁的语言表达最复杂的信息。</li></ul></li><li><p><strong>建立关系</strong></p><ul><li>语言是建立和维护情感连接的工具。</li><li>高情商的人懂得倾听，而非一味表达，建立更深层次的关系。</li></ul></li><li><p><strong>推动行动</strong></p><ul><li>有效言语能激发他人行动，产生实际改变。</li><li>例如，领导者的激励讲话、销售的促成技巧、父母的教育方式等。</li></ul></li></ol><h3 id="三、如何做到言简意赅"><a href="#三、如何做到言简意赅" class="headerlink" title="三、如何做到言简意赅"></a>三、如何做到言简意赅</h3><ol><li><p><strong>言前思虑</strong></p><ul><li>说话前问自己三个问题：是否必要、是否合适、是否由我表达。</li><li>建立自我筛选机制，提升言语质量。</li></ul></li><li><p><strong>多听少说</strong></p><ul><li>人类有两只耳朵和一张嘴，应多听少说。</li><li>交谈中，倾听时间占 2&#x2F;3，说话时间不超过 1&#x2F;3。</li></ul></li><li><p><strong>避免情绪化表达</strong></p><ul><li>情绪激动时，理性思维下降，容易表达混乱。</li><li>遇到情绪波动时，应保持沉默，冷静后再表达。</li></ul></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><strong>言语的价值</strong>：在信息过载的时代，言语应被珍惜，每句话都应有其意义。</li><li><strong>真正的智慧</strong>：不在于说得多，而在于知道何时说、何时不说，以及如何用最精准的语言表达最有价值的思想。</li><li><strong>成为有影响力的人</strong>：在喧嚣的世界中，做一个懂得珍惜言语的人，让每一句话都值得倾听，如金玉般珍贵。</li></ul>]]></content>
    
    
    <summary type="html">B站大脑成长计划摘抄，无事不要讲话，开口只为三个目的。</summary>
    
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/"/>
    
    <category term="B站大脑成长计划" scheme="https://blog.no-claw.com/categories/%E6%91%98%E6%8A%84/B%E7%AB%99%E5%A4%A7%E8%84%91%E6%88%90%E9%95%BF%E8%AE%A1%E5%88%92/"/>
    
    
    <category term="摘抄" scheme="https://blog.no-claw.com/tags/%E6%91%98%E6%8A%84/"/>
    
  </entry>
  
  <entry>
    <title>Docker 启动 Easysearch 时自定义初始密码的几种方式</title>
    <link href="https://blog.no-claw.com/posts/588b9d1d/"/>
    <id>https://blog.no-claw.com/posts/588b9d1d/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 Docker 部署 <strong>Easysearch</strong> 时，通常需要在启动容器时设置初始管理员密码。如果没有预先设置密码，系统可能会使用默认值或随机生成密码，不仅增加后续管理的复杂性，也存在安全隐患。</p><p>本文将详细介绍在 <code>docker run</code> 启动 Easysearch 容器时，通过不同方式传入环境变量（<code>env</code>）来自定义密码的多种方法。每种方法都配有实用示例和说明，帮助你根据实际环境灵活选择。</p>  <span id="more"></span><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></pre></td><td class="code"><pre><span class="line">docker run --name easysearch \</span><br><span class="line">  -e EASYSEARCH_INITIAL_ADMIN_PASSWORD=passwd123 \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><p>启动完成后，可进入容器验证变量是否生效：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it easysearch bash</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$EASYSEARCH_INITIAL_ADMIN_PASSWORD</span></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">passwd123</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005152811756.png" alt="image-20251005152811756"></p><p>这个是在容器内部查看 ENV 的结果：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005152846742.png" alt="image-20251005152846742"></p><p>✅ <strong>优点：</strong></p><ul><li>简单直接，适合手动调试；</li><li>不需要额外文件。</li></ul><p>⚠️ <strong>缺点：</strong></p><ul><li>密码会暴露在命令行历史中；</li><li>修改不便，不适合生产环境。</li></ul><h3 id="🗂-二、从环境文件加载（推荐）"><a href="#🗂-二、从环境文件加载（推荐）" class="headerlink" title="🗂 二、从环境文件加载（推荐）"></a>🗂 二、从环境文件加载（推荐）</h3><p>如果你需要设置多个变量或希望更好地管理配置，建议使用 <code>.env</code> 文件。<br>这种方式清晰、安全、易于维护，是 <strong>最推荐的做法</strong>。</p><h4 id="创建-env-文件"><a href="#创建-env-文件" class="headerlink" title="创建 .env 文件"></a>创建 <code>.env</code> 文件</h4><p>在当前目录下新建 <code>.env</code> 文件：</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">EASYSEARCH_INITIAL_ADMIN_PASSWORD=envfile123</span><br></pre></td></tr></table></figure><h4 id="启动容器："><a href="#启动容器：" class="headerlink" title="启动容器："></a>启动容器：</h4><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">docker run --name easysearch \</span><br><span class="line">  --env-file .<span class="built_in">env</span> \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005153637750.png" alt="image-20251005153637750"></p><p>验证变量：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it easysearch bash</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$EASYSEARCH_INITIAL_ADMIN_PASSWORD</span></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">envfile123</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005154705982.png" alt="image-20251005154705982"></p><p>✅ <strong>优点：</strong></p><ul><li>环境变量集中管理；</li><li>避免明文密码出现在命令行；</li><li>文件可复用、便于协作。</li></ul><p>⚠️ <strong>注意事项：</strong></p><ul><li><p><code>.env</code> 文件路径需正确（相对或绝对路径）；</p></li><li><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">EASYSEARCH_INITIAL_ADMIN_PASSWORD=&quot;P@ss word!123&quot;</span><br></pre></td></tr></table></figure></li></ul><p>💡 <strong>补充说明：</strong></p><ul><li>在 <strong>Docker Compose</strong> 中，<code>.env</code> 文件会自动读取；</li><li>使用 <code>docker run</code> 时，必须显式指定 <code>--env-file</code>。</li></ul><h3 id="💻-三、引用宿主机环境变量"><a href="#💻-三、引用宿主机环境变量" class="headerlink" title="💻 三、引用宿主机环境变量"></a>💻 三、引用宿主机环境变量</h3><p>当宿主机上已经定义了某些变量时，也可以直接将它们传递给容器。<br>这种方式非常适合自动化脚本和 CI&#x2F;CD 场景。</p><h4 id="1️⃣-导出宿主机变量"><a href="#1️⃣-导出宿主机变量" class="headerlink" title="1️⃣ 导出宿主机变量"></a>1️⃣ 导出宿主机变量</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> EASYSEARCH_INITIAL_ADMIN_PASSWORD=hostenv123</span><br></pre></td></tr></table></figure><h4 id="2️⃣-启动容器时引用"><a href="#2️⃣-启动容器时引用" class="headerlink" title="2️⃣ 启动容器时引用"></a>2️⃣ 启动容器时引用</h4><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">docker run -e EASYSEARCH_INITIAL_ADMIN_PASSWORD \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</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">docker run --<span class="built_in">env</span> EASYSEARCH_INITIAL_ADMIN_PASSWORD \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005153800103.png" alt="image-20251005153800103"></p><p>验证变量：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it easysearch bash</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$EASYSEARCH_INITIAL_ADMIN_PASSWORD</span></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">hostenv123</span><br></pre></td></tr></table></figure><p>✅ <strong>优点：</strong></p><ul><li>更安全，不会在命令行中暴露；</li><li>适用于 CI&#x2F;CD 环境和自动化部署。</li></ul><p>⚠️ <strong>注意：</strong></p><ul><li>启动容器前确认变量已导出；</li><li>使用 <code>printenv</code> 查看宿主机变量。</li></ul><h3 id="🔒-四、生产级方案：结合-Secret-管理工具"><a href="#🔒-四、生产级方案：结合-Secret-管理工具" class="headerlink" title="🔒 四、生产级方案：结合 Secret 管理工具"></a>🔒 四、生产级方案：结合 Secret 管理工具</h3><p>在生产环境中，建议不要直接在 <code>.env</code> 文件或命令行中存储密码。<br>可以结合以下工具来安全地管理敏感信息：</p><ul><li><strong>Docker Secrets</strong>：在 Swarm 模式中安全注入敏感值；</li><li><strong>HashiCorp Vault &#x2F; AWS Secrets Manager &#x2F; Kubernetes Secrets</strong>：通过外部密钥服务动态传递；</li><li>**<code>.gitignore</code>**：确保 <code>.env</code> 文件不被提交到代码仓库。</li></ul><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></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it easysearch <span class="built_in">env</span> | grep EASYSEARCH_INITIAL_ADMIN_PASSWORD</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">docker inspect easysearch | grep EASYSEARCH_INITIAL_ADMIN_PASSWORD</span><br></pre></td></tr></table></figure><p>若输出包含你的密码值，即表示设置成功。</p><h3 id="🧩-六、不同方式对比"><a href="#🧩-六、不同方式对比" class="headerlink" title="🧩 六、不同方式对比"></a>🧩 六、不同方式对比</h3><table><thead><tr><th>方式</th><th>适用场景</th><th>安全性</th><th>便利性</th><th>是否推荐</th></tr></thead><tbody><tr><td>命令行直接传入</td><td>快速测试</td><td>⭐</td><td>⭐⭐⭐</td><td>❌</td></tr><tr><td><code>.env</code> 文件</td><td>开发&#x2F;测试</td><td>⭐⭐</td><td>⭐⭐⭐⭐</td><td>✅</td></tr><tr><td>宿主机变量</td><td>自动化部署</td><td>⭐⭐⭐</td><td>⭐⭐⭐</td><td>✅</td></tr><tr><td>Secret 工具</td><td>生产环境</td><td>⭐⭐⭐⭐</td><td>⭐⭐</td><td>✅✅✅</td></tr></tbody></table><h3 id="📦-七、综合示例：多变量-默认值"><a href="#📦-七、综合示例：多变量-默认值" class="headerlink" title="📦 七、综合示例：多变量 + 默认值"></a>📦 七、综合示例：多变量 + 默认值</h3><p>假设我们要在生产环境部署，并定义多个参数：</p><p><code>.env</code> 文件内容如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">EASYSEARCH_INITIAL_ADMIN_PASSWORD=$&#123;EASYSEARCH_PASS:-default123&#125;</span><br><span class="line">EASYSEARCH_CLUSTER_NAME=prod_cluster</span><br><span class="line">EASYSEARCH_HEAP_SIZE=4g</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">EASYSEARCH_PASS=SuperSecret123 \</span><br><span class="line">docker run --name easysearch-prod \</span><br><span class="line">  --env-file .<span class="built_in">env</span> \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><p>此时：</p><ul><li>若外部定义了 <code>EASYSEARCH_PASS</code>，则使用该值；</li><li>否则回退到 <code>.env</code> 文件中的默认值 <code>default123</code>。</li></ul><h3 id="🧭-总结"><a href="#🧭-总结" class="headerlink" title="🧭 总结"></a>🧭 总结</h3><p>在 Docker 环境中配置 Easysearch 初始密码的常用方式主要有三种：</p><ol><li><strong>命令行传入（-e）</strong>：适合快速测试；</li><li><strong>环境文件（–env-file）</strong>：管理方便、最推荐；</li><li><strong>引用宿主机变量</strong>：安全灵活，适合 CI&#x2F;CD。</li></ol><blockquote><p>🚀 <strong>最佳实践建议：</strong></p><ul><li>开发环境使用 <code>.env</code> 文件；</li><li>生产环境使用 Secret 管理；</li><li>将 <code>.env</code> 文件加入 <code>.gitignore</code>；</li><li>定期轮换密码，确保安全。</li></ul></blockquote><p><strong>参考命令：</strong></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">docker run --name easysearch \</span><br><span class="line">  -e EASYSEARCH_INITIAL_ADMIN_PASSWORD=passwd123 \</span><br><span class="line">  --<span class="built_in">ulimit</span> memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><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">version: <span class="string">&quot;3.3&quot;</span></span><br><span class="line">services:</span><br><span class="line">  easysearch:</span><br><span class="line">    restart: always</span><br><span class="line">    image: registry.lazycat.cloud/u04123229/infinilabs/easysearch:5dc6296f5370aacc</span><br><span class="line">    ports:</span><br><span class="line">      - 9200:9200</span><br><span class="line">    environment:</span><br><span class="line">      - EASYSEARCH_INITIAL_ADMIN_PASSWORD=admin123</span><br><span class="line">networks: &#123;&#125;ß</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">介绍 Docker 启动 Easysearch 时自定义初始密码的多种配置方法</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>不用每次都改 `easysearch.yml` 也能改启动参数 —— 用 Docker 环境变量搞定一切</title>
    <link href="https://blog.no-claw.com/posts/ce1ebcd3/"/>
    <id>https://blog.no-claw.com/posts/ce1ebcd3/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:05:13.619Z</updated>
    
    <content type="html"><![CDATA[<p>在用 Docker 部署 Easysearch 的时候，很多人习惯性地去改容器里的 <code>easysearch.yml</code>。<br>但每改一次，就得重建镜像或重新挂载配置，既不方便，也不利于自动化。</p><p>其实，Docker 天生就支持通过<strong>环境变量</strong>来传递参数。<br>只要我们把要改的配置写进 <code>.env</code> 文件，再用 <code>--env-file</code> 加载，就能在启动时覆盖 <code>easysearch.yml</code> 的对应设置。<br>这样，既不用改镜像，也不用动配置文件，还能方便地调试、切换和管理。</p>  <span id="more"></span><p>下面就来详细讲讲这套思路的原理、写法与实践。</p><h3 id="Docker-环境变量机制简介"><a href="#Docker-环境变量机制简介" class="headerlink" title="Docker 环境变量机制简介"></a>Docker 环境变量机制简介</h3><p>Docker 启动容器时，会把宿主机上的环境变量传递进容器内部。<br>容器里的程序（例如 Easysearch）在启动时，会读取这些变量并用来覆盖或替代默认配置。</p><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">环境变量 &gt; easysearch.yml &gt; 默认值</span><br></pre></td></tr></table></figure><p>也就是说，只要我们在启动容器时提供了对应的环境变量，就能覆盖掉配置文件里的同名参数。<br>这就是“用 Docker 环境变量替代修改配置文件”的原理。</p><h3 id="env-文件写法"><a href="#env-文件写法" class="headerlink" title=".env 文件写法"></a><code>.env</code> 文件写法</h3><p>先准备一个 <code>.env</code> 文件（放在和 Docker 命令同级的目录下）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">EASYSEARCH_INITIAL_ADMIN_PASSWORD=envfile123</span><br><span class="line">cluster.name=mysearch</span><br><span class="line">elasticsearch.api_compatibility=true</span><br><span class="line">node.name=node-1</span><br><span class="line">network.host=0.0.0.0</span><br></pre></td></tr></table></figure><p>这几个变量的含义如下：</p><table><thead><tr><th>变量名</th><th>功能</th><th>说明</th></tr></thead><tbody><tr><td><code>EASYSEARCH_INITIAL_ADMIN_PASSWORD</code></td><td>初始化管理员密码</td><td>推荐通过环境变量传递，安全又方便</td></tr><tr><td><code>cluster.name</code></td><td>集群名称</td><td>多节点部署时保持一致</td></tr><tr><td><code>elasticsearch.api_compatibility</code></td><td>是否启用 ES API 兼容模式</td><td>一般设为 <code>true</code>，方便客户端兼容</td></tr><tr><td><code>node.name</code></td><td>节点名称</td><td>区分不同节点</td></tr><tr><td><code>network.host</code></td><td>监听地址</td><td><code>0.0.0.0</code> 表示允许外部访问</td></tr></tbody></table><p><code>.env</code> 文件的格式非常简单，每行一个 <code>key=value</code>，不要有多余空格，也不要加引号。<br>文件放在项目目录下即可，Docker 会自动读取。</p><h3 id="启动容器：用-env-文件注入配置"><a href="#启动容器：用-env-文件注入配置" class="headerlink" title="启动容器：用 .env 文件注入配置"></a>启动容器：用 <code>.env</code> 文件注入配置</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></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  --name easysearch \</span><br><span class="line">  --env-file ./.env \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  -p 9300:9300 \</span><br><span class="line">  easysearch:latest</span><br></pre></td></tr></table></figure><p>这里的 <code>--env-file ./.env</code> 参数告诉 Docker 从 <code>.env</code> 文件中加载变量。<br>Docker 会自动把 <code>.env</code> 中定义的内容注入到容器环境中，EasySearch 启动时就会自动读取。</p><p>如果你想在启动时再临时改动一个参数，可以直接加 <code>-e</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 easysearch \</span><br><span class="line">  --env-file ./.env \</span><br><span class="line">  -e EASYSEARCH_INITIAL_ADMIN_PASSWORD=override123 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  -p 9300:9300 \</span><br><span class="line">  easysearch:latest</span><br></pre></td></tr></table></figure><p>这时候命令行里的 <code>-e</code> 会优先于 <code>.env</code> 文件的值。</p><h3 id="验证环境变量是否生效"><a href="#验证环境变量是否生效" class="headerlink" title="验证环境变量是否生效"></a>验证环境变量是否生效</h3><p>容器启动完成后，可以用 <code>curl</code> 验证 EasySearch 是否按 <code>.env</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">curl -s -u admin:envfile123 http://localhost:9200</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></pre></td><td class="code"><pre><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;node-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mysearch&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;number&quot;</span><span class="punctuation">:</span> <span class="string">&quot;8.13.0&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tagline&quot;</span><span class="punctuation">:</span> <span class="string">&quot;You Know, for Search&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005161811392.png" alt="image-20251005161811392"></p><p>几个关键字段说明环境变量确实生效：</p><ul><li><code>&quot;name&quot;: &quot;node-1&quot;</code> 来自 <code>node.name</code></li><li><code>&quot;cluster_name&quot;: &quot;mysearch&quot;</code> 来自 <code>cluster.name</code></li><li>管理员密码能登录，说明 <code>EASYSEARCH_INITIAL_ADMIN_PASSWORD</code> 已应用</li></ul><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></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> easysearch <span class="built_in">env</span></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">➜  未命名文件夹 14 docker exec easysearch env</span><br><span class="line"></span><br><span class="line">PATH=/sbin:/app/easysearch/jdk/bin:/app/easysearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</span><br><span class="line">HOSTNAME=f3de6d6ab781</span><br><span class="line">EASYSEARCH_INITIAL_ADMIN_PASSWORD=envfile123</span><br><span class="line">cluster.name=mysearch</span><br><span class="line">elasticsearch.api_compatibility=true</span><br><span class="line">node.name=node-1</span><br><span class="line">network.host=0.0.0.0</span><br><span class="line">HOME=/root</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005163754887.png" alt="image-20251005163754887"></p><p>或者只看我们关心的部分：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> easysearch <span class="built_in">env</span> | grep EASYSEARCH</span><br><span class="line">docker <span class="built_in">exec</span> easysearch <span class="built_in">env</span> | grep cluster</span><br></pre></td></tr></table></figure><p>这样能清楚看到 <code>.env</code> 文件中定义的变量是否真的被传进去了。<br>如果某个值缺失或拼写错误，这个方法很容易排查。</p><h3 id="环境变量与配置文件的关系"><a href="#环境变量与配置文件的关系" class="headerlink" title="环境变量与配置文件的关系"></a>环境变量与配置文件的关系</h3><p>在 Docker 镜像中，EasySearch 通常有一个默认的 <code>easysearch.yml</code>。<br>当容器启动时，程序会按以下优先级加载配置：</p><ol><li><strong>命令行参数或 <code>-e</code> 指定的环境变量</strong></li><li><strong><code>--env-file</code> 传入的变量</strong></li><li><strong>容器内 <code>/etc/easysearch/easysearch.yml</code> 文件</strong></li><li><strong>内置默认值</strong></li></ol><p>因此，当你通过 <code>.env</code> 或 <code>-e</code> 设置参数后，<strong>这些值会覆盖配置文件里的同名项</strong>。<br>你完全不需要去修改容器内部的配置文件。</p><p>这正是现代容器化部署推荐的做法：<br>配置文件保持模板化，动态参数全部用环境变量注入。</p><h3 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h3><h4 id="1-保留-env-example-模板"><a href="#1-保留-env-example-模板" class="headerlink" title="1. 保留 .env.example 模板"></a>1. 保留 <code>.env.example</code> 模板</h4><p>在项目目录中放一个 <code>.env.example</code> 文件，内容示例化：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">EASYSEARCH_INITIAL_ADMIN_PASSWORD=changeme</span><br><span class="line">cluster.name=my-cluster</span><br><span class="line">elasticsearch.api_compatibility=true</span><br><span class="line">node.name=node-1</span><br><span class="line">network.host=0.0.0.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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> .env.example .<span class="built_in">env</span></span><br></pre></td></tr></table></figure><p>再修改必要的值即可。</p><h4 id="2-env-不要进版本库"><a href="#2-env-不要进版本库" class="headerlink" title="2. .env 不要进版本库"></a>2. <code>.env</code> 不要进版本库</h4><p>把 <code>.env</code> 加入 <code>.gitignore</code>，避免把真实密码上传。</p><h4 id="3-用不同-env-文件区分环境"><a href="#3-用不同-env-文件区分环境" class="headerlink" title="3. 用不同 .env 文件区分环境"></a>3. 用不同 <code>.env</code> 文件区分环境</h4><p>你可以创建多份环境文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.env.dev</span><br><span class="line">.env.staging</span><br><span class="line">.env.prod</span><br></pre></td></tr></table></figure><p>启动时指定不同的文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --env-file ./.env.dev ...</span><br><span class="line">docker run -d --env-file ./.env.prod ...</span><br></pre></td></tr></table></figure><p>这样一套镜像就能跑多个环境，彻底解耦配置与部署。</p><h3 id="总结：环境变量让部署更轻、更灵活"><a href="#总结：环境变量让部署更轻、更灵活" class="headerlink" title="总结：环境变量让部署更轻、更灵活"></a>总结：环境变量让部署更轻、更灵活</h3><p>通过 Docker 的环境变量机制，我们可以：</p><ul><li>不再频繁修改 <code>easysearch.yml</code>；</li><li>用 <code>.env</code> 文件集中管理参数；</li><li>轻松区分不同环境；</li><li>无需重建镜像就能调整配置；</li><li>安全地注入密码等敏感信息。</li></ul><p>从此以后，部署 EasySearch 只需要两步：</p><ol><li>准备 <code>.env</code>；</li><li>一条 <code>docker run --env-file</code> 命令。</li></ol><p>所有的参数都能即时生效，<strong>配置文件原封不动</strong>。<br>这就是现代容器化运维的思路：</p><blockquote><p>“配置解耦、参数注入、环境即定义。”</p></blockquote><p>当你下次用 <code>curl</code> 看见返回里显示的<br><code>cluster_name: mysearch</code>、<code>name: node-1</code>，<br>那就是 <code>.env</code> 的功劳——<br>再也不用去翻 <code>easysearch.yml</code>。</p>]]></content>
    
    
    <summary type="html">通过 Docker 环境变量修改 Easysearch 启动参数，无需手动编辑配置文件</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 索引别名（Index Alias）详解</title>
    <link href="https://blog.no-claw.com/posts/c41993b0/"/>
    <id>https://blog.no-claw.com/posts/c41993b0/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Easysearch 中，<strong>索引别名（Index Alias）</strong> 是一种逻辑名称，它可以指向一个或多个真实索引。<br>使用别名的好处在于：</p><ul><li>让应用层无需感知底层索引名变化；</li><li>方便进行索引切换、版本升级和数据迁移；</li><li>支持查询、写入、过滤、路由等控制；</li><li>实现读写分离或权限隔离。<span id="more"></span>简而言之，<strong>别名是索引的抽象层</strong>，就像数据库中的“视图（View）”或操作系统中的“符号链接（symlink）”。</li></ul><h3 id="创建索引别名"><a href="#创建索引别名" class="headerlink" title="创建索引别名"></a>创建索引别名</h3><p>别名可以在创建索引时定义，也可以在已有索引上添加。</p><h4 id="在创建索引时定义别名"><a href="#在创建索引时定义别名" class="headerlink" title="在创建索引时定义别名"></a>在创建索引时定义别名</h4><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">PUT /logs_2025-10</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;aliases&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;logs_current&quot;</span>: &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该操作创建索引 <code>logs_2025-10</code>，并同时定义一个别名 <code>logs_current</code>。</p><p>之后，所有针对 <code>logs_current</code> 的查询都会路由到 <code>logs_2025-10</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></pre></td><td class="code"><pre><span class="line">POST logs_2025-10/_doc</span><br><span class="line">&#123;<span class="string">&quot;age&quot;</span>:20&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">GET /logs_current/_search</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006083254551.png"></p><h4 id="给现有索引添加别名"><a href="#给现有索引添加别名" class="headerlink" title="给现有索引添加别名"></a>给现有索引添加别名</h4><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">POST /logs_2025-10/_alias/logs_current_v2</span><br></pre></td></tr></table></figure><p>或者使用 <code>_aliases</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">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123; <span class="string">&quot;add&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_current_v3&quot;</span> &#125;&#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006083715981.png" alt="image-20251006083715981"></p><h3 id="查询与写入的区别"><a href="#查询与写入的区别" class="headerlink" title="查询与写入的区别"></a>查询与写入的区别</h3><p>默认情况下，<strong>别名仅支持查询</strong>。<br>如果一个别名指向多个索引，那么写入（<code>POST /alias/_doc</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_argument_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;no write index is defined for alias [logs_current_v2]. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_argument_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;no write index is defined for alias [logs_current_v2]. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index&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;status&quot;</span><span class="punctuation">:</span> <span class="number">400</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006084352866.png" alt="image-20251006084352866"></p><p>为了解决这一问题，可以通过 <code>is_write_index</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">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123; <span class="string">&quot;add&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10-01&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_all&quot;</span> &#125;&#125;,</span><br><span class="line">    &#123; <span class="string">&quot;add&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10-02&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_all&quot;</span>, <span class="string">&quot;is_write_index&quot;</span>: <span class="literal">true</span> &#125;&#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时：</p><ul><li>查询 <code>GET /logs_all/_search</code> 会同时检索两个索引；</li><li>写入 <code>POST /logs_all/_doc</code> 时，数据会写入 <code>logs_2025-10</code>。</li></ul><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2025/10/06/1759712200781-aa062578-d23a-4da2-8e4b-53de48e97a28.png"></p><h3 id="动态切换索引（零停机升级）"><a href="#动态切换索引（零停机升级）" class="headerlink" title="动态切换索引（零停机升级）"></a>动态切换索引（零停机升级）</h3><p>别名的最大优势之一是实现索引的<strong>无缝切换</strong>。</p><p>例如，应用程序始终通过 <code>logs_all</code> 查询数据，而底层实际索引会按天数变化。</p><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 /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123; <span class="string">&quot;remove&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10-01&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_all&quot;</span> &#125;&#125;,</span><br><span class="line">    &#123; <span class="string">&quot;add&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10-03&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_all&quot;</span> &#125;&#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我移除了 logs_2025-10-01，然后添加了 logs_2025-10-03。</p><p>可以使用<code>GET /_cat/aliases?v</code>查看。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006085901163.png" alt="image-20251006085901163"></p><h3 id="过滤别名（Filtered-Alias）"><a href="#过滤别名（Filtered-Alias）" class="headerlink" title="过滤别名（Filtered Alias）"></a>过滤别名（Filtered Alias）</h3><p>别名还可以定义过滤条件，控制用户只能看到部分数据。<br>这是实现<strong>数据分区视图或权限隔离</strong>的常见方式。</p><p>它展示如何让一个别名只返回 <code>region=china</code> 的文档，而不暴露其他地区的数据。</p><h4 id="1-创建一个示例索引并插入数据"><a href="#1-创建一个示例索引并插入数据" class="headerlink" title="1. 创建一个示例索引并插入数据"></a>1. 创建一个示例索引并插入数据</h4><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></pre></td><td class="code"><pre><span class="line">PUT /transactions</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;region&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;user&quot;</span>:   &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;amount&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;float&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">POST /transactions/_bulk</span><br><span class="line">&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_id&quot;</span>: 1 &#125; &#125;</span><br><span class="line">&#123; <span class="string">&quot;region&quot;</span>: <span class="string">&quot;china&quot;</span>, <span class="string">&quot;user&quot;</span>: <span class="string">&quot;alice&quot;</span>, <span class="string">&quot;amount&quot;</span>: 100.0 &#125;</span><br><span class="line">&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_id&quot;</span>: 2 &#125; &#125;</span><br><span class="line">&#123; <span class="string">&quot;region&quot;</span>: <span class="string">&quot;usa&quot;</span>, <span class="string">&quot;user&quot;</span>: <span class="string">&quot;bob&quot;</span>, <span class="string">&quot;amount&quot;</span>: 200.0 &#125;</span><br><span class="line">&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_id&quot;</span>: 3 &#125; &#125;</span><br><span class="line">&#123; <span class="string">&quot;region&quot;</span>: <span class="string">&quot;china&quot;</span>, <span class="string">&quot;user&quot;</span>: <span class="string">&quot;cindy&quot;</span>, <span class="string">&quot;amount&quot;</span>: 150.0 &#125;</span><br><span class="line">&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_id&quot;</span>: 4 &#125; &#125;</span><br><span class="line">&#123; <span class="string">&quot;region&quot;</span>: <span class="string">&quot;japan&quot;</span>, <span class="string">&quot;user&quot;</span>: <span class="string">&quot;daisuke&quot;</span>, <span class="string">&quot;amount&quot;</span>: 300.0 &#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">POST /transactions/_refresh</span><br></pre></td></tr></table></figure><h4 id="创建过滤别名"><a href="#创建过滤别名" class="headerlink" title="创建过滤别名"></a>创建过滤别名</h4><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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;add&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;index&quot;</span>: <span class="string">&quot;transactions&quot;</span>,</span><br><span class="line">        <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;transactions_cn&quot;</span>,</span><br><span class="line">        <span class="string">&quot;filter&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;term&quot;</span>: &#123; <span class="string">&quot;region&quot;</span>: <span class="string">&quot;china&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">&#125;</span><br></pre></td></tr></table></figure><h4 id="使用过滤别名查询"><a href="#使用过滤别名查询" class="headerlink" title="使用过滤别名查询"></a>使用过滤别名查询</h4><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 /transactions_cn/_search</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><span class="line">13</span><br><span class="line">14</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;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;hits&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="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;china&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="string">&quot;alice&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;amount&quot;</span><span class="punctuation">:</span> <span class="number">100.0</span> <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="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;china&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cindy&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;amount&quot;</span><span class="punctuation">:</span> <span class="number">150.0</span> <span class="punctuation">&#125;</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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>可以看到：</p><ul><li>来自 <code>usa</code> 和 <code>japan</code> 的记录不会出现在结果中；</li><li>别名层面自动做了过滤；</li><li>应用层调用时完全不需要在查询语句中加 <code>term</code> 条件。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006090520921.png" alt="image-20251006090520921"></p><h3 id="路由别名（Routing-Alias）"><a href="#路由别名（Routing-Alias）" class="headerlink" title="路由别名（Routing Alias）"></a>路由别名（Routing Alias）</h3><p>Elasticsearch 的数据分片（sharding）是通过一个公式决定的：</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">shard = hash(routing) % number_of_primary_shards</span><br></pre></td></tr></table></figure><ul><li>默认情况下，routing &#x3D; <code>_id</code></li><li>但如果你有多租户、分国家、分部门的场景，可以用业务逻辑字段当作 routing。</li><li>routing 相同的数据会落在同一个分片上，提高写入和查询的性能。</li></ul><p>因此：</p><blockquote><p>把别名和 routing 绑定起来，可以实现“逻辑分区 + 性能优化 + 查询隔离”。</p></blockquote><p>下面通过一个完整的数据例子演示。</p><h4 id="创建索引"><a href="#创建索引" class="headerlink" title="创建索引"></a>创建索引</h4><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">PUT <span class="built_in">users</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;number_of_shards&quot;</span>: 4,</span><br><span class="line">    <span class="string">&quot;number_of_replicas&quot;</span>: 0</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;name&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;country&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;age&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;integer&quot;</span> &#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><h4 id="创建带-routing-的别名"><a href="#创建带-routing-的别名" class="headerlink" title="创建带 routing 的别名"></a>创建带 routing 的别名</h4><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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;add&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;index&quot;</span>: <span class="string">&quot;users&quot;</span>,</span><br><span class="line">        <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;users_cn&quot;</span>,</span><br><span class="line">        <span class="string">&quot;routing&quot;</span>: <span class="string">&quot;china&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;add&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;index&quot;</span>: <span class="string">&quot;users&quot;</span>,</span><br><span class="line">        <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;users_us&quot;</span>,</span><br><span class="line">        <span class="string">&quot;routing&quot;</span>: <span class="string">&quot;usa&quot;</span></span><br><span class="line">      &#125;</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><table><thead><tr><th>Alias</th><th>Routing</th><th>用途</th></tr></thead><tbody><tr><td><code>users_cn</code></td><td><code>&quot;china&quot;</code></td><td>代表中国用户</td></tr><tr><td><code>users_us</code></td><td><code>&quot;usa&quot;</code></td><td>代表美国用户</td></tr></tbody></table><h4 id="通过别名写入数据"><a href="#通过别名写入数据" class="headerlink" title="通过别名写入数据"></a>通过别名写入数据</h4><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></pre></td><td class="code"><pre><span class="line">POST users_cn/_doc</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;country&quot;</span>: <span class="string">&quot;CN&quot;</span>,</span><br><span class="line">  <span class="string">&quot;age&quot;</span>: 29</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">POST users_cn/_doc</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;country&quot;</span>: <span class="string">&quot;CN&quot;</span>,</span><br><span class="line">  <span class="string">&quot;age&quot;</span>: 34</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">POST users_us/_doc</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;name&quot;</span>: <span class="string">&quot;John&quot;</span>,</span><br><span class="line">  <span class="string">&quot;country&quot;</span>: <span class="string">&quot;US&quot;</span>,</span><br><span class="line">  <span class="string">&quot;age&quot;</span>: 42</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">POST users_us/_doc</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Emily&quot;</span>,</span><br><span class="line">  <span class="string">&quot;country&quot;</span>: <span class="string">&quot;US&quot;</span>,</span><br><span class="line">  <span class="string">&quot;age&quot;</span>: 31</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>✅ 实际都写入到同一个物理索引 <code>users</code>，<br>但数据被根据 routing（china &#x2F; usa）分到了不同分片。</p><h3 id="4️⃣-验证分片路由情况"><a href="#4️⃣-验证分片路由情况" class="headerlink" title="4️⃣ 验证分片路由情况"></a>4️⃣ 验证分片路由情况</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">GET <span class="built_in">users</span>/_search_shards</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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;nodes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;mIS34pJJRrWWYDERAJLuqw&quot;</span><span class="punctuation">:</span> <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;node-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ephemeral_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;BgJLaTndTAWGxDGri8125w&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;transport_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;172.100.1.2:9300&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;attributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;users&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><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="attr">&quot;shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;state&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STARTED&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;primary&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&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mIS34pJJRrWWYDERAJLuqw&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;relocating_node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocation_id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;XT6Ds-NTSb-hhUeNCeHHjA&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;state&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STARTED&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;primary&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&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mIS34pJJRrWWYDERAJLuqw&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;relocating_node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocation_id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;74BEgGYOTOO0tg7kVXtvDA&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;state&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STARTED&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;primary&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&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mIS34pJJRrWWYDERAJLuqw&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;relocating_node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocation_id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;9hdcuoz5TbWql3kipudCxA&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;state&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STARTED&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;primary&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&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mIS34pJJRrWWYDERAJLuqw&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;relocating_node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocation_id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;h0esaYy8QJmvfiGLjn3Zwg&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</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">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251006113500768.png" alt="image-20251006113500768"></p><h4 id="查询数据"><a href="#查询数据" class="headerlink" title="查询数据"></a>查询数据</h4><h5 id="查询中国区用户："><a href="#查询中国区用户：" class="headerlink" title="查询中国区用户："></a>查询中国区用户：</h5><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 users_cn/_search</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;_source&quot;</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;age&quot;</span><span class="punctuation">:</span> <span class="number">29</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;_source&quot;</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;age&quot;</span><span class="punctuation">:</span> <span class="number">34</span> <span class="punctuation">&#125;</span> <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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h5 id="查询美国区用户："><a href="#查询美国区用户：" class="headerlink" title="查询美国区用户："></a>查询美国区用户：</h5><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 users_us/_search</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;_source&quot;</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;John&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">42</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;_source&quot;</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;Emily&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">31</span> <span class="punctuation">&#125;</span> <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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="如果直接查物理索引"><a href="#如果直接查物理索引" class="headerlink" title="如果直接查物理索引"></a>如果直接查物理索引</h4><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 <span class="built_in">users</span>/_search</span><br></pre></td></tr></table></figure><p>返回所有 4 条记录，因为没带 routing。</p><h4 id="再加一个过滤型-alias（可选）"><a href="#再加一个过滤型-alias（可选）" class="headerlink" title="再加一个过滤型 alias（可选）"></a>再加一个过滤型 alias（可选）</h4><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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;add&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;index&quot;</span>: <span class="string">&quot;users&quot;</span>,</span><br><span class="line">        <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;users_adults&quot;</span>,</span><br><span class="line">        <span class="string">&quot;filter&quot;</span>: &#123; <span class="string">&quot;range&quot;</span>: &#123; <span class="string">&quot;age&quot;</span>: &#123; <span class="string">&quot;gte&quot;</span>: 30 &#125; &#125; &#125;</span><br><span class="line">      &#125;</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></pre></td><td class="code"><pre><span class="line">GET users_adults/_search</span><br></pre></td></tr></table></figure><p>→ 只返回 <code>王芳</code>（34 岁）和 <code>John</code>（42 岁）和 Emily（31 岁）。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/10/06/1759721485202-5d6276f3-5ed6-4f2d-8b31-c510cf0b1b1f.png"><br>。</p><table><thead><tr><th>场景</th><th>routing 带来的好处</th></tr></thead><tbody><tr><td>写入</td><td>相同 routing 的文档总是写入同一分片，减少 shard 跳转</td></tr><tr><td>查询</td><td>查询时只访问一个 shard，速度可提升数倍</td></tr><tr><td>多租户</td><td>每个租户 routing 不同，实现物理隔离</td></tr><tr><td>地域分区</td><td>中国区、美国区等逻辑分区共享同一个索引</td></tr></tbody></table><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></pre></td><td class="code"><pre><span class="line">GET /_cat/aliases?v</span><br></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">alias          index          filter  routing.index  routing.search  is_write_index</span><br><span class="line">logs_current   logs_2025-10   -       -              -               -</span><br><span class="line">logs_all       logs_2025-10   -       -              -               true</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 /logs_2025-10/_alias/logs_current</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">POST /_aliases</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;actions&quot;</span>: [</span><br><span class="line">    &#123; <span class="string">&quot;remove&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;logs_2025-10&quot;</span>, <span class="string">&quot;alias&quot;</span>: <span class="string">&quot;logs_current&quot;</span> &#125;&#125;</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>Easysearch 的索引别名是一个轻量、强大且几乎“零成本”的机制，它在索引生命周期管理中起着核心作用。</p><p>合理使用别名，可以实现：</p><ul><li>热切换（零停机索引迁移）；</li><li>分片控制（按租户或地理位置隔离）；</li><li>安全访问（按条件过滤可见数据）；</li><li>持续演进（读写分离 + 版本平滑过渡）。</li></ul><p>对于任何生产环境的 Easysearch 集群来说，<strong>别名是不可或缺的基础能力</strong>。</p>]]></content>
    
    
    <summary type="html">详解 Easysearch 索引别名的创建、使用场景与管理操作</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch 自动 Mapping 与 MySQL Schema 的对比分析</title>
    <link href="https://blog.no-claw.com/posts/ce1ebcd3/"/>
    <id>https://blog.no-claw.com/posts/ce1ebcd3/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在企业级数据系统中，<strong>Elasticsearch（简称 ES）</strong> 与 <strong>MySQL</strong> 是两种完全不同的数据管理哲学。</p><p>一个是面向<strong>搜索与分析</strong>的分布式引擎，一个是面向<strong>事务与一致性</strong>的关系型数据库。</p><p>而当我们深入理解它们的数据结构定义方式——ES 的 <strong>自动 mapping 推断</strong> 与 MySQL 的 <strong>手动 schema 定义</strong>——就会发现，它们的核心设计理念几乎是两个世界。</p><p>本文将从机制、原理、优缺点和使用建议等角度，系统对比两者的差异，重点聚焦在 Elasticsearch 的自动 mapping 特性上。</p><h3 id="一、什么是-Mapping-与-Schema"><a href="#一、什么是-Mapping-与-Schema" class="headerlink" title="一、什么是 Mapping 与 Schema"></a>一、什么是 Mapping 与 Schema</h3><p>在 MySQL 中，我们习惯使用 <strong>表结构（Schema）</strong> 来定义数据字段及其类型：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</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><br><span class="line">  age <span class="type">INT</span>,</span><br><span class="line">  created_at DATETIME</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>每一行都必须严格遵守这个表结构，类型固定，字段不可缺少。<br>这是典型的 <strong>Schema-first</strong> 模型：<strong>在写入之前必须定义好结构</strong>。</p><p>而在 Elasticsearch 中，索引（Index）虽然也有 schema 概念，但它是通过 <strong>Mapping</strong> 来定义字段类型和分析方式的。<br>Mapping 可以手动声明，也可以让 ES 自动推断。例如：</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">POST /user/_doc</span><br><span class="line">&#123; <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Alice&quot;</span>, <span class="string">&quot;age&quot;</span>: 25 &#125;</span><br></pre></td></tr></table></figure><p>ES 会自动创建 <code>user</code> 索引，并根据字段值类型生成如下 mapping：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;keyword&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span> <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="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005204843587.png" alt="image-20251005204843587"></p><p>这就是 <strong>自动 mapping（dynamic mapping）</strong>：ES 在第一次看到某字段时自动推断类型并写入 mapping。<br>不需要提前定义结构，系统“自学习”出 schema。</p><h3 id="二、自动-Mapping-的推断机制"><a href="#二、自动-Mapping-的推断机制" class="headerlink" title="二、自动 Mapping 的推断机制"></a>二、自动 Mapping 的推断机制</h3><p>Elasticsearch 的 mapping 推断是动态的（Dynamic Mapping）。当写入新文档时，ES 会：</p><ol><li>扫描每个字段；</li><li>根据字段值的类型判断（string、number、boolean、date 等）；</li><li>将结果写入 <code>_mapping</code>；</li><li>更新索引元数据并持久化。</li></ol><p>例如：</p><table><thead><tr><th>值</th><th>推断类型</th><th>备注</th></tr></thead><tbody><tr><td><code>&quot;hello&quot;</code></td><td>text + keyword</td><td>同时支持全文检索与精确匹配</td></tr><tr><td><code>123</code></td><td>long</td><td>数值型</td></tr><tr><td><code>12.3</code></td><td>double</td><td>浮点型</td></tr><tr><td><code>&quot;2025-10-05&quot;</code></td><td>date</td><td>自动识别日期格式</td></tr><tr><td><code>true</code></td><td>boolean</td><td>布尔值</td></tr></tbody></table><p>ES 的这种自动识别让开发者在数据探索早期几乎零配置即可使用，非常便捷。<br>但这种便捷背后也隐藏着风险——错误推断、类型冲突、mapping 爆炸等问题可能在后期放大。</p><h3 id="三、MySQL-的-Schema-定义机制"><a href="#三、MySQL-的-Schema-定义机制" class="headerlink" title="三、MySQL 的 Schema 定义机制"></a>三、MySQL 的 Schema 定义机制</h3><p>与 ES 不同，MySQL 属于 <strong>强类型、静态结构</strong> 模型。<br>它要求所有字段在写入前就被定义好。任何表结构变更都需要执行 <code>ALTER TABLE</code>，会产生锁表或重建索引的代价。</p><p>优点是：</p><ul><li>数据一致性强；</li><li>结构清晰；</li><li>易于维护和优化；</li><li>支持事务与约束。</li></ul><p>缺点则是：</p><ul><li>演进成本高；</li><li>扩展不灵活；</li><li>对半结构化数据支持差。</li></ul><p>如果说 MySQL 的 schema 是“一座刚性大厦”，那 ES 的 mapping 就像“可随时扩建的集装箱”。</p><h3 id="四、自动-Mapping-的优势：灵活与速度"><a href="#四、自动-Mapping-的优势：灵活与速度" class="headerlink" title="四、自动 Mapping 的优势：灵活与速度"></a>四、自动 Mapping 的优势：灵活与速度</h3><h4 id="1-开发效率高"><a href="#1-开发效率高" class="headerlink" title="1. 开发效率高"></a>1. 开发效率高</h4><p>在日志、埋点、IoT 等场景中，数据字段极多且经常变化。<br>自动 mapping 让开发者无需提前规划字段，只要把 JSON 写进去，ES 就能立刻索引和查询。</p><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;host&quot;</span><span class="punctuation">:</span> <span class="string">&quot;server-1&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;response_time&quot;</span><span class="punctuation">:</span> <span class="number">123</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">200</span> <span class="punctuation">&#125;</span></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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;host&quot;</span><span class="punctuation">:</span> <span class="string">&quot;server-2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;response_time&quot;</span><span class="punctuation">:</span> <span class="number">98</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ap-southeast-1&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>ES 也会自动为 <code>region</code> 增加字段定义，无需手动修改 mapping。<br>这在 MySQL 中则必须执行结构变更。</p><h4 id="2-兼容性好"><a href="#2-兼容性好" class="headerlink" title="2. 兼容性好"></a>2. 兼容性好</h4><p>ES 的索引不要求所有文档字段一致。某些文档可以缺字段而不影响写入。<br>对动态 JSON 数据、日志、监控事件特别友好。</p><h4 id="3-适配性强"><a href="#3-适配性强" class="headerlink" title="3. 适配性强"></a>3. 适配性强</h4><p>数据可来自多源系统（API、Kafka、日志流），字段差异大。<br>自动 mapping 让这些异构数据能快速进入索引，后期再统一分析。</p><h4 id="4-快速原型构建"><a href="#4-快速原型构建" class="headerlink" title="4. 快速原型构建"></a>4. 快速原型构建</h4><p>在数据探索阶段，不必先定义 schema，就能立刻搜索和聚合，是数据科学家和分析工程师最喜欢的特性之一。</p><h3 id="五、自动-Mapping-的风险与缺陷"><a href="#五、自动-Mapping-的风险与缺陷" class="headerlink" title="五、自动 Mapping 的风险与缺陷"></a>五、自动 Mapping 的风险与缺陷</h3><h4 id="1-类型误判（Type-Guessing）"><a href="#1-类型误判（Type-Guessing）" class="headerlink" title="1. 类型误判（Type Guessing）"></a>1. 类型误判（Type Guessing）</h4><p>ES 根据值内容推断类型，但不是总能猜对：</p><table><thead><tr><th>原始值</th><th>被推断类型</th><th>潜在问题</th></tr></thead><tbody><tr><td><code>&quot;00123&quot;</code></td><td>text</td><td>实际上是字符串数字</td></tr><tr><td><code>&quot;2024/12/01&quot;</code></td><td>date</td><td>格式异常可能被误识别</td></tr><tr><td><code>123.0</code></td><td>double</td><td>实际希望 long，却被识别为浮点</td></tr><tr><td><code>true</code> &#x2F; <code>false</code></td><td>boolean</td><td>可能来自字符串而非布尔值</td></tr></tbody></table><p>类型一旦被推断并写入 mapping，就<strong>无法修改</strong>。<br>如果写错，只能重建索引并重新导入数据。</p><h4 id="2-Mapping-Explosion（映射爆炸）"><a href="#2-Mapping-Explosion（映射爆炸）" class="headerlink" title="2. Mapping Explosion（映射爆炸）"></a>2. Mapping Explosion（映射爆炸）</h4><p>ES 的每个字段都要占用堆内存（field data、倒排索引、统计信息）。<br>当系统存在动态命名字段（如 <code>user_1</code>, <code>user_2</code>, …）时，会生成成千上万个字段，导致：</p><ul><li>Mapping 文件膨胀；</li><li>节点 heap 占用急剧上升；</li><li>查询性能下降；</li><li>甚至引发 “too many fields” 异常。</li></ul><p>官方建议：单个索引字段数不要超过 1000。</p><h4 id="3-类型不可变"><a href="#3-类型不可变" class="headerlink" title="3. 类型不可变"></a>3. 类型不可变</h4><p>ES 中字段一旦创建，类型就锁定。<br>比如第一次写入 <code>&quot;price&quot;: &quot;123&quot;</code> 被识别为 <code>text</code>，之后再写入数字 <code>price: 123</code> 就会报错：</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">mapper_parsing_exception: cannot merge a field of type [long] with [text]</span><br></pre></td></tr></table></figure><p>修复方法只有一个：重建索引。</p><h4 id="4-搜索分析不准确"><a href="#4-搜索分析不准确" class="headerlink" title="4. 搜索分析不准确"></a>4. 搜索分析不准确</h4><p>自动 mapping 对 string 默认生成 <code>text</code> + <code>keyword</code> 两种字段。<br>在全文检索时 OK，但在聚合、排序、精确匹配时会引发困惑。<br>许多用户在 Kibana 中查询 <code>&quot;region.keyword&quot;</code> 才能聚合，是由 mapping 自动生成机制决定的。</p><h3 id="六、MySQL-的优势：可控与稳定"><a href="#六、MySQL-的优势：可控与稳定" class="headerlink" title="六、MySQL 的优势：可控与稳定"></a>六、MySQL 的优势：可控与稳定</h3><p>相比之下，MySQL 的 schema 固定、类型严格，所有字段定义都在 DBA 控制下。<br>这意味着：</p><ul><li>任何类型变化都是显式的；</li><li>查询结果一致性强；</li><li>可维护性高；</li><li>性能优化空间大。</li></ul><p>在交易、账务、库存、财务等场景中，MySQL 的稳定性远胜 ES。<br>它的缺点恰恰是 ES 的优点：<strong>灵活性差但可控</strong>。</p><h3 id="七、如何平衡：让自动-mapping-可控"><a href="#七、如何平衡：让自动-mapping-可控" class="headerlink" title="七、如何平衡：让自动 mapping 可控"></a>七、如何平衡：让自动 mapping 可控</h3><p>ES 提供了几种方式，在保留灵活性的同时减少风险。</p><h4 id="1-动态模板（Dynamic-Templates）"><a href="#1-动态模板（Dynamic-Templates）" class="headerlink" title="1. 动态模板（Dynamic Templates）"></a>1. 动态模板（Dynamic Templates）</h4><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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;dynamic_templates&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;numbers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match_mapping_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;mapping&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;float&quot;</span> <span class="punctuation">&#125;</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="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;strings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*_id&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;mapping&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</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>_id</code> 结尾的字段都被强制识别为 keyword，而不是 text。</p><h4 id="2-关闭自动-mapping"><a href="#2-关闭自动-mapping" class="headerlink" title="2. 关闭自动 mapping"></a>2. 关闭自动 mapping</h4><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;dynamic&quot;</span><span class="punctuation">:</span> <span class="string">&quot;strict&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>意味着：未定义字段禁止写入。<br>防止误导入数据结构。</p><h4 id="3-限制字段数"><a href="#3-限制字段数" class="headerlink" title="3. 限制字段数"></a>3. 限制字段数</h4><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">PUT /my_index/_settings</span><br><span class="line">&#123; <span class="string">&quot;index.mapping.total_fields.limit&quot;</span>: 1000 &#125;</span><br></pre></td></tr></table></figure><p>避免 mapping explosion。</p><h4 id="4-明确控制核心字段"><a href="#4-明确控制核心字段" class="headerlink" title="4. 明确控制核心字段"></a>4. 明确控制核心字段</h4><p>对关键字段（时间戳、数值、地理位置、ID）手动声明类型。<br>剩余部分交由 dynamic mapping 自动处理，可兼顾灵活与安全。</p><h3 id="八、性能与存储影响对比"><a href="#八、性能与存储影响对比" class="headerlink" title="八、性能与存储影响对比"></a>八、性能与存储影响对比</h3><table><thead><tr><th>指标</th><th>Elasticsearch</th><th>MySQL</th></tr></thead><tbody><tr><td>写入性能</td><td>较高（分布式、异步）</td><td>较低（事务同步）</td></tr><tr><td>读取性能</td><td>适合全文检索、聚合分析</td><td>适合主键查询、范围查询</td></tr><tr><td>schema 变更成本</td><td>无（自动）</td><td>高（需 ALTER）</td></tr><tr><td>内存消耗</td><td>较大（索引元数据）</td><td>较小</td></tr><tr><td>数据一致性</td><td>弱一致</td><td>强一致</td></tr><tr><td>横向扩展性</td><td>强</td><td>中等</td></tr></tbody></table><p>结论：<br>ES 的自动 mapping 提供了极高的写入灵活性，但代价是索引元数据膨胀、内存占用高、类型错误风险大。<br>MySQL 的 schema 则更适合结构化、高一致性业务。</p><h3 id="九、使用建议：什么时候用自动-mapping？"><a href="#九、使用建议：什么时候用自动-mapping？" class="headerlink" title="九、使用建议：什么时候用自动 mapping？"></a>九、使用建议：什么时候用自动 mapping？</h3><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>日志、监控、埋点</td><td>✅ 开启自动 mapping（dynamic&#x3D;true）</td></tr><tr><td>搜索引擎、用户画像</td><td>✅ 自动 mapping + 动态模板</td></tr><tr><td>电商订单、金融账务</td><td>❌ 手动 mapping（dynamic&#x3D;strict）</td></tr><tr><td>混合型数据（部分稳定、部分动态）</td><td>⚙️ 手动定义核心字段 + 动态模板控制扩展字段</td></tr><tr><td>MySQL → ES 同步</td><td>🚫 禁止自动 mapping，使用 Logstash&#x2F;ETL 生成预定义 mapping</td></tr></tbody></table><h3 id="十、总结：灵活与秩序的取舍"><a href="#十、总结：灵活与秩序的取舍" class="headerlink" title="十、总结：灵活与秩序的取舍"></a>十、总结：灵活与秩序的取舍</h3><table><thead><tr><th>维度</th><th>Elasticsearch 自动 Mapping</th><th>MySQL Schema</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>强</td></tr><tr><td>修改成本</td><td>低（但错误代价高）</td><td>高（但可控）</td></tr><tr><td>可维护性</td><td>中等（需监控 mapping 爆炸）</td><td>高</td></tr><tr><td>适用场景</td><td>搜索、日志、非结构化数据</td><td>交易、财务、结构化数据</td></tr></tbody></table><p>Elasticsearch 的自动 mapping 是一把双刃剑：<br>它让数据“随写随用”，带来极大的灵活性；<br>但同时，也可能在规模化阶段埋下类型混乱、性能下降的隐患。</p><p>最佳实践是在项目早期利用其灵活性快速构建原型；<br>而在生产阶段，结合手动 mapping 与动态模板，建立“半自动、可控”的数据模型。</p><p>真正成熟的 ES 使用者，从来不会完全依赖自动 mapping。<br><strong>自动化是起点，不是终点；灵活性需要以控制为前提。</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在企业级数据系统中，&lt;strong&gt;Elasticsearch（简称 ES）&lt;/strong&gt; 与 &lt;strong&gt;MySQL&lt;/strong&gt; 是两种完全不同的数据管理哲学。&lt;/p&gt;
&lt;p&gt;一个是面向&lt;strong&gt;搜索与分析&lt;/strong&gt;的分布式引擎，一个是面向&lt;s</summary>
      
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 的写入流程（一）：refresh</title>
    <link href="https://blog.no-claw.com/posts/eb05eb6d/"/>
    <id>https://blog.no-claw.com/posts/eb05eb6d/</id>
    <published>2025-10-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Elasticsearch 或者 Easysearch 这样的搜索引擎中，<strong>写入流程</strong>是理解性能调优和搜索可见性最核心的部分之一。许多同学刚接触 ES 时，最常见的疑惑就是：“为什么我刚插入的数据查不到？”、“refresh 和 flush 有什么区别？”、“refresh_interval 设置成多少合适？”</p><p>这篇文章我们就专门讲清楚 <strong>refresh（刷新）</strong> 这一环节。它是 ES 写入流程的关键节点，既影响了数据什么时候能被搜索到，也影响了整个系统的写入性能和稳定性。</p>  <span id="more"></span><h3 id="1-什么是刷新时间"><a href="#1-什么是刷新时间" class="headerlink" title="1. 什么是刷新时间"></a>1. 什么是刷新时间</h3><h4 id="1-1-refresh-的定义"><a href="#1-1-refresh-的定义" class="headerlink" title="1.1 refresh 的定义"></a>1.1 refresh 的定义</h4><ul><li><p><strong>刷新 (refresh)</strong> &#x3D; 把 <strong>内存 buffer</strong> 里的数据写到新的 segment 文件（先进入 OS cache），然后让它们对搜索可见。</p></li><li><p><strong>刷新时间 (refresh interval)</strong> &#x3D; ES 自动触发 refresh 的周期。</p><p>换句话说，refresh 的目标不是“数据持久化”，而是“数据可见”。也就是说，数据写进来之后，先保存在内存 buffer 里，这时候你去搜索是查不到的；一旦发生了 refresh，这些数据就会生成一个新的 Lucene segment，被索引打开，立即可搜索。</p></li></ul><h4 id="1-2-为什么需要-refresh？"><a href="#1-2-为什么需要-refresh？" class="headerlink" title="1.2 为什么需要 refresh？"></a>1.2 为什么需要 refresh？</h4><p>Lucene 是一个基于 segment 的倒排索引系统。segment 文件是 <strong>只读的</strong>，所以每次有新文档进来，都要生成新的 segment。refresh 就是触发这个生成过程的机制。<br>如果没有 refresh，你写入的数据永远停留在 buffer 里，不会变成 segment，自然也就查不到。</p><h3 id="2-默认值与查询验证"><a href="#2-默认值与查询验证" class="headerlink" title="2. 默认值与查询验证"></a>2. 默认值与查询验证</h3><p>默认情况下，ES 的 <code>refresh_interval</code> 是 <strong>1s</strong>。也就是说，ES 每秒会自动刷新一次，所以新写入的数据通常 1 秒内就能查到。</p><p>我们可以通过 <code>_settings</code> API 来查看：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看某个索引的 refresh_interval</span></span><br><span class="line">GET /my_index/_settings?include_defaults=<span class="literal">true</span>&amp;filter_path=**.refresh_interval</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;my_index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;refresh_interval&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1s&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>这说明 <code>my_index</code> 索引的刷新间隔是 1 秒。</p><p>如果你刚写入一条文档，立刻查询可能查不到，但只要等 1 秒钟，它就会出现在搜索结果里。这个“近实时（Near Real Time, NRT）”特性，就是 ES 的核心设计。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005084904716.png" alt="image-20251005084904716"></p><h3 id="3-修改刷新时间"><a href="#3-修改刷新时间" class="headerlink" title="3. 修改刷新时间"></a>3. 修改刷新时间</h3><p>在不同场景下，1 秒钟的 refresh_interval 并不是最优的。有时候我们希望更快可见，有时候则希望尽量少刷新，以提高写入性能。ES 允许你动态修改刷新间隔。</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"><span class="comment"># 设置为 30s（减少频繁刷新，写入性能更高）</span></span><br><span class="line">PUT /my_index/_settings</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;index&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;refresh_interval&quot;</span>: <span class="string">&quot;30s&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">PUT /my_index/_settings</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;index&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;refresh_interval&quot;</span>: <span class="string">&quot;-1&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>设置为 <code>30s</code>：表示 30 秒刷新一次。适合日志类场景，写多查少，降低频繁小 segment 的生成。</li><li>设置为 <code>-1</code>：表示完全禁用自动 refresh，只能通过手动 <code>_refresh</code> 让数据可见。这个模式常见于 <strong>大批量数据导入</strong>。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005085049427.png" alt="image-20251005085049427"></li></ul><h3 id="4-取值含义与场景适配"><a href="#4-取值含义与场景适配" class="headerlink" title="4. 取值含义与场景适配"></a>4. 取值含义与场景适配</h3><ul><li><code>&quot;1s&quot;</code>：默认值，平衡写入和查询，适合绝大多数通用场景。</li><li><code>&quot;30s&quot;</code> 或更大：适合日志&#x2F;监控场景，写多查少，减少小 segment 生成，提高写入吞吐。</li><li><code>&quot;-1&quot;</code>：禁用自动刷新，通常用于大规模初始化导入数据。导入完成后，手动 refresh，再改回默认值。</li></ul><h3 id="5-刷新时间对性能的影响"><a href="#5-刷新时间对性能的影响" class="headerlink" title="5. 刷新时间对性能的影响"></a>5. 刷新时间对性能的影响</h3><p>理解 refresh_interval 的性能影响非常重要。</p><ul><li><p><strong>时间越短（比如 500ms、1s）</strong></p><ul><li>优点：几乎实时可见，写入后很快就能查询到。</li><li>缺点：会生成大量小 segment，触发频繁的合并，降低整体写入吞吐。</li></ul></li><li><p><strong>时间越长（比如 30s、60s）</strong></p><ul><li>优点：减少 segment 数量，提升写入效率。</li><li>缺点：数据可见延迟更高。</li></ul></li><li><p><strong>禁用自动 refresh</strong></p><ul><li>优点：写入性能最佳，可以最大化导入速度。</li><li>缺点：完全不可查，必须手动 refresh 才能看到数据。</li></ul></li></ul><h3 id="6-实战建议"><a href="#6-实战建议" class="headerlink" title="6. 实战建议"></a>6. 实战建议</h3><ul><li><strong>普通搜索型索引</strong>（电商商品、用户数据）：用默认 <code>1s</code>。</li><li><strong>日志&#x2F;监控索引</strong>（写多查少）：调大到 <code>30s</code> 或 <code>60s</code>，甚至更长。</li><li><strong>大批量导入</strong>（初始化数据）：设置 <code>-1</code>，导入后手动 refresh，再恢复 <code>1s</code>。</li></ul><p>这种调优思路能够兼顾写入性能和搜索体验。</p><table><thead><tr><th>refresh_interval</th><th>数据可见性</th><th>写入性能</th><th>典型场景</th></tr></thead><tbody><tr><td><code>1s</code> (默认)</td><td>~1s 可查</td><td>中等</td><td>电商搜索、通用场景</td></tr><tr><td><code>30s</code></td><td>~30s 可查</td><td>较高</td><td>日志、监控</td></tr><tr><td><code>-1</code></td><td>手动可查</td><td>最高</td><td>大规模数据导入</td></tr></tbody></table><h3 id="7-refresh-参数取值详解"><a href="#7-refresh-参数取值详解" class="headerlink" title="7. refresh 参数取值详解"></a>7. refresh 参数取值详解</h3><p>在写入 API 里，还可以通过 <code>refresh</code> 参数控制是否立刻刷新：</p><ul><li><p><code>refresh=false</code>（默认）</p><ul><li>不会自动 refresh，性能最好。</li><li>新写的数据需要等下一个 refresh 周期才能查到。</li></ul></li><li><p><code>refresh=true</code></p><ul><li>执行完后强制 refresh。</li><li>数据立即可见，但每次都会触发 refresh，性能代价较大。</li></ul><p>我们能够看到，即使 index_b 前面设置了”refresh_interval”: “-1”，再手动 refresh 之后也能够查找到了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005085901033-20251005091818958.png" alt="image-20251005085901033"></p></li><li><p><code>refresh=wait_for</code></p><ul><li>不强制立即 refresh，而是等待下一个 refresh 周期完成后再返回。</li><li>适合既想要数据可见，又不想过多消耗性能的场景。</li><li>注意：如果索引设置了 <code>&quot;refresh_interval&quot;: &quot;-1&quot;</code>，那么 <code>wait_for</code> 会一直卡住不返回，这时候最好手动 refresh。</li></ul></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005085456505.png" alt="image-20251005085456505"></p><h3 id="8-手动刷新索引"><a href="#8-手动刷新索引" class="headerlink" title="8. 手动刷新索引"></a>8. 手动刷新索引</h3><p>在 Easysearch 里，<strong>手动刷新索引</strong> 就是调用 <code>_refresh</code> API。<br>这个操作会立刻把 <strong>内存 buffer</strong> 里的数据写到新的 segment（进入 OS cache），并让它们对搜索可见。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005091917850.png" alt="image-20251005091917850"></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"><span class="comment"># 刷新单个索引</span></span><br><span class="line">POST /my_index/_refresh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刷新多个索引</span></span><br><span class="line">POST /index_a,index_b/_refresh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刷新整个集群</span></span><br><span class="line">POST /_refresh</span><br></pre></td></tr></table></figure><p><strong>使用场景：</strong></p><ul><li>测试时：写入数据 → 立刻刷新 → 马上查。</li><li>批量导入时：禁用自动 refresh，导入完成后一次性手动 refresh。</li></ul><p>⚠️ 注意：频繁手动 refresh 会导致大量小 segment，性能很差。生产环境中要谨慎使用。</p><h3 id="9-写入生命周期时间轴"><a href="#9-写入生命周期时间轴" class="headerlink" title="9. 写入生命周期时间轴"></a>9. 写入生命周期时间轴</h3><p>为了更直观理解，我们看下 ES 写入生命周期：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">时间轴 →</span><br><span class="line">[写入]         [refresh]             [flush]</span><br><span class="line">   │               │                    │</span><br><span class="line">   ▼               ▼                    ▼</span><br><span class="line">文档写入 → Indexing Buffer → Segment(OS cache) → fsync磁盘</span><br><span class="line">              + Translog(日志)</span><br></pre></td></tr></table></figure><h4 id="9-1-文档写入-t-0"><a href="#9-1-文档写入-t-0" class="headerlink" title="9.1 文档写入 (t&#x3D;0)"></a>9.1 文档写入 (t&#x3D;0)</h4><ul><li>文档进入 <strong>内存 buffer</strong></li><li>同时写入 <strong>translog</strong></li><li>此时数据不可查询。</li></ul><h4 id="9-2-refresh-t-refresh-interval"><a href="#9-2-refresh-t-refresh-interval" class="headerlink" title="9.2 refresh (t &#x3D; refresh_interval)"></a>9.2 refresh (t &#x3D; refresh_interval)</h4><ul><li>buffer 转换为新的 segment 文件</li><li>segment 进入 OS cache</li><li>Lucene 打开 segment，数据可查询，但未必落盘。</li></ul><h4 id="9-3-flush"><a href="#9-3-flush" class="headerlink" title="9.3 flush"></a>9.3 flush</h4><ul><li>强制 fsync，把 OS cache 写到磁盘</li><li>清空 translog</li><li>此时数据既可查询，也保证持久化。</li></ul><h4 id="最佳实践流程：批量导入优化"><a href="#最佳实践流程：批量导入优化" class="headerlink" title="最佳实践流程：批量导入优化"></a>最佳实践流程：批量导入优化</h4><ol><li>新建索引 → 设置 <code>refresh_interval=-1</code>。</li><li>使用 <code>_bulk</code> 批量导入数据。</li><li>导入完成后，手动执行 <code>POST /my_index/_refresh</code>。</li><li>恢复 <code>refresh_interval=1s</code>（或业务需要的值）。</li></ol><p>这样能显著提升导入性能，同时保证导入完成后数据立即可见。</p><h3 id="📌-总结"><a href="#📌-总结" class="headerlink" title="📌 总结"></a>📌 总结</h3><ul><li><strong>refresh</strong> 决定了写入数据多久能被查询到。</li><li>它与 <strong>flush</strong> 不同：flush 是保证持久化，refresh 是保证可见性。</li><li>默认 <code>1s</code>，意味着 ES 是一个“近实时”系统。</li><li>在写多查少的场景，调大 refresh_interval 能显著提升性能。</li><li>在大规模导入时，禁用 refresh，然后手动 refresh，是常见的优化手法。</li></ul><p>理解 refresh，不仅能帮助你解决“为什么数据查不到”的问题，还能让你在性能和实时性之间做出合理的权衡。</p>]]></content>
    
    
    <summary type="html">深入解析 Easysearch 写入流程中的 refresh 机制</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服炫技篇（四）：蓝牙音浪，懒猫开唱</title>
    <link href="https://blog.no-claw.com/posts/afaab095/"/>
    <id>https://blog.no-claw.com/posts/afaab095/</id>
    <published>2025-10-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫微服硬件层面自带了蓝牙模块。理论上，它应该可以像一台笔记本电脑或手机那样，直接连接蓝牙音箱，成为一个音频输出端。那么问题来了：在没有桌面 GUI 的前提下，能否纯命令行完成 <strong>蓝牙配对 + 音乐播放</strong>？</p><p>折腾了一上午，我从安装依赖、加载服务、设备配对到最终的音频播放，完整走通了流程。过程中踩了几个典型的坑，把命令总结下来，甚至进一步改造成一个家庭“蓝牙音频中心”。</p><span id="more"></span><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>懒猫微服本质上就是一台 Linux 设备，可以在 neofetch 中查看系统信息。所以蓝牙相关的配置流程和常见的 Ubuntu&#x2F;Debian 系统基本一致。只要我们搞定了蓝牙协议栈、音频服务和配对，就能让它像 PC 一样推送音频到蓝牙音箱。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251002142630651.png" alt="image-20251002142630651"></p><p>懒猫微服没有提供 GUI 桌面环境，所以所有的这些步骤都得通过命令行完成。</p><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install bluez bluez-tools pulseaudio-module-bluetooth libspa-0.2-bluetooth</span><br></pre></td></tr></table></figure><p>这里有几个关键点：</p><ul><li><strong>bluez</strong>：Linux 的蓝牙协议栈，底层必须有这个，否则系统完全不认识蓝牙设备。</li><li><strong>bluez-tools</strong>：命令行工具集合，比如 <code>bluetoothctl</code> 就靠它。</li><li><strong>pulseaudio-module-bluetooth</strong>：PulseAudio 的蓝牙扩展模块，用于把音频流通过 A2DP 推给蓝牙音箱。</li><li><strong>libspa-0.2-bluetooth</strong>：PipeWire 的蓝牙插件，懒猫微服上装着可能是新版本的音频栈，有时候需要它做兼容。</li></ul><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"><span class="built_in">sudo</span> systemctl restart bluetooth</span><br></pre></td></tr></table></figure><p>蓝牙服务在驱动加载、硬件唤醒等场景下，偶尔会出现异常，导致设备不可见。重启服务能有效避免 <strong>无法扫描设备</strong>、<strong>连接超时</strong> 等问题。</p><blockquote><p>如果遇到 <code>No default controller available</code> 的错误，可以通过 <code>systemctl status bluetooth</code> 检查蓝牙守护进程是否正常运行。</p></blockquote><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></pre></td><td class="code"><pre><span class="line">bluetoothctl</span><br></pre></td></tr></table></figure><p>核心工具是 <code>bluetoothctl</code>。进入交互模式后，依次执行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">power on</span><br><span class="line">agent on</span><br><span class="line">default-agent</span><br><span class="line">scan on</span><br><span class="line">pair &lt;MAC&gt;</span><br><span class="line">trust &lt;MAC&gt;</span><br><span class="line">connect &lt;MAC&gt;</span><br></pre></td></tr></table></figure><p>逐条解释一下：</p><ul><li><strong>power on</strong>：打开蓝牙硬件。</li><li><strong>agent on</strong> + <strong>default-agent</strong>：启用配对代理，否则你输入 <code>pair</code> 时可能会报 “No agent available”。</li><li><strong>scan on</strong>：扫描附近设备，你会看到形如 <code>XX:XX:XX:XX:XX:XX</code> 的 MAC 地址和设备名。</li><li><strong>pair</strong> ：和音箱配对。第一次一般要确认一下。</li><li><strong>trust</strong> ：把设备设为可信，下次开机会自动连接。</li><li><strong>connect</strong> ：连接上去，成功的话会提示 <code>Connection successful</code>。</li></ul><p>我使用的是纽曼的蓝牙音箱，蓝牙控制台的输出大概是这样的：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3bd44cf1e16cd2d6e691456f447f0204.png" alt="配对截图"></p><p>当出现“成功配对并已连接”时，说明音频链路已建立。</p><h3 id="播放音乐"><a href="#播放音乐" class="headerlink" title="播放音乐"></a>播放音乐</h3><p>安装轻量级播放器：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install mpg123</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">mpg123 hello_tuya.mp3</span><br></pre></td></tr></table></figure><p>运行时终端显示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">(base) lzcbox-029c588e ~ # mpg123 hello_tuya.mp3</span><br><span class="line">High Performance MPEG 1.0/2.0/2.5 Audio Player for Layers 1, 2 and 3</span><br><span class="line">        version 1.31.2; written and copyright by Michael Hipp and others</span><br><span class="line">        free software (LGPL) without any warranty but with best wishes</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Terminal control enabled, press &#x27;h&#x27; for listing of keys and functions.</span><br><span class="line"></span><br><span class="line">Playing MPEG stream 1 of 1: hello_tuya.mp3 ...</span><br><span class="line">&gt; 01+97  00:00.00+00:03.45 --- 100=100   8 kb/s   36 B acc    0 clip p+0.000</span><br><span class="line">MPEG 2.0 L III vbr 16000 mono</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">[0:03] Decoding of hello_tuya.mp3 finished.</span><br></pre></td></tr></table></figure><p>这时候音箱就能播放我们加载的 MP3 文件了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251002141941121-20251002142356611-20251002142525502.png" alt="播放截图"></p><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 安装依赖</span></span><br><span class="line"><span class="built_in">sudo</span> apt install -y bluez bluez-tools pulseaudio-module-bluetooth libspa-0.2-bluetooth</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 重启蓝牙服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl restart bluetooth</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 进入蓝牙控制台做配对</span></span><br><span class="line">bluetoothctl</span><br><span class="line"><span class="comment"># power on</span></span><br><span class="line"><span class="comment"># agent on</span></span><br><span class="line"><span class="comment"># default-agent</span></span><br><span class="line"><span class="comment"># scan on</span></span><br><span class="line"><span class="comment"># pair &lt;MAC&gt;</span></span><br><span class="line"><span class="comment"># trust &lt;MAC&gt;</span></span><br><span class="line"><span class="comment"># connect &lt;MAC&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 测试播放</span></span><br><span class="line"><span class="built_in">sudo</span> apt install -y mpg123</span><br><span class="line">mpg123 hello_tuya.mp3</span><br></pre></td></tr></table></figure><h3 id="踩坑与经验分享"><a href="#踩坑与经验分享" class="headerlink" title="踩坑与经验分享"></a>踩坑与经验分享</h3><ol><li><p><strong>连接失败（br-connection-page-timeout）</strong><br>有时候会报类似：</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">Failed to connect: org.bluez.Error.Failed br-connection-page-timeout</span><br></pre></td></tr></table></figure><p>这个大概率是音箱没进入配对模式，或者离设备太远。解决方法就是：重置音箱 → 靠近设备 → 再连。</p></li><li><p><strong>开机自启</strong><br>懒猫微服重启后可能蓝牙 sink 不会自动挂载，你需要写个 systemd 服务，启动时执行 <code>bluetoothctl connect &lt;MAC&gt;</code>。</p></li><li><p><strong>容器化方案</strong><br>如果想更整洁，可以拉个容器专门跑音乐服务，把 mp3 拉进去播放，或者跑个 MPD（Music Player Daemon）+ ncmpcpp。这样甚至可以做一个“云端点歌机”。</p></li></ol><p>其实做到这一步后，懒猫微服就具备了一个“音频推送中转站”的能力。未来可以玩出很多花样：</p><ul><li><strong>自动播放提醒</strong>：结合 crontab，在每天早上 8 点自动播一首歌当闹钟。</li><li><strong>语音播报</strong>：结合 TTS（文字转语音），让懒猫微服播报天气、消息、提醒。</li><li><strong>网络收音机</strong>：用 <code>mpg123 http://...</code> 直接播放网络电台。</li><li><strong>家庭中控</strong>：接入 Home Assistant，把蓝牙音箱当智能家居设备控制。</li></ul><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>懒猫微服虽然定位是轻量 NAS&#x2F;微服务平台，但它的硬件能力其实远比我们想象的要强。今天这一折腾，我把它变成了一个“小型蓝牙音频播放器”。这种玩法对技术宅来说挺有意思：你既能学到 Linux 蓝牙栈和音频服务的原理，又能把手头的小盒子改造成一个实用的设备。</p>]]></content>
    
    
    <summary type="html">纯命令行实现懒猫微服蓝牙配对音箱并播放音乐</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="炫技" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%82%AB%E6%8A%80/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服炫技篇（九十九）：从今天起，不用安装客户端也能访问懒猫微服啦～</title>
    <link href="https://blog.no-claw.com/posts/cc9f9be0/"/>
    <id>https://blog.no-claw.com/posts/cc9f9be0/</id>
    <published>2025-09-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日常使用懒猫微服的过程中，我经常会遇到这样一种情况：<br>当我想把网盘里的内容分享给朋友时，发现对方需要先下载懒猫微服的 APP，然后还要注册、登录，整个流程相对繁琐。对于大多数电脑小白来说，这一关可能直接劝退。如果对方只是临时想要下载一个文件，比如一个 PDF 文档或者一张照片，这样的门槛显得过高，不太符合“即开即用”的理念。</p><p>那么，有没有可能在<strong>不安装客户端</strong>的情况下，也能直接通过浏览器访问到懒猫微服的内容？</p><span id="more"></span><p>这篇文章就是我折腾出来的一个解决方案：通过代理的方式，把原本只能在客户端访问的资源，转化为“浏览器可直达”的体验。下面我会一步步展开。</p><h3 id="灵感的由来"><a href="#灵感的由来" class="headerlink" title="灵感的由来"></a>灵感的由来</h3><p>这个灵感并不是凭空出现的，而是源自我之前的工作经历。那时，公司网络屏蔽了维基百科，导致很多查资料的场景非常不方便。解决方案就是通过配置特定的 HTTP 代理，把流量转发出去，从而实现访问被封锁的网站。</p><p>在那个时期，我经常需要和公司的 IT 的同事沟通这些事情。这段经历让我意识到，<strong>代理其实就是一种“流量搬运工”</strong>。它不需要改变服务本身，而是通过“转发”让原本不可达的资源变得可达。</p><p>于是我就想：能不能借助代理，把访问懒猫微服的流量“搬运”出来，从而绕过必须安装客户端的限制？</p><h3 id="代理的基本思路"><a href="#代理的基本思路" class="headerlink" title="代理的基本思路"></a>代理的基本思路</h3><p>我们可以把这个问题抽象成一个三方交互的模型：</p><ul><li><strong>A：客户端设备</strong>（比如我的 iPad）</li><li><strong>B：代理服务器</strong>（也就是我在懒猫微服上部署的另一台 PC 或者路由器）</li><li><strong>C：目标网站</strong>（也就是懒猫微服的 Web 服务入口）</li></ul><p>通常情况下，A 是无法直接访问 C 的，因为懒猫微服要求使用它的 APP 来做身份校验。</p><p>但如果我在 B 上部署一个代理，让 A 的请求先走 B，再由 B 转发到 C，那么 A 就能“曲线救国”。</p><p>这种思路其实就是“中转站”模型：</p><ul><li><strong>对 A 来说</strong>：它只需要知道怎么连到 B，并不关心后面发生了什么。</li><li><strong>对 C 来说</strong>：它只知道自己收到了请求，但并不清楚请求最初来自哪里。</li></ul><p>这就是代理的核心魅力：<strong>用最小的改动，让本来不兼容的系统实现互通</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251001122206972.png" alt="image-20251001122206972"></p><h3 id="两种可行的代理方式"><a href="#两种可行的代理方式" class="headerlink" title="两种可行的代理方式"></a>两种可行的代理方式</h3><p>我主要尝试了两种代理思路，每一种都有各自的适用场景：</p><h5 id="1-透明代理"><a href="#1-透明代理" class="headerlink" title="1. 透明代理"></a>1. 透明代理</h5><p>透明代理的原理是：把客户端的默认网关改成代理服务器的地址，让所有流量都先经过代理，然后再由代理进行转发。</p><ul><li><strong>优点</strong>：<ul><li>客户端无感知，不需要手动配置浏览器或应用里的代理。</li><li>适合家庭或小型局域网环境，一次配置后所有设备都能享受便利。</li></ul></li><li><strong>缺点</strong>：<ul><li>需要控制 DHCP 或手动改网关，对普通用户来说稍微复杂。</li><li>如果局域网环境比较复杂，可能会出现路由冲突或 IP 配置错误。</li></ul></li></ul><h4 id="2-HTTP-代理"><a href="#2-HTTP-代理" class="headerlink" title="2. HTTP 代理"></a>2. HTTP 代理</h4><p>HTTP 代理则更直观：直接在客户端的网络设置里，配置一个 HTTP 代理（IP + 端口），所有浏览器流量就会走这个代理。</p><ul><li><strong>优点</strong>：<ul><li>操作简单，任何支持代理设置的设备都能快速配置。</li><li>灵活性高，可以按需开关。</li></ul></li><li><strong>缺点</strong>：<ul><li>某些应用不会遵守 HTTP 代理设置，比如一些直连的客户端或 P2P 软件。</li><li>对于非 HTTP 协议的流量支持有限。</li></ul></li></ul><h3 id="实际操作：iPad-接入代理"><a href="#实际操作：iPad-接入代理" class="headerlink" title="实际操作：iPad 接入代理"></a>实际操作：iPad 接入代理</h3><p>我最终选择了 HTTP 代理方案。操作过程很简单：</p><ol><li>在登陆懒猫微服的一个电脑上上部署了一个 HTTP 代理。</li><li>在 iPad 的 WiFi 设置里，填上代理服务器的地址和端口。</li><li>保存后重新打开浏览器，直接访问 <code>heiyu.space</code> 域名。</li></ol><p>结果很惊喜：在没有代理之前，这个域名是打不开的；在配置了代理之后，页面顺利加载了。<br>这意味着，即便没有安装懒猫微服 APP，也能直接通过浏览器访问资源。</p><blockquote><p>如果采用透明代理，操作方式是：在 iPad 的网络设置里，把默认网关改为代理机器的内网 IP。实际效果展示</p></blockquote><h4 id="配置前"><a href="#配置前" class="headerlink" title="配置前"></a>配置前</h4><p>从截图中可以看到我本地没有安装猫微服客户端，如果不配置网关的这种情况回无法访问 <code>heiyu.space</code>，浏览器报错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/%E6%88%AA%E5%B1%8F%202025-10-01%2012.09.56.png" alt="截屏 2025-10-01 12.09.56"></p><h4 id="配置后"><a href="#配置后" class="headerlink" title="配置后"></a>配置后</h4><p>配置好跳板机之后，懒猫微服的启动器页面顺利加载。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D%E5%90%AF%E5%8A%A8%E5%99%A8.png" alt="懒猫微服启动器"></p><p>甚至可以进到我的面食比例计算机，里面的内容直接用浏览器访问。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/%E9%9D%A2%E9%A3%9F%E6%AF%94%E4%BE%8B%E8%AE%A1%E7%AE%97%E5%99%A8.png" alt="面食比例计算器"></p><h3 id="适用的场景"><a href="#适用的场景" class="headerlink" title="适用的场景"></a>适用的场景</h3><p>这种方案在日常生活中非常有用：</p><ol><li><strong>临时分享文件</strong><br>想让朋友下载一个大文件，但又不想让他专门注册一个懒猫微服账号。通过代理，朋友只需要浏览器就能搞定。</li><li><strong>跨平台访问</strong><br>有些设备（智能电视、游戏机、打印机）无法安装懒猫微服客户端，但仍然可能需要访问内容，这时代理就是最优解。</li><li><strong>应急访问</strong><br>出差在外，临时需要访问家里的 NAS 或懒猫微服，但设备条件有限。只要提前部署好代理，就能轻松接入。</li><li><strong>局域网共享</strong><br>家里有多台设备想同时访问懒猫微服，但不想每台都安装客户端。直接通过透明代理，全屋共享。</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以前分享文件给朋友，还是觉得流程太重：下载 APP、注册账号、登录，再操作一堆。现在，我只要这样一个流量转发，就能把分享变成真正的一件小事——点开链接，直接下载。</p><p>这才是我想要的体验：技术改变生活。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在日常使用懒猫微服的过程中，我经常会遇到这样一种情况：&lt;br&gt;当我想把网盘里的内容分享给朋友时，发现对方需要先下载懒猫微服的 APP，然后还要注册、登录，整个流程相对繁琐。对于大多数电脑小白来说，这一关可能直接劝退。如果对方只是临时想要下载一个文件，比如一个 PDF 文档或者一张照片，这样的门槛显得过高，不太符合“即开即用”的理念。&lt;/p&gt;
&lt;p&gt;那么，有没有可能在&lt;strong&gt;不安装客户端&lt;/strong&gt;的情况下，也能直接通过浏览器访问到懒猫微服的内容？&lt;/p&gt;</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="炫技" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%82%AB%E6%8A%80/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>在嘉立创的泰山派上也能运行Easysearch</title>
    <link href="https://blog.no-claw.com/posts/d0139002/"/>
    <id>https://blog.no-claw.com/posts/d0139002/</id>
    <published>2025-09-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近一段时间我折腾硬件比较多，经常翻箱倒柜找各种开发板出来玩。某天在角落里翻到一块嘉立创的<strong>泰山派</strong>开发板（Taishan Pi），这是一块基于 <strong>Rockchip RK3566</strong> 的嵌入式 Linux 板卡。严格来说，它的性能比树莓派还要逊色一些，尤其是 CPU 主频和内存带宽方面。但手痒之下，我突然想到了一个念头：能不能在这样一块嵌入式开发板上跑一个完整的 <strong>Easysearch</strong> 实例呢？</p><p>Easysearch 本质上是一个搜索引擎数据库，是 Elasticsearch 的国产化替代方案。它在大多数情况下被部署在 x86_64&#x2F;arm 架构的服务器上，搭配 SSD 或 NVMe 作为存储，用来做全文检索、大规模日志分析或向量搜索。在常规的生产场景中，我们很少会把它和“嵌入式开发板”联想在一起。毕竟，后者 CPU 性能有限、内存紧张、存储设备大多是 eMMC 或低速 SD 卡，看起来完全不是数据库的适配环境。</p><span id="more"></span><p>不过，<strong>学习和实验的环境</strong>往往不需要极致的性能。于是，我决定尝试一下，把 Easysearch 移植到泰山派上跑起来。</p><h3 id="导出镜像"><a href="#导出镜像" class="headerlink" title="导出镜像"></a>导出镜像</h3><p>由于我的开发板连接 Dockerhub 经常超时， 而我的 MacOS 有之前缓存的 Docker 镜像。我的第一步是把镜像在 Mac 上先导出。</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">docker save -o easysearch-1.15.0.tar infinilabs/easysearch:1.15.0</span><br></pre></td></tr></table></figure><p>上面这条命令会把名为 <code>infinilabs/easysearch:1.15.0</code> 的镜像打包成一个 tar 文件，文件大小大概 700MB 左右。导出的好处是：我不需要在嵌入式开发板上重新去拉取 Docker Hub 镜像（速度慢且容易失败），只需要把 tar 包通过 FTP 或者 SFTP 上传过去即可。</p><h3 id="上传到开发板"><a href="#上传到开发板" class="headerlink" title="上传到开发板"></a>上传到开发板</h3><p>由于泰山派跑的是一个裁剪过的 Linux 系统，自带 SSH 和 FTP 服务，所以我直接用 FTP 客户端把 <code>easysearch-1.15.0.tar</code> 上传到板子的 <code>/home</code> 目录。上传速度受限于板载 eMMC，大概 3MB&#x2F;s 左右，耐心等一会儿就好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251001220450393.png" alt="image-20251001220450393"></p><p>上传完毕后，我用 SSH 登录到开发板。</p><h3 id="加载镜像"><a href="#加载镜像" class="headerlink" title="加载镜像"></a>加载镜像</h3><p>进入板子后，第一件事情就是把镜像导入到本地 Docker：</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">docker load -i easysearch-1.15.0.tar</span><br></pre></td></tr></table></figure><p>等待几分钟，Docker 会把镜像重新解压并注册到本地。由于板子 CPU 性能有限，这个步骤比在笔记本上要慢不少，但最终会看到 familiar 的镜像 ID 出现在 <code>docker images</code> 的列表中。</p><h3 id="调整内核参数"><a href="#调整内核参数" class="headerlink" title="调整内核参数"></a>调整内核参数</h3><p>接下来要注意的就是一个经典坑：Easysearch&#x2F;Elasticsearch 类的数据库要求内核参数 <code>vm.max_map_count</code> 至少设置为 262144，否则启动会直接报错。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo sysctl -w vm.max_map_count=262144</span><br></pre></td></tr></table></figure><p>这条命令会临时修改内核参数。如果你打算长期使用，可以把它写到 <code>/etc/sysctl.conf</code> 或者 <code>/etc/sysctl.d/</code> 里。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4d7085b4e63e9c41ed05715e9d39b125.png" alt="4d7085b4e63e9c41ed05715e9d39b125"></p><h3 id="启动容器"><a href="#启动容器" class="headerlink" title="启动容器"></a>启动容器</h3><p>有了镜像和内核参数，接下来就是熟悉的 <code>docker run</code> 了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker run --name easysearch \</span><br><span class="line">  --ulimit memlock=-1:-1 \</span><br><span class="line">  -p 9200:9200 \</span><br><span class="line">  infinilabs/easysearch:1.15.3</span><br></pre></td></tr></table></figure><p>第一次启动时，容器会打印出初始的超级管理员密码，可以在日志里找到。</p><h3 id="UI-访问与体验"><a href="#UI-访问与体验" class="headerlink" title="UI 访问与体验"></a>UI 访问与体验</h3><p>容器启动后，在浏览器访问开发板的 IP 地址的 9200 端口，就能打开 Easysearch 的自带管理 UI。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251001220226982.png" alt="image-20251001220226982"></p><p>UI 上显示，除了内存很快被吃满之外，其他方面表现完全可用。毕竟 Easysearch 默认会尝试尽可能缓存索引数据，而 RK3566 开发板通常只有 2GB 或 4GB 内存，溢出是难免的。但从查询速度和索引写入的小规模实验来看，性能并没有想象中那么糟糕。我尝试运行了一些 DSL，也没有什么问题。</p><p>换句话说，虽然这套组合是 <strong>arm CPU + eMMC 存储</strong>，但作为学习和体验之用已经足够。</p><h3 id="性能观察"><a href="#性能观察" class="headerlink" title="性能观察"></a>性能观察</h3><p>为了更直观地观察运行情况，我在容器启动后打开 <code>htop</code>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251001220958785.png" alt="image-20251001220958785"></p><p>可以看到 Easysearch 启动初始化时 CPU 占用飙升，内存使用量也接近物理上限。但在完成索引加载后，CPU 占用下降明显，保持在个位数到十几的水平。</p><p>这说明在嵌入式环境下跑数据库的瓶颈更多是 <strong>初始化</strong> 和 <strong>大规模数据处理</strong>，而不是日常的小规模查询。对于个人实验、API 测试和功能熟悉，完全可以胜任。</p><h3 id="适用场景与局限"><a href="#适用场景与局限" class="headerlink" title="适用场景与局限"></a>适用场景与局限</h3><p>把 Easysearch 部署在 RK3566 这样的小板子上，意义主要有两个：</p><ol><li><strong>学习和实验环境</strong>：如果你只是想熟悉 Easysearch 的 API、UI 界面，或者学习 Elasticsearch 生态，不必为了一点点测试数据开一台大服务器。</li><li><strong>轻量级应用</strong>：某些嵌入式场景下需要本地搜索功能，比如日志收集、离线数据查询、小型 IoT 网关，Easysearch 也能派上用场。</li></ol><p>当然，它也有明显的局限：</p><ul><li><strong>内存限制</strong>：2GB 内存基本无法支撑大规模索引，超过几十万文档就会吃紧。</li><li><strong>存储性能</strong>：eMMC 的顺序写速度大约 40MB&#x2F;s，随机写更低，这对索引写入速度有一定影响。</li><li><strong>CPU 性能</strong>：四核 ARM Cortex-A55，单核性能有限，并发查询时表现会受限。</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这次尝试算是一次“异想天开”的实验：把 Easysearch 从 PC 搬到了一块嵌入式开发板上。最终结果是——它真的能跑，而且还比预想的顺畅。除了内存不足和初始化速度慢，实际使用体验完全能满足学习场景。</p><p>如果你平时不想让笔记本长时间开着数据库，又恰好手边有一块开发板，那么完全可以用类似的办法，把 Easysearch 跑在开发板上作为一台“轻量数据库机”。</p><p><strong>一句话总结：</strong> Easysearch 其实没有你想象中那么“重”，只要底层 Linux 和 Docker 能跑，它就能跑。</p>]]></content>
    
    
    <summary type="html">在嘉立创泰山派开发板上成功运行 Easysearch 的实践记录</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫算力仓初探（一）：开箱手记</title>
    <link href="https://blog.no-claw.com/posts/7ae44bfb/"/>
    <id>https://blog.no-claw.com/posts/7ae44bfb/</id>
    <published>2025-09-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫微服最近上新了一款产品，名字叫 <strong>「懒猫 AI 算力仓」</strong>。<br>作为老用户，首发自然第一时间支持了一下。</p><p>其实我对这个设备的期待已经很久了。过去一年里，AI 大模型的爆发几乎让每个技术人都产生过同一个念头：能不能有一台属于自己的“算力仓”？不用再排队租云 GPU，不用担心账单像无底洞一样增长，更不用把敏感数据传到云端。所以赶了个首发，等了两天拿到了商品。</p><p><strong>懒猫 AI 算力仓的核心是 NVIDIA Jetson AGX Orin</strong>。</p><span id="more"></span><p>这块板子可能很多人都听过，它本来的定位是“边缘计算”和“机器人中枢”，算是英伟达给嵌入式 AI 场景设计的亲儿子产品。比如跑 YOLO 目标检测、机械臂控制，这些都是它的常见场景。</p><p>除此之外， Orin 还有一个优势，就是 <strong>完整继承 CUDA 生态</strong>。这就意味着，它和桌面 GPU 一样，能跑主流大模型和 AI 框架。相比之下，很多国产芯片虽然跑分也许很高，但因为缺乏 CUDA 生态，真正落地的时候往往要做大量适配工作。可能你下载了模型，但能不能跑起来就是另一个问题了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926190036366-20250926191346936-20250926191839278-20250926192032634.png" alt="渲染图"></p><p><strong>CUDA &#x3D; 生态优势</strong>。</p><p>这点在 AI 时代非常关键。使用懒猫 AI 算力仓，下载完模型就能马上测试，而非 CUDA 芯片用户，可能还要花上几个月适配环境。这就是差距。</p><p>所以当懒猫把 Orin 改造成家用级的 AI 算力仓时，我的第一反应就是：<strong>这玩意儿正好补上了家里缺的那块“私有超算”拼图。</strong></p><p>包装比我预想的更精美，拆开的时候甚至有点“科幻感”。外观是简洁的机身，棱角分明，风格上有点像星球大战里的装置。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/eb751b92c3b4aea950adf5df8818fef2-20250926191424716-20250926192101025.jpg" alt="实物图"></p><p>尽管实物拿着沉甸甸的，但尺寸比我想象的还要小一些，高度大约只有懒猫微服的 NAS 一半。拿在手里的重量也不到 1300 克，比 MacBook Pro 还要轻很多，所以理论上你带出门也不是不行（不是）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a3ae36dd200008b4f2d4b97e3828aca2-20250926191540512-20250926191840322-20250926192111914.jpg" alt="重量图"></p><p>这种“袖珍 + 全固态”的组合，第一观感就是结实耐用，而且空间利用率极高。塞进机柜里刚刚好。</p><p>接下来看看核心参数：</p><ul><li><strong>GPU</strong>：275T 算力，64 Tensor Core，2048 核心，最高 1.3GHz</li><li><strong>CPU</strong>：12 核 ARM Cortex-A78AE，最高 2.0GHz，轻量推理和任务调度非常合适</li><li><strong>显存</strong>：LPDDR5，带宽 204.8GB&#x2F;s，容量高达 64GB（显存+内存一体化设计）</li></ul><p>这套组合，基本就决定了它能直接运行 <strong>70B 参数级别的大模型</strong>，而且是真正意义上的“无限 Tokens”，不像云端那样有额度限制。</p><p>在存储扩展方面：</p><ul><li>自带 64GB eMMC 5.1 系统盘（足够跑系统和基础应用）</li><li>预留 2 个 M.2 插槽，最多支持 <strong>32TB SSD</strong>，这就意味着即便你想同时存储多个大模型，也能一次性装下。</li></ul><p>接口配置也很周到：</p><ul><li>视频输出：HDMI</li><li>外设：2 个 USB 3.2</li><li>网络：双网口（10G + 2.5G），特别适合多机联动或做本地集群</li><li>额外：还支持 WiFi6，无线连接没问题</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/screen2_pc_2-20250926191320702-20250926191839972.png" alt="接口图"></p><p>官方参数写着整机功耗 <strong>64W</strong>，所以很省电。要知道，一块台式机的 4090 往往就能拉到 400W，而 Orin 的 64W 几乎可以说是“节能怪兽”。我把一个小型工作站，直接塞进了我的机柜里。</p><p>这次买算力仓，还有一个重要原因：我家里已经在用懒猫微服的生态。算力仓一接入，就能和原来的设备形成闭环。</p><p>比如：</p><ul><li>NAS 负责存储数据和模型；</li><li>算力仓负责推理和生成；</li><li>懒猫微服的软件层负责调度和调用。</li></ul><p>这种组合让我感觉像是给自己搭了一个“家用 AI 实验室”。来一张和机柜里其他设备的合影 ↓</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4a69c38d0f8bae1668fb6ad5b7063909.jpg" alt="机柜合影"></p><p>懒猫算力仓的应用场景非常多：</p><ol><li><strong>大模型推理</strong>：本地跑 70B 模型，直接当私有 ChatGPT，避免云端隐私风险。</li><li><strong>文生图 &#x2F; 文生视频</strong>：跑 Stable Diffusion、Luma AI 之类的模型，图生视频速度应该会很快。</li><li><strong>科研实验</strong>：对于学生和研究者来说，算力仓相当于一台低功耗的工作站，可以在寝室里跑实验。</li><li><strong>企业内部 POC</strong>：小公司如果要测试 AI 原型，直接上算力仓，比租云 GPU 灵活得多。</li><li><strong>边缘推理 + 本地 RAG</strong>：结合家里的数据仓库，跑一个完全离线的知识问答系统，做“自己的 AI 知识助手”。</li></ol><p>整体来说，<strong>懒猫 AI 算力仓</strong>给我的感觉就是：</p><ul><li>小小一台，参数硬核；</li><li>CUDA 生态无缝兼容，开箱即跑大模型；</li><li>扩展灵活，功耗低，噪音小；</li><li>和懒猫微服生态结合，几乎是“家用 AI 实验室”的标配；</li><li>对我这种经常要跑 RAG、文生图的场景非常友好。</li></ul><p>如果说之前还在犹豫“要不要上云 GPU”，现在真的可以考虑直接在家里搞一台 Orin 算力仓。<strong>算力、省钱、数据安全，全部兼顾。</strong></p><p>更重要的是，这不是一台冰冷的机器，而是让我随时能打开 AI 世界大门的“钥匙”。</p>]]></content>
    
    
    <summary type="html">懒猫算力仓开箱体验，初步了解硬件配置与外观</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="算力仓" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%AE%97%E5%8A%9B%E4%BB%93/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫算力仓初探（二）：懒猫微服如何绑定算力仓？</title>
    <link href="https://blog.no-claw.com/posts/cbd8657f/"/>
    <id>https://blog.no-claw.com/posts/cbd8657f/</id>
    <published>2025-09-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在上一篇《懒猫算力仓初探（一）：久违的开箱》中，我们已经认识了这台看似“小巧”，实则暗藏强劲算力的设备——懒猫 AI 算力仓。很多朋友拿到设备后，最关心的问题就是：如何让这台算力仓与懒猫微服生态结合，从而发挥它真正的威力？今天这一篇，就带大家详细走一遍绑定流程，并结合实际体验，聊聊我在操作过程中遇到的细节和思考。</p><h3 id="为什么要绑定算力仓？"><a href="#为什么要绑定算力仓？" class="headerlink" title="为什么要绑定算力仓？"></a>为什么要绑定算力仓？</h3><p>算力仓从硬件层面来说就是一台高性能的小型 AI 算力设备，但如果只是孤立地插上网线、运行系统，它就像一台“裸机”，并不能和我们常用的软件生态融合。懒猫微服作为一个容器化的应用生态，本身聚合了丰富的 AI 应用、插件和社区资源。把算力仓绑定到微服，就意味着你不仅能在局域网内访问算力，还能直接调用它的 API 来支撑各类应用。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926214512857.png" alt="image-20250926214512857"></p><p>比如：</p><ul><li>在浏览器中实时调用算力仓进行网页总结。</li><li>在微服的应用市场中安装 AI 辅助工具，直接调用本地算力而不是远程云端。</li><li>在需要高隐私的场景下，本地算力仓可以避免敏感数据上传到外部云环境，数据留存在局域网。</li></ul><p>一句话总结：<strong>绑定就是打通生态，让算力仓成为微服体系中的“外挂显卡”</strong>。没有绑定，它只是一个单机的开发板；绑定之后，它才真正融入到懒猫微服的生态网络里。</p><h3 id="步骤一：准备-AI-浏览器"><a href="#步骤一：准备-AI-浏览器" class="headerlink" title="步骤一：准备 AI 浏览器"></a>步骤一：准备 AI 浏览器</h3><p>绑定流程的第一步是安装懒猫 AI 浏览器。这款浏览器是懒猫微服官方推出的专用入口，它在 Chromium 的基础上去除了 Google 的追踪代码，主打隐私和轻量。同时，它内置了算力仓插件，可以让浏览器与算力仓直接通信。</p><p>很多人可能会问：“为什么一定要用它？我能不能直接用 Chrome 或 Edge？”答案是理论上可以，但官方推荐 AI 浏览器，是因为它不仅省去了自己安装插件的步骤，还能确保和算力仓的兼容性。尤其是对小白用户而言，开箱即用的体验比手动安装插件要友好得多。</p><p>安装完成后，浏览器会自动跳转到 <code>https://ai.&lt;name&gt;.heiyu.space/</code> 这个地址。这其实是一个专门的配置门户，我们后续的绑定操作就是在这里完成的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926213513034.png" alt="image-20250926213513034"></p><h3 id="步骤二：下载安装算力仓插件"><a href="#步骤二：下载安装算力仓插件" class="headerlink" title="步骤二：下载安装算力仓插件"></a>步骤二：下载安装算力仓插件</h3><p>如果你使用的不是 AI 浏览器，也没关系。官方同样提供了独立的插件下载入口：</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">https://extensions-ai.&lt;name&gt;.heiyu.space/</span><br></pre></td></tr></table></figure><p>在这里下载安装后，效果和 AI 浏览器自带插件是完全一致的。区别只在于操作步骤多了一点点，但对于喜欢用主力浏览器的人来说，这个方式会更灵活。</p><p>插件的核心作用，就是在网页端注入算力仓的能力。比如，你在 Google 搜索页面时，可以直接点击总结按钮，让算力仓帮你对搜索结果做归纳。这一刻，你就能直观感受到“本地 AI 算力”与日常浏览体验结合的爽感。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926214958408.png" alt="image-20250926214958408"></p><h3 id="步骤三：进入算力仓绑定界面"><a href="#步骤三：进入算力仓绑定界面" class="headerlink" title="步骤三：进入算力仓绑定界面"></a>步骤三：进入算力仓绑定界面</h3><p>当浏览器启动并加载插件后，在右上角点击设置按钮，就能看到绑定算力仓的入口。界面非常简洁，主要就是一个“添加算力仓”的流程。</p><p>这里需要注意，绑定并不是直接输入算力仓的 IP，而是先让微服客户端与算力仓建立信任关系。换句话说，微服扮演的是“中介人”的角色，它负责把应用和算力仓之间的调用关系建立起来。这样设计的好处是：用户不需要关心底层 API 地址，只需要点几下就能完成对接。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926214732960.png" alt="image-20250926214732960"></p><h3 id="步骤四：开启微服客户端"><a href="#步骤四：开启微服客户端" class="headerlink" title="步骤四：开启微服客户端"></a>步骤四：开启微服客户端</h3><p>绑定算力仓之前，你需要先启动本地的懒猫微服客户端。在客户端里输入你的微服名称，然后等待连接成功。</p><p>这一环节是整个流程的关键：只有当微服客户端处于运行状态时，它才能帮你发现微服和算力仓。算力仓和微服的关系，就像 NAS 和群晖套件一样，必须要有一个“宿主环境”来承载它。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/800c28583660bcc92f751b4b542f43b2.png" alt="800c28583660bcc92f751b4b542f43b2"></p><h3 id="步骤五：添加算力仓"><a href="#步骤五：添加算力仓" class="headerlink" title="步骤五：添加算力仓"></a>步骤五：添加算力仓</h3><p>当微服和客户端连接成功后，你会在界面里看到“添加算力仓”的按钮。点击之后，微服就会开始在局域网内扫描设备。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4e001efa60a790bdecbbf7f07f420ce7.png" alt="4e001efa60a790bdecbbf7f07f420ce7"></p><p>这里有一个细节体验：<strong>只要算力仓接上网线，就能被自动发现</strong>。猜测它通过 mDNS&#x2F;Bonjour 的方式在局域网广播，客户端自然就能识别出来。对比市面上一些设备需要手动输入 IP 或者搞复杂的端口映射，懒猫算力仓的体验确实要顺滑很多。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/73b50f24caa028ad87376dcb26c096b0.png" alt="73b50f24caa028ad87376dcb26c096b0"></p><p>值得一提的是，算力仓的设计允许你绑定多台设备。如果你财力雄厚，可以一次性接入多个算力仓，微服会自动帮你做任务分配。这意味着什么？意味着你可以搭建一个“家庭 AI 超算集群”。对于 AI 极客来说，这是一个非常诱人的玩法。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ccb928d93c50ddefff6f2e4343a332dc.png" alt="ccb928d93c50ddefff6f2e4343a332dc"></p><h3 id="步骤六：设备配对"><a href="#步骤六：设备配对" class="headerlink" title="步骤六：设备配对"></a>步骤六：设备配对</h3><p>扫描到算力仓之后，在设备的背面你会看到一个“M”按键。只要按下它，就能完成和微服的配对。</p><p>为什么需要这一步？因为这是设备级的安全认证。只有物理触发了 M 键，微服才会确认你是设备的真实拥有者，避免别人通过网络把你的算力仓“偷走”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/84acbc7e3cb20a7a5276c523e3a7c03a.png" alt="84acbc7e3cb20a7a5276c523e3a7c03a"></p><p>按下之后，你会在客户端看到“配对成功”的提示，这意味着你的算力仓正式加入到了微服生态。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/23993a6c538cdc623e78bc491b15bcc0.png" alt="23993a6c538cdc623e78bc491b15bcc0"></p><h3 id="步骤七：绑定完成，功能解锁"><a href="#步骤七：绑定完成，功能解锁" class="headerlink" title="步骤七：绑定完成，功能解锁"></a>步骤七：绑定完成，功能解锁</h3><p>绑定完成后，你会在微服的设置页面里看到算力仓的选项。此时，你就可以选择不同的模型来运行对应功能。</p><p>比如：</p><ul><li>在网页总结时选择轻量级的 7B 模型，保证速度快。</li><li>在本地知识库问答时，调用 13B 模型，兼顾准确性和上下文理解。</li></ul><p>这一切，都是通过一个简洁的界面完成的，几乎不需要你去敲命令。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926214152435.png" alt="image-20250926214152435"></p><h3 id="使用体验：Google-首页总结"><a href="#使用体验：Google-首页总结" class="headerlink" title="使用体验：Google 首页总结"></a>使用体验：Google 首页总结</h3><p>绑定成功的第一个直观体验，就是在 Google 首页上使用总结功能。以前你可能需要安装第三方插件，或者把内容复制到 ChatGPT 界面里再问，现在直接在浏览器里点一下按钮，就能调用本地算力仓来生成总结。</p><p>更重要的是，这个总结是<strong>本地推理</strong>完成的，你的数据不会上传到外部云端。这对于关心隐私的人来说，是一个巨大的优势。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926213404428.png" alt="image-20250926213404428"></p><h3 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h3><p>整个绑定流程下来，我最大的感受有三点：</p><ol><li><strong>上手难度低</strong><br>无论是 AI 浏览器的内置插件，还是局域网自动发现的机制，都极大降低了新手的门槛。你不需要懂网络配置，也不需要手动输入复杂命令，基本“下一步、下一步”就能完成。</li><li><strong>安全性设计合理</strong><br>配对环节必须按下 M 键，这是一种物理确认机制，避免了远程劫持的风险。这点比纯软件的配对更让人安心。</li><li><strong>扩展性强</strong><br>从理论上讲，你可以无限接入算力仓，组建一个家庭算力集群。未来随着 AI 模型越来越大，这种本地集群的玩法可能会越来越受欢迎。</li></ol><p>在我看来，懒猫算力仓和懒猫微服的结合，是一次“硬件 + 软件生态”的双重升级。前者提供了扎实的算力基础，后者则给了它用武之地。两者结合之后，才算真正完成了从设备到应用的闭环。</p><p>未来我会继续分享在算力仓上部署不同 AI 应用的体验，看看它在文本生成、图像处理、甚至视频推理等场景中的表现。敬请期待《懒猫算力仓初探（三）：如何 SSH 登陆以及配置免密 sudo？》。</p>]]></content>
    
    
    <summary type="html">图文教程：懒猫微服绑定算力仓的完整操作步骤</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="算力仓" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%AE%97%E5%8A%9B%E4%BB%93/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫算力仓初探（三）：如何 SSH 免密登陆以及配置免密 sudo？</title>
    <link href="https://blog.no-claw.com/posts/a4c04b60/"/>
    <id>https://blog.no-claw.com/posts/a4c04b60/</id>
    <published>2025-09-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在拿到懒猫 AI 算力仓之后，除了插显示器操作，更常见的方式是通过 SSH 远程管理。这样不仅方便日常使用，也能在不接显示器的情况下完成调试和部署。本文将一步步演示如何用 SSH 登录算力仓，并配置免密登录与免密 sudo，让远程操作更高效、更顺滑。</p><p>懒猫算力仓启动后，我们可以通过 <strong>SSH</strong> 来进行远程管理。设备出厂时已默认开启 SSH 服务端，所以我们要做的第一步是获取它的 IP 地址。</p><h3 id="一、获取-IP-地址"><a href="#一、获取-IP-地址" class="headerlink" title="一、获取 IP 地址"></a>一、获取 IP 地址</h3><span id="more"></span><p>如果你使用路由器，可以在路由器后台查看分配到的 IP，并用 <code>telnet ip地址</code> 测试端口是否开放：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926194146520.png" alt="image-20250926194146520"></p><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">ip addr</span><br></pre></td></tr></table></figure><p>懒猫算力仓的默认用户名和密码都是 <code>nvidia</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">ssh nvidia@192.168.1.100</span><br></pre></td></tr></table></figure><p>首次登录会提示是否接受 <code>fingerprint</code>，输入 <code>yes</code> 即可。</p><h3 id="二、配置-SSH-免密登录"><a href="#二、配置-SSH-免密登录" class="headerlink" title="二、配置 SSH 免密登录"></a>二、配置 SSH 免密登录</h3><p>每次输入密码太麻烦，可以配置免密登录。</p><ol><li><p><strong>在本地生成 SSH 密钥</strong>（如果还没有）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -b 4096</span><br></pre></td></tr></table></figure><p>一路回车，默认生成在 <code>~/.ssh/id_rsa</code> 和 <code>~/.ssh/id_rsa.pub</code>。</p></li><li><p><strong>将公钥拷贝到目标机</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id nvidia@192.168.1.100</span><br></pre></td></tr></table></figure><p>如果目标机没有 <code>ssh-copy-id</code>，可以手动追加：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> ~/.ssh/id_rsa.pub | ssh nvidia@192.168.1.100 <span class="string">&quot;mkdir -p ~/.ssh &amp;&amp; cat &gt;&gt; ~/.ssh/authorized_keys&quot;</span></span><br></pre></td></tr></table></figure><p>以后只需执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh nvidia@192.168.1.100</span><br></pre></td></tr></table></figure><p>就能直接登录而无需密码。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926194621569.png" alt="image-20250926194621569"></p></li></ol><h3 id="三、使用-ssh-config-简化命令"><a href="#三、使用-ssh-config-简化命令" class="headerlink" title="三、使用 ~/.ssh/config 简化命令"></a>三、使用 <code>~/.ssh/config</code> 简化命令</h3><p>如果你经常要连同一台设备，可以在本机配置 SSH 简化命令。</p><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">vim ~/.ssh/config</span><br></pre></td></tr></table></figure><p>添加配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Host orin</span><br><span class="line">    HostName 192.168.1.100</span><br><span class="line">    User nvidia</span><br><span class="line">    Port 22</span><br><span class="line">    IdentityFile ~/.ssh/id_rsa</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">ssh orin</span><br></pre></td></tr></table></figure><p>配合免密登录，体验更流畅。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250926200656015.png" alt="image-20250926200656015"></p><h3 id="四、配置免密-sudo"><a href="#四、配置免密-sudo" class="headerlink" title="四、配置免密 sudo"></a>四、配置免密 sudo</h3><p>Ubuntu 上免密 <code>sudo</code> 有两种方式：</p><h4 id="方式一：直接修改-sudoers-文件"><a href="#方式一：直接修改-sudoers-文件" class="headerlink" title="方式一：直接修改 sudoers 文件"></a>方式一：直接修改 sudoers 文件</h4><p>进入算力仓：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /etc/sudoers</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">root    ALL=(ALL:ALL) ALL</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">nvidia  ALL=(ALL:ALL) NOPASSWD:ALL</span><br></pre></td></tr></table></figure><p>保存退出即可。</p><h4 id="方式二（推荐）：新建-sudoers-d-配置文件"><a href="#方式二（推荐）：新建-sudoers-d-配置文件" class="headerlink" title="方式二（推荐）：新建 sudoers.d 配置文件"></a>方式二（推荐）：新建 sudoers.d 配置文件</h4><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"><span class="built_in">echo</span> <span class="string">&quot;nvidia ALL=(ALL) NOPASSWD:ALL&quot;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/sudoers.d/nvidia</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 440 /etc/sudoers.d/nvidia</span><br></pre></td></tr></table></figure><h3 id="五、验证配置"><a href="#五、验证配置" class="headerlink" title="五、验证配置"></a>五、验证配置</h3><ol><li><p>尝试免密 SSH 登录。</p></li><li><p>登录后执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">ls</span> /root</span><br></pre></td></tr></table></figure><p>如果无需输入密码，说明免密 sudo 已生效。</p></li></ol><h3 id="六、SSH-常见排查方法（通用-Linux）"><a href="#六、SSH-常见排查方法（通用-Linux）" class="headerlink" title="六、SSH 常见排查方法（通用 Linux）"></a>六、SSH 常见排查方法（通用 Linux）</h3><p>如果 SSH 无法登录，可以按“本地 → 网络 → 服务端 → 配置”逐步排查：</p><h4 id="1-确认基本信息"><a href="#1-确认基本信息" class="headerlink" title="1. 确认基本信息"></a>1. 确认基本信息</h4><ul><li><p>用户名、IP 是否正确：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh user@192.168.1.100</span><br></pre></td></tr></table></figure></li><li><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">ssh -p 2222 user@192.168.1.100</span><br></pre></td></tr></table></figure></li></ul><h4 id="2-本地网络检查"><a href="#2-本地网络检查" class="headerlink" title="2. 本地网络检查"></a>2. 本地网络检查</h4><ul><li><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">ping 192.168.1.100</span><br></pre></td></tr></table></figure></li><li><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">telnet 192.168.1.100 22</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">nc -zv 192.168.1.100 22</span><br></pre></td></tr></table></figure></li></ul><h4 id="3-服务端检查"><a href="#3-服务端检查" class="headerlink" title="3. 服务端检查"></a>3. 服务端检查</h4><ul><li><p>确认 SSH 服务是否运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl status ssh    <span class="comment"># Ubuntu/Debian</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status sshd   <span class="comment"># CentOS/RedHat</span></span><br></pre></td></tr></table></figure></li><li><p>若未运行，启动并设置开机自启：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> ssh --now</span><br></pre></td></tr></table></figure></li></ul><h4 id="4-防火墙-安全组"><a href="#4-防火墙-安全组" class="headerlink" title="4. 防火墙&#x2F;安全组"></a>4. 防火墙&#x2F;安全组</h4><ul><li><p>Ubuntu:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ufw status</span><br><span class="line"><span class="built_in">sudo</span> ufw allow 22/tcp</span><br></pre></td></tr></table></figure></li><li><p>CentOS&#x2F;RHEL:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> firewall-cmd --permanent --add-service=ssh &amp;&amp; <span class="built_in">sudo</span> firewall-cmd --reload</span><br></pre></td></tr></table></figure></li></ul><h4 id="5-SSH-配置文件"><a href="#5-SSH-配置文件" class="headerlink" title="5. SSH 配置文件"></a>5. SSH 配置文件</h4><p>检查：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nano /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>关键项：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Port 22</span><br><span class="line">PermitRootLogin no</span><br><span class="line">PasswordAuthentication yes</span><br></pre></td></tr></table></figure><p>修改后重启：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl restart ssh</span><br></pre></td></tr></table></figure><h4 id="6-日志排查"><a href="#6-日志排查" class="headerlink" title="6. 日志排查"></a>6. 日志排查</h4><ul><li><p>Ubuntu：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">tail</span> -f /var/log/auth.log</span><br></pre></td></tr></table></figure></li><li><p>CentOS：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">tail</span> -f /var/log/secure</span><br></pre></td></tr></table></figure></li></ul><h4 id="7-权限检查"><a href="#7-权限检查" class="headerlink" title="7. 权限检查"></a>7. 权限检查</h4><p>确保 <code>.ssh</code> 目录和文件权限正确：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 700 ~/.ssh</span><br><span class="line"><span class="built_in">chmod</span> 600 ~/.ssh/authorized_keys</span><br></pre></td></tr></table></figure><p>通过本文的步骤，我们完成了以下几件事：</p><ol><li>使用 SSH 登录懒猫算力仓；</li><li>配置 SSH 公钥，实现免密登录；</li><li>利用 ~&#x2F;.ssh&#x2F;config 简化连接命令；</li><li>配置免密 sudo，省去输入密码的麻烦；</li><li>提供了常见 SSH 故障的排查方法。</li></ol><p>这样配置下来，你就能在懒猫算力仓上实现“一步登录 → 直接 sudo”的丝滑体验。无论是本地调试模型，还是在局域网中远程部署服务，你都能做到“一条命令直达 root 权限”。这让懒猫算力仓真正变成了一个可以随时掌控的 AI 算力伙伴</p>]]></content>
    
    
    <summary type="html">配置算力仓 SSH 免密登录和免密 sudo 的详细教程</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="算力仓" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%AE%97%E5%8A%9B%E4%BB%93/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫算力仓初探（四）：如何高效的向算力仓传输文件？</title>
    <link href="https://blog.no-claw.com/posts/7419d0c1/"/>
    <id>https://blog.no-claw.com/posts/7419d0c1/</id>
    <published>2025-09-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日常使用 <strong>懒猫 AI 算力仓</strong> 的过程中，文件传输是一个绕不开的话题。和微服不一样的是，算力仓没有运行专门的网盘服务。在使用过程中，不管你是想把本地写好的程序同步到算力仓里运行，还是需要把推理结果、训练好的模型参数下载回客户端，都需要一个稳定、安全、方便的传输方案。</p><p>很多新手刚上手算力仓时，都会遇到类似问题：</p><span id="more"></span><ul><li>“我怎么把代码传到算力仓里跑？”</li><li>“模型权重文件太大了，用 U 盘拷贝是不是更快？”</li><li>“我在笔记本写好脚本，能不能直接同步到算力仓？”</li></ul><p>其实完全不用担心。虽然算力仓的核心是 <strong>NVIDIA Jetson AGX Orin</strong> 开发板，但它运行的系统本质上是一个 Linux 发行版（Ubuntu 22.04）。因此，大多数 Linux 服务器的文件传输方法，都可以无缝应用到懒猫 AI 算力仓上。</p><p>本文将从 <strong>命令行工具</strong>、<strong>图形化工具</strong>、<strong>高效同步</strong>、<strong>临时传输</strong> 和 <strong>云同步</strong> 五个方面，详细介绍客户端如何与懒猫 AI 算力仓进行文件传输，帮助你构建一个高效、稳定的工作流。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250929175036168.png" alt="懒猫 AI 算力仓"></p><h3 id="一、前置准备：确认算力仓的访问方式"><a href="#一、前置准备：确认算力仓的访问方式" class="headerlink" title="一、前置准备：确认算力仓的访问方式"></a>一、前置准备：确认算力仓的访问方式</h3><p>在传文件之前，首先要确认两件事：<strong>算力仓的 IP 地址</strong>， <strong>ssh 服务运行状态</strong>。</p><h4 id="1-查看-IP-地址"><a href="#1-查看-IP-地址" class="headerlink" title="1. 查看 IP 地址"></a>1. 查看 IP 地址</h4><ul><li><strong>路由器后台</strong>：如果算力仓接入了家里的路由器，可以在后台设备列表中直接找到分配给它的内网 IP 地址。</li><li><strong>命令行</strong>：如果你能直接访问算力仓终端，执行以下命令：</li></ul><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">ip addr show</span><br></pre></td></tr></table></figure><p>找到类似 <code>192.168.x.x</code> 的地址即可。</p><blockquote><p>建议：为避免每次重启导致 IP 变化，可以在路由器中绑定 <strong>固定 IP</strong>。</p></blockquote><h4 id="2-开启-SSH-服务"><a href="#2-开启-SSH-服务" class="headerlink" title="2. 开启 SSH 服务"></a>2. 开启 SSH 服务</h4><p>懒猫 AI 算力仓默认预装了 <strong>OpenSSH</strong> 服务，因此只要知道用户名和密码，就能远程登录和传输文件。</p><ul><li>默认用户名：<code>nvidia</code></li><li>默认密码：<code>nvidia</code></li></ul><p>当然，更推荐配置 <strong>SSH Key 免密登录</strong>，更安全也更高效。具体配置可以参考这篇文章：<a href="https://mp.weixin.qq.com/s/HTzUdyfO4nXV6CPziWMFdg">算力仓 SSH 免密配置教程</a>。</p><h3 id="二、SCP：最经典的命令行传输方式"><a href="#二、SCP：最经典的命令行传输方式" class="headerlink" title="二、SCP：最经典的命令行传输方式"></a>二、SCP：最经典的命令行传输方式</h3><p><strong>SCP（Secure Copy Protocol）</strong> 是 Linux&#x2F;Unix 世界中最常见的文件传输方式，基于 SSH 加密，操作简单。</p><h4 id="常见用法"><a href="#常见用法" class="headerlink" title="常见用法"></a>常见用法</h4><ul><li><p><strong>上传文件到算力仓</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scp file.txt nvidia@192.168.x.x:~</span><br></pre></td></tr></table></figure></li><li><p><strong>从算力仓下载文件到客户端</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scp nvidia@192.168.x.x:~/1.txt .</span><br></pre></td></tr></table></figure></li><li><p><strong>上传整个目录</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scp -r ./localdir nvidia@192.168.5.50:/home/nvidia/</span><br></pre></td></tr></table></figure></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250929180358267.png" alt="SCP 示例"></p><h4 id="使用体验"><a href="#使用体验" class="headerlink" title="使用体验"></a>使用体验</h4><p>SCP 的优点是 <strong>原生支持、无需额外安装</strong>，适合偶尔传输单个文件或小目录；缺点是大文件速度一般，且不支持断点续传。</p><h3 id="三、SFTP：更灵活的文件传输"><a href="#三、SFTP：更灵活的文件传输" class="headerlink" title="三、SFTP：更灵活的文件传输"></a>三、SFTP：更灵活的文件传输</h3><p><strong>SFTP（SSH File Transfer Protocol）</strong> 同样基于 SSH，但它支持交互式操作，功能更丰富。</p><h4 id="命令行示例"><a href="#命令行示例" class="headerlink" title="命令行示例"></a>命令行示例</h4><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">sftp nvidia@192.168.5.50</span><br><span class="line"></span><br><span class="line">sftp&gt; lpwd             <span class="comment"># 查看本地目录</span></span><br><span class="line">sftp&gt; <span class="built_in">pwd</span>              <span class="comment"># 查看远程目录</span></span><br><span class="line">sftp&gt; put data.csv     <span class="comment"># 上传文件</span></span><br><span class="line">sftp&gt; get result.log   <span class="comment"># 下载文件</span></span><br><span class="line">sftp&gt; put -r models/ backup/   <span class="comment"># 上传目录</span></span><br><span class="line">sftp&gt; get -r logs/ ./          <span class="comment"># 下载目录</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250929181110359.png" alt="SFTP 命令行"></p><h4 id="图形化客户端"><a href="#图形化客户端" class="headerlink" title="图形化客户端"></a>图形化客户端</h4><p>如果你不喜欢命令行，可以选择图形化工具：</p><ul><li><a href="https://winscp.net/">WinSCP</a>（Windows）—— 界面类似资源管理器，拖拽即可传输文件。</li><li><a href="https://filezilla-project.org/">FileZilla</a>（跨平台）—— 免费开源，支持 SFTP，操作直观。</li></ul><p>参考文章：<a href="https://mp.weixin.qq.com/s/aXjsiUrbtFLYQODLYDt-MA">FileZilla 连接算力仓</a>。</p><blockquote><p>适用场景：SFTP 非常适合需要频繁上传&#x2F;下载小文件的用户，尤其是习惯拖拽操作的开发者。</p></blockquote><h3 id="四、rsync：大文件与目录同步神器"><a href="#四、rsync：大文件与目录同步神器" class="headerlink" title="四、rsync：大文件与目录同步神器"></a>四、rsync：大文件与目录同步神器</h3><p>如果你需要频繁同步数据集、模型权重或代码目录，<code>rsync</code> 是比 SCP 更高效的工具。</p><h4 id="常见命令"><a href="#常见命令" class="headerlink" title="常见命令"></a>常见命令</h4><ul><li><p><strong>上传目录</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz ./localdir/ nvidia@192.168.x.x:/home/nvidia/remote-dir</span><br></pre></td></tr></table></figure></li><li><p><strong>下载目录</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz nvidia@192.168.x.x:/home/nvidia/remote-dir ./localdir/</span><br></pre></td></tr></table></figure></li><li><p><strong>断点续传大文件</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz --partial --progress largefile.tar nvidia@192.168.x.x:/home/nvidia/</span><br></pre></td></tr></table></figure></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250929182022052.png" alt="rsync 传输"></p><h4 id="使用体验-1"><a href="#使用体验-1" class="headerlink" title="使用体验"></a>使用体验</h4><ul><li>速度快（仅传输差异部分）</li><li>支持断点续传</li><li>非常适合同步大文件和频繁更新的项目</li></ul><p>例如，如果你在本地不断修改代码仓库，<code>rsync</code> 能快速同步更新的部分，而无需重新传输整个目录。</p><h3 id="五、临时传输：快速“丢一个文件”"><a href="#五、临时传输：快速“丢一个文件”" class="headerlink" title="五、临时传输：快速“丢一个文件”"></a>五、临时传输：快速“丢一个文件”</h3><p>有时候你只是想传一个临时文件，不想折腾 SSH，这时可以用 <strong>HTTP 服务</strong> 或 <strong>curl</strong>。</p><h4 id="Python-内置-HTTP-服务"><a href="#Python-内置-HTTP-服务" class="headerlink" title="Python 内置 HTTP 服务"></a>Python 内置 HTTP 服务</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/myfiles</span><br><span class="line">python3 -m http.server 8000</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">http://192.168.x.x:8000/</span><br></pre></td></tr></table></figure><p>在算力仓终端使用如下命令即可下载文件。</p><h4 id="curl-下载"><a href="#curl-下载" class="headerlink" title="curl 下载"></a>curl 下载</h4><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 -O http://192.168.x.x:8000/target_file.txt</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 -# -O http://192.168.x.x:8000/large_file.iso</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 -o /path/to/save/file.txt http://192.168.x.x:8000/file.txt</span><br></pre></td></tr></table></figure><blockquote><p>适用场景：临时传小文件时非常好用，几乎不需要额外配置。</p></blockquote><h3 id="六、云盘与同步工具：跨设备协作更高效"><a href="#六、云盘与同步工具：跨设备协作更高效" class="headerlink" title="六、云盘与同步工具：跨设备协作更高效"></a>六、云盘与同步工具：跨设备协作更高效</h3><p>因为算力仓有 GUI 桌面，所以可以当作 PC 来用，如果你在笔记本和算力仓之间频繁切换，可以考虑 <strong>云同步工具</strong>。</p><ul><li><strong>Dropbox &#x2F; Google Drive &#x2F; OneDrive</strong>：跨平台支持，需要在算力仓上安装客户端。</li><li>**<a href="https://syncthing.net/">Syncthing</a>**：去中心化文件同步工具，实时更新、速度快。</li><li><strong>Git + GitHub&#x2F;GitLab</strong>：适合代码文件的版本控制与协作开发。</li></ul><blockquote><p>使用经验：我个人推荐 <strong>Syncthing + Git</strong> 的组合。Syncthing 用来自动同步数据和配置文件，而 Git 用来管理代码版本。这样既能保证文件实时更新，又能避免版本混乱。</p></blockquote><h3 id="七、选择建议与总结"><a href="#七、选择建议与总结" class="headerlink" title="七、选择建议与总结"></a>七、选择建议与总结</h3><p>常见场景下的最佳选择：</p><ol><li><strong>偶尔传输小文件</strong> → <code>scp</code></li><li><strong>图形化体验</strong> → <code>SFTP + WinSCP/FileZilla</code></li><li><strong>大文件和频繁同步</strong> → <code>rsync</code></li><li><strong>临时丢文件</strong> → Python HTTP 服务</li><li><strong>云盘与同步工具</strong> → Syncthing + Git</li></ol><p>对大多数懒猫 AI 算力仓用户来说，<strong>SCP + rsync</strong> 足以满足日常需求。如果你希望构建更稳定的工作流，可以结合 <strong>云盘&#x2F;同步工具</strong>，让文件管理更加自动化。</p><h3 id="八、结语"><a href="#八、结语" class="headerlink" title="八、结语"></a>八、结语</h3><p>懒猫 AI 算力仓不仅仅是一台小型 AI 超算，它同时也是一个灵活的 Linux 工作站。掌握不同的文件传输方式，能让你在写代码、跑实验、部署服务时更加高效。</p><ul><li>如果你只是偶尔传输文件，用 <code>scp</code> 就足够。</li><li>如果你是日常开发者，建议结合 <strong>SFTP + rsync</strong>。</li><li>如果你追求自动化与协作，<strong>Syncthing + Git</strong> 会让你事半功倍。</li></ul><p>未来，当你完全把算力仓融入到工作流时，它就不仅是一个推理&#x2F;训练机器，而是真正的 <strong>远程 AI 工作站</strong>。</p><p>掌握好文件传输的方法，你会发现：<strong>懒猫 AI 算力仓不仅提供算力，更能成为你日常开发中的最佳助手。</strong></p>]]></content>
    
    
    <summary type="html">介绍几种高效向懒猫算力仓传输文件的方法</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="算力仓" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%AE%97%E5%8A%9B%E4%BB%93/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫算力仓初探（五）：有了图形界面，算力仓轻松设置静态 IP</title>
    <link href="https://blog.no-claw.com/posts/2f8bfee6/"/>
    <id>https://blog.no-claw.com/posts/2f8bfee6/</id>
    <published>2025-09-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫 AI 算力仓是一台功能完整的边缘算力设备，基于 <strong>Ubuntu 22.04 桌面系统</strong>，既能运行 Docker、容器编排，又能承担 AI 推理任务。与懒猫微服不同的是，算力仓自带完整的 <strong>图形界面（GUI）</strong>，这让我们可以更直观地配置网络，比如今天要讲的——静态 IP。</p><p>本篇文章将详细讲解如何在算力仓桌面环境中，通过<strong>图形化方式（GUI）</strong>配置静态 IP 地址，并对比命令行 <code>nmtui</code> 的方法，让你的算力仓网络配置更稳、更容易管理。</p><h3 id="为什么要设置静态-IP？"><a href="#为什么要设置静态-IP？" class="headerlink" title="为什么要设置静态 IP？"></a>为什么要设置静态 IP？</h3><p>算力仓不像懒猫微服那样自带域名访问功能。<br>如果我们希望通过 SSH、VSCode 或 API 服务长期远程访问它，就必须让它的 IP 地址保持不变。否则，一旦 DHCP 动态分配的地址发生变化（比如重启路由器后），所有外部访问都会中断。</p><span id="more"></span><p>因此，<strong>静态 IP 是算力仓稳定访问的关键配置之一。</strong></p><h3 id="认识算力仓的图形界面"><a href="#认识算力仓的图形界面" class="headerlink" title="认识算力仓的图形界面"></a>认识算力仓的图形界面</h3><p>在「关于本机」中可以看到，算力仓运行的是 <strong>Ubuntu 22.04 LTS 桌面版</strong>。这意味着我们无需编辑配置文件或敲命令，而是可以直接通过系统设置进行网络管理。</p><p>右上角的系统托盘里有一个网络图标（有线网络是双箭头，Wi-Fi 是扇形波纹），这就是进入网络设置的入口。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a7295d8746fe4fd6e09d869e54527300-20251005124934623.png" alt="a7295d8746fe4fd6e09d869e54527300"></p><p>在 Ubuntu 桌面下，图形界面的背后其实就是 <strong>NetworkManager</strong> 服务，也就是我们在服务器端使用 <code>nmtui</code> 或 <code>nmcli</code> 命令操作的同一个核心组件。</p><h3 id="通过图形界面配置静态-IP（推荐方法）"><a href="#通过图形界面配置静态-IP（推荐方法）" class="headerlink" title="通过图形界面配置静态 IP（推荐方法）"></a>通过图形界面配置静态 IP（推荐方法）</h3><p>下面我们一步步完成整个配置流程，从打开设置到验证网络。</p><h4 id="打开网络设置"><a href="#打开网络设置" class="headerlink" title="打开网络设置"></a>打开网络设置</h4><p>点击右上角网络图标 → 选择「设置」或「Network」。<br>也可以在左下角应用菜单中搜索「Settings」，在左侧栏中选择「网络（Network）」。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0776627b6fcb975c41cfe8942acd5163-20251005124934790.png" alt="0776627b6fcb975c41cfe8942acd5163"></p><p>进入后，你会看到两种网络接口：</p><ul><li><strong>Wired（有线网络）</strong></li><li><strong>Wi-Fi（无线网络）</strong></li></ul><p>如果算力仓通过网线连接局域网，就选择有线网络进行配置。</p><h4 id="选择要修改的网络接口"><a href="#选择要修改的网络接口" class="headerlink" title="选择要修改的网络接口"></a>选择要修改的网络接口</h4><p>在「有线网络」一栏中，找到当前正在使用的接口（如 _Wired connection 1_），点击右侧的齿轮 ⚙️ 图标。</p><p>这个配置界面对应的是 NetworkManager 的“连接配置文件”。<br>每一个物理接口都有自己的配置文件，保存 IP、DNS、网关等信息。</p><h4 id="切换为“手动（Manual）”模式"><a href="#切换为“手动（Manual）”模式" class="headerlink" title="切换为“手动（Manual）”模式"></a>切换为“手动（Manual）”模式</h4><p>切换到 <strong>IPv4</strong> 标签页，可以看到「方法（Method）」下拉菜单：</p><ul><li>Automatic (DHCP)</li><li>Manual</li><li>Link-Local Only</li></ul><p>默认是「自动（DHCP）」，我们需要改为「手动（Manual）」。</p><p>修改后，会出现以下字段：</p><table><thead><tr><th>字段</th><th>示例值</th><th>说明</th></tr></thead><tbody><tr><td>地址（Address）</td><td><code>192.168.1.100</code></td><td>设备的固定 IP</td></tr><tr><td>子网掩码（Netmask）</td><td><code>255.255.255.0</code> 或 <code>/24</code></td><td>家用路由器一般为 <code>/24</code></td></tr><tr><td>网关（Gateway）</td><td><code>192.168.1.1</code></td><td>通常是路由器的地址</td></tr><tr><td>DNS</td><td><code>8.8.8.8, 1.1.1.1</code></td><td>推荐填写 Google 与 Cloudflare 的 DNS</td></tr></tbody></table><p><strong>注意：</strong><br>IP 地址必须是未被占用的。可以先用命令测试：</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">ping 192.168.1.100</span><br></pre></td></tr></table></figure><p>若返回 “Destination Host Unreachable”，说明该 IP 可用。</p><h4 id="保存并验证"><a href="#保存并验证" class="headerlink" title="保存并验证"></a>保存并验证</h4><p>填写完成后，点击右下角的「Apply」或「Save」。</p><p>接着关闭再开启一次该网络接口，让配置立即生效。<br>（很多人忘记这步，导致 IP 没有更新。）</p><p>打开终端（Ctrl + Alt + T），输入：</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">ifconfig</span><br></pre></td></tr></table></figure><p>确认你的新 IP 已生效后，测试外网连通性：</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">ping 8.8.8.8</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">64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=30.5 ms</span><br></pre></td></tr></table></figure><p>说明静态 IP 设置成功。</p><h4 id="配合懒猫微服实现远程访问"><a href="#配合懒猫微服实现远程访问" class="headerlink" title="配合懒猫微服实现远程访问"></a>配合懒猫微服实现远程访问</h4><p>静态 IP 设置完成后，你就可以在懒猫微服上用端口转发工具，将算力仓的 SSH 服务暴露到微服域名下。</p><p>例如，通过微服域名的某个端口就能直接 SSH 登录算力仓，实现两端联动：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005123932578.png" alt="image-20251005123932578"></p><h3 id="与命令行方式的对比"><a href="#与命令行方式的对比" class="headerlink" title="与命令行方式的对比"></a>与命令行方式的对比</h3><p>之前我们在懒猫微服上使用过 <code>nmtui</code> 命令配置静态 IP。<br>其实两种方法底层一致，都是操作 <strong>NetworkManager</strong>。</p><table><thead><tr><th>对比项</th><th>图形界面设置</th><th><code>nmtui</code> 命令行设置</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>较高</td></tr><tr><td>生效方式</td><td>即时生效</td><td>需手动保存</td></tr><tr><td>使用场景</td><td>桌面系统（算力仓）</td><td>无图形环境（微服）</td></tr></tbody></table><p>一句话总结：</p><blockquote><p>算力仓有桌面，用图形界面；<br>微服是服务器，用命令行。</p></blockquote><p>两者都基于同一个 NetworkManager，稳定、可靠又高效。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过图形界面设置静态 IP 的优点：</p><ul><li>操作直观，无需命令；</li><li>自动生成配置文件，减少出错；</li><li>保存后立即生效；</li><li>可随时切换回 DHCP，灵活方便。</li></ul><p><strong>推荐实践：</strong></p><ul><li><strong>懒猫微服（无界面）</strong> → 用命令行 <code>nmtui</code></li><li><strong>懒猫算力仓（有界面）</strong> → 用图形界面「设置」</li></ul><blockquote><p>懒猫微服靠命令行，算力仓靠图形界面。<br>底层同源，体验不同，效果一样稳。</p></blockquote>]]></content>
    
    
    <summary type="html">通过图形界面为懒猫算力仓设置静态 IP 地址</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="算力仓" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%AE%97%E5%8A%9B%E4%BB%93/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 可视化升级：无需额外部署 UI 软件</title>
    <link href="https://blog.no-claw.com/posts/a378e5b1/"/>
    <id>https://blog.no-claw.com/posts/a378e5b1/</id>
    <published>2025-09-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近 Easysearch 上线了一个非常实用的新功能 —— 内置 UI 可视化工具。它可以随着集群一并部署，无需额外安装任何插件或第三方软件。相比之下，虽然 Console 已经比 Kibana 简化了很多，但这个内置 UI 在易用性和轻量化方面更进一步。只需访问 <code>/_ui</code> 路径，就能直接进入可视化页面。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921045908492.png" alt="image-20250921045908492"></p><span id="more"></span><h3 id="集群监控开箱即用"><a href="#集群监控开箱即用" class="headerlink" title="集群监控开箱即用"></a>集群监控开箱即用</h3><p>登录后，首页就能看到集群的核心监控信息，包括节点数量、分片分布和索引情况。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921045940880-20250921051014825.png" alt="image-20250921045940880"></p><p>默认面板已经内置了常用指标：节点、分片、索引级别的监控数据都能直接查看。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921050421668.png" alt="image-20250921050421668"></p><p>许多用户期盼已久的 <strong>字段存储图</strong> 也终于回归，让存储空间的使用情况一目了然。整体上，基本所需的监控指标都齐备了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921050532286-20250921051015123.png" alt="image-20250921050532286"></p><h3 id="内置开发工具，所见即所得"><a href="#内置开发工具，所见即所得" class="headerlink" title="内置开发工具，所见即所得"></a>内置开发工具，所见即所得</h3><p>其实我最喜欢的功能是这个开发工具，这样在部署集群之后就可以很容易的写 DSL 语句来执行来。不需要调用 REST API 或者暗转跟其他工具，Easysearch 部署好之后，所见即所得。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921050208215-20250921051015269.png" alt="image-20250921050208215"></p><p>这意味着集群部署完成后，你就能立刻：</p><ul><li>编写并执行查询语句</li><li>测试复杂的聚合和过滤</li><li>实时查看返回结果</li></ul><p>这种“所见即所得”的体验，大大提升了调试和使用的效率。</p><h3 id="别名与索引模板管理"><a href="#别名与索引模板管理" class="headerlink" title="别名与索引模板管理"></a>别名与索引模板管理</h3><p>除了监控和开发工具，界面中还集成了 <strong>别名管理</strong> 和 <strong>索引模板</strong> 功能。该有的功能差不多都有了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921050348521.png" alt="image-20250921050348521"></p><p>这让日常配置和维护变得更加直观，无需频繁在命令行和 JSON 文件之间来回切换。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这次更新，可以说是把一个“微缩版 Console”直接集成进了 Easysearch 内部：</p><ul><li>开箱即用，无需额外部署 UI</li><li>监控指标完善，核心信息一目了然</li><li>集成开发工具，提升 DSL 使用体验</li><li>支持别名和索引模板，简化日常维护</li></ul><p>对于开发者和运维人员来说，这无疑让 Easysearch 的使用更加高效、轻量化，真正实现了“安装即用”。</p>]]></content>
    
    
    <summary type="html">Easysearch 内置可视化功能升级，无需额外部署 UI 工具即可管理</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十七）：KSpeeder 加速，让 Docker 镜像不再超时</title>
    <link href="https://blog.no-claw.com/posts/6300bfd5/"/>
    <id>https://blog.no-claw.com/posts/6300bfd5/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在国内使用 Docker 时，镜像拉取缓慢、超时以及镜像源不稳定几乎是开发者的日常困扰。虽然市面上存在一些第三方加速镜像，但它们往往会随着时间逐渐失效，或者更新不够及时。</p><p>如今，我们可以在 <strong>懒猫微服</strong> 的应用商店中一键部署 <strong>KSpeeder</strong>，并结合懒猫自带的 <strong>端口转发、内网穿透、可视化管理</strong> 等功能，使 Docker 镜像加速更加高效、易用和可维护。</p><h3 id="为什么选择懒猫微服"><a href="#为什么选择懒猫微服" class="headerlink" title="为什么选择懒猫微服"></a>为什么选择懒猫微服</h3><p>在传统环境下，部署像 KSpeeder 这样的工具通常需要：</p><ul><li>手动构建镜像和容器；</li><li>自行配置网络和转发规则；</li><li>解决外部访问和权限问题。</li></ul><p>而在懒猫微服上，这些复杂步骤被大幅简化：</p><span id="more"></span><ul><li><strong>应用商店一键安装</strong>：直接获取 KSpeeder，无需自行构建镜像或处理依赖问题；</li><li><strong>内网穿透</strong>：无需复杂配置，即可在异地访问加速服务；</li></ul><p>这使得 KSpeeder 不再是一个需要额外维护的独立工具，而是融入懒猫微服生态的“开箱即用”解决方案。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250923074519753-20250923083611759.png" alt="懒猫微服应用商店"></p><h3 id="KSpeeder-的功能特性"><a href="#KSpeeder-的功能特性" class="headerlink" title="KSpeeder 的功能特性"></a>KSpeeder 的功能特性</h3><p>在懒猫微服的支持下，KSpeeder 能够提供以下关键能力：</p><ul><li><strong>多镜像并发下载</strong><br>支持同时从多个镜像源并行下载，就像开启多条高速通道，一次性快速拉取多个镜像，节省大量等待时间。</li><li><strong>动态负载均衡</strong><br>智能选择最优镜像源，并根据实时情况分配下载任务，充分利用带宽和资源，始终保持较高下载速度。</li><li><strong>断点续传</strong><br>下载中断后可从中断位置继续，无需重新开始，尤其在拉取大镜像时能有效节省时间和流量。</li><li><strong>实时监控</strong><br>提供可视化界面，直观展示下载进度、镜像源利用率等信息，方便用户动态调整策略，确保任务顺利完成。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250923074618769.png" alt="功能界面"></p><p>特别感谢社区群友 <strong>Peterpig</strong> 的帮助，由于 KSpeeder 的限制，镜像源无法直接通过 ingress 暴露端口，因此这里使用了懒猫微服的 <strong>端口转发功能</strong>，保证服务可用。</p><h3 id="配置与使用"><a href="#配置与使用" class="headerlink" title="配置与使用"></a>配置与使用</h3><h4 id="1-修改-hosts-文件"><a href="#1-修改-hosts-文件" class="headerlink" title="1. 修改 hosts 文件"></a>1. 修改 hosts 文件</h4><p>在本地客户端上，将 <code>registry.linkease.net</code> 指向懒猫微服所在主机的 IP 地址：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /etc/hosts</span><br></pre></td></tr></table></figure><p>在文件最后一行添加：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.x.x   registry.linkease.net</span><br></pre></td></tr></table></figure><p>（例如：<code>192.168.5.128   registry.linkease.net</code>）</p><p>完成后刷新 DNS 缓存：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dscacheutil -flushcache</span><br><span class="line"><span class="built_in">sudo</span> killall -HUP mDNSResponder</span><br></pre></td></tr></table></figure><p>通过 <code>ping</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">ping registry.linkease.net</span><br></pre></td></tr></table></figure><h4 id="2-拉取镜像"><a href="#2-拉取镜像" class="headerlink" title="2. 拉取镜像"></a>2. 拉取镜像</h4><p>完成配置后，就可以直接通过 KSpeeder 加速源拉取镜像：</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">docker pull registry.linkease.net:5443/image:tag</span><br></pre></td></tr></table></figure><h4 id="3-Docker-引擎配置"><a href="#3-Docker-引擎配置" class="headerlink" title="3. Docker 引擎配置"></a>3. Docker 引擎配置</h4><p>我使用的是 Orbstack，可以在其配置文件中加入镜像加速源，让 Docker 默认通过懒猫微服上的 KSpeeder 服务：</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="attr">&quot;registry-mirrors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;https://registry.linkease.net:5443&quot;</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>这样每次执行 <code>docker pull</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">docker pull nginx</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"><span class="built_in">cat</span> ~/.orbstack/config/docker.json</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;registry-mirrors&quot;</span> : [</span><br><span class="line">    <span class="string">&quot;https:\/\/registry.linkease.net:5443&quot;</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2a39d0cb031c0325def90945917e0120.png" alt="Orbstack 配置"></p><h3 id="效果验证"><a href="#效果验证" class="headerlink" title="效果验证"></a>效果验证</h3><p>配置完成后，可以在懒猫微服的 Web 界面直观地查看镜像下载状态与速度。</p><p>实际测试表明，借助懒猫微服的 <strong>端口转发和可视化管理能力</strong>，KSpeeder 能够稳定高效地运行，显著缓解 Docker 镜像下载慢和中断的问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3da382cfb1371998a0e8d194e74533c9.png" alt="加速效果"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li><strong>KSpeeder</strong> 提供了并发下载、负载均衡、断点续传和实时监控等功能，有效提升 Docker 镜像拉取效率。</li><li><strong>懒猫微服</strong> 则通过应用商店、端口转发、内网穿透和可视化管理，最大化发挥了 KSpeeder 的价值，让开发者能够快速部署、低成本维护。</li></ul><p>在频繁依赖 Docker 镜像的开发环境中，<strong>“KSpeeder + 懒猫微服”</strong> 是一个高效、稳定、可扩展的组合方案，值得推荐。</p>]]></content>
    
    
    <summary type="html">用 KSpeeder 加速懒猫微服上的 Docker 镜像拉取，告别超时问题。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十六）：Obsidian 用 GIT 同步总是冲突，果断存到懒猫微服了</title>
    <link href="https://blog.no-claw.com/posts/a1d9284e/"/>
    <id>https://blog.no-claw.com/posts/a1d9284e/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>三年前，朋友推荐我使用 Obsidian 记笔记。除了炫酷的插件功能之外，最吸引人的就是云同步。虽然 Obsidian 提供了官方同步功能，但价格不菲，所以我们一直在寻找平替方案。朋友选择了官方同步，而我当时用了 Git 来同步。</p><p>看似解决了问题，但 Git 同步的体验并不好：<strong>多端同步时经常遇到冲突</strong>，每次写完笔记都要处理一堆合并，既麻烦又影响心情。这件事困扰了我很久。</p><p>直到后来入手了 <strong>懒猫微服</strong>，问题才彻底缓解，所以决定把这段折腾经历记录下来。</p><span id="more"></span><h3 id="用懒猫微服替代-Git，同步更顺畅"><a href="#用懒猫微服替代-Git，同步更顺畅" class="headerlink" title="用懒猫微服替代 Git，同步更顺畅"></a>用懒猫微服替代 Git，同步更顺畅</h3><p>懒猫提供了基于 <strong>WebDAV 协议</strong>的网络存储，天然支持 Obsidian 的同步需求。它不像 Git 那么复杂，不需要 commit&#x2F;push，也不会频繁出现冲突。只要配置一次，就能稳定、流畅地跑起来。</p><p>所以我把 Obsidian 笔记直接备份到懒猫微服。在网盘中可以看到 WebDAV 的域名、用户名和密码。懒猫微服的转发没有任何限制，443 端口也能直接使用。这样即使在外面，也能随时访问。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920201600119-20250920205148662.png" alt="image-20250920201600119"></p><h3 id="插件安装与配置"><a href="#插件安装与配置" class="headerlink" title="插件安装与配置"></a>插件安装与配置</h3><p>首先在 Obsidian 插件市场搜索并安装第三方插件 <strong>【Remotely Save】</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920201306126.png" alt="image-20250920201306126"></p><p>在【Remotely Save】中，选择 <strong>WebDAV 模式</strong>，然后输入服务器地址：【地址+端口+目录路径】。</p><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">https://file.name.heiyu.space/dav/ob_notes</span><br></pre></td></tr></table></figure><p>由于 <code>/dav</code> 是网盘的根目录，我新建了一个文件夹 <strong>ob_notes</strong> 来存放笔记，方便结构化管理。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920201508668.png" alt="image-20250920201508668"></p><p>输入懒猫微服提示的用户名和密码，鉴权方式保持 <strong>basic</strong>，然后检查服务器连接。如果目录不存在，就会在检查时报错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920202143870.png" alt="image-20250920202143870"></p><h3 id="开启自动同步"><a href="#开启自动同步" class="headerlink" title="开启自动同步"></a>开启自动同步</h3><p>我设置了 <strong>自动运行</strong>，并且每 10 分钟同步一次。毕竟是自己的微服，不存在 API 限制，也不用担心额外费用。更重要的是，我开启了 <strong>双向同步</strong>：</p><ul><li>本地改动会同步到微服；</li><li>微服上的改动也会同步回本地。</li></ul><p>再也不用担心 Git 冲突，写完笔记就能安心同步。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920204647077.png" alt="image-20250920204647077"></p><p>这是我在网盘上的结果，保存的瞬间，笔记就自动完成同步：</p><ul><li>保存会<strong>自动更新到服务端</strong></li><li>本地新建或删除文件，也会<strong>实时更新到微服</strong></li><li>微服新建或删除文件，本地也会<strong>自动保持一致</strong></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250920202205342.png" alt="image-20250920202205342"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>写到这里，其实结论很简单：<strong>懒猫微服彻底解决了我在 Obsidian 上被 Git 同步折磨的痛点</strong>。相比频繁冲突的 Git，懒猫微服的 WebDAV 同步方式更简单、更稳定，还能随时随地使用。</p><p>如果你也在用 Obsidian，又还在为同步问题发愁，不妨果断试试懒猫微服——真正做到写完即同步，省心又安心。</p><p>一句话：<strong>Obsidian 最佳拍档，果断选懒猫微服。</strong></p>]]></content>
    
    
    <summary type="html">Obsidian 用 Git 同步频繁冲突，改用懒猫微服网盘存储笔记更省心。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十八）：坏掉的 Windows 不要扔，硬盘插在懒猫上还能用</title>
    <link href="https://blog.no-claw.com/posts/768b4867/"/>
    <id>https://blog.no-claw.com/posts/768b4867/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近整理旧电脑，发现有台 Windows 已经无法正常开机了。但是里面可能还存着一些以前的资<br>料和照片，还是想先看看有没有重要数据能救回来。这种情况电脑已经无法开机了，用 Windows PE 去修复启动基本没戏，于是我换了个思路：干脆把硬盘直接拆出来，接到懒猫微服上去。</p><p>说干就干，从箱子里翻出一个硬盘盒，把拆下来的硬盘装进去，再用 Type-C 数据线插到懒猫微服后面的接口，立刻就能当作外接硬盘使用。</p><span id="more"></span><h3 id="df-TH-查看挂载情况"><a href="#df-TH-查看挂载情况" class="headerlink" title="df -TH 查看挂载情况"></a>df -TH 查看挂载情况</h3><p>为了确认是否挂载成功，我在懒猫的终端输入了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">df -TH</span><br></pre></td></tr></table></figure><p>输出结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Filesystem            Type      Size  Used Avail Use% Mounted on</span><br><span class="line">/dev/sdb3             fuseblk   2.1T   48G  2.0T   3% /lzcsys/run/media/系统</span><br></pre></td></tr></table></figure><p>逐行解释一下：</p><ul><li><strong>设备</strong>：<code>/dev/sdb3</code> → 说明识别到的硬盘分区是 <code>sdb3</code>；</li><li><strong>文件系统类型</strong>：<code>fuseblk</code> → 代表这是通过 <strong>FUSE 驱动</strong>挂载的块设备。大多数情况下，这就是 <strong>NTFS</strong> 文件系统，因为 Linux 自带的 NTFS 支持往往依赖 <code>ntfs-3g</code> 这样的 FUSE 驱动；</li><li><strong>容量情况</strong>：硬盘总大小 2.1 TB，目前只用了 48 GB，剩余 2.0 TB；</li><li><strong>挂载点</strong>：<code>/lzcsys/run/media/系统</code>，懒猫系统会自动在 <code>/media</code> 下生成一个中文目录，名字就是 Windows 盘符的中文别名（比如“系统”）。</li></ul><p>也就是说，这块 Windows 硬盘已经被完整挂载，可以直接访问。</p><h3 id="lsblk-查看分区结构"><a href="#lsblk-查看分区结构" class="headerlink" title="lsblk 查看分区结构"></a>lsblk 查看分区结构</h3><p>为了更清楚地看到硬盘的分区情况，我又执行了 <code>lsblk</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">(base) lzcbox-029c588e ~ # lsblk</span><br><span class="line">NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS</span><br><span class="line">sdb           8:16   0   1.8T  0 disk</span><br><span class="line">├─sdb1        8:17   0   300M  0 part</span><br><span class="line">├─sdb2        8:18   0    16M  0 part</span><br><span class="line">└─sdb3        8:19   0   1.8T  0 part /lzcsys/run/media/系统</span><br></pre></td></tr></table></figure><p>从结果可以看出：</p><ul><li><code>sdb1</code> → 300 MB，这个就是 <strong>EFI 启动分区</strong>。在 Windows 系统里，这个分区通常用来存放启动管理器、Boot Loader 等信息；</li><li><code>sdb2</code> → 16 MB，这是 <strong>MSR（Microsoft Reserved）分区</strong>，Windows 在初始化 GPT 磁盘时会自动保留，用来为将来的系统更新留空间；</li><li><code>sdb3</code> → 1.8 TB 主分区，真正存放用户数据的地方，现在已经被挂载到了 <code>/lzcsys/run/media/系统</code>。</li></ul><p>这套分区方案是 Windows 的标准 GPT 分区表结构。如果是 Linux，通常会看到 <code>/boot</code>、<code>swap</code>、<code>/home</code> 这样的挂载点。而 Windows 则用 EFI + MSR + 主分区的方式来管理。</p><h3 id="进入数据目录"><a href="#进入数据目录" class="headerlink" title="进入数据目录"></a>进入数据目录</h3><p>知道分区后，下一步就是进入挂载目录 <code>/lzcsys/run/media/系统</code>。<br>里面的目录结构几乎和 Windows 下看到的一样，核心的就是 <code>Users</code> 文件夹。</p><p>例如：</p><figure class="highlight plaintext"><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">(base) lzcbox-029c588e ~ # ls /lzcsys/run/media/系统/Users</span><br><span class="line">Administrator  Default  MyUser  Public</span><br></pre></td></tr></table></figure><p>在 <code>MyUser</code> 目录下，能找到 <code>Desktop</code>、<code>Documents</code>、<code>Downloads</code>、<code>Pictures</code> 等熟悉的文件夹。<br>这就是 Windows 用户的个人数据目录。只要进入对应的用户文件夹，就能直接把桌面文件、文档、照片、下载内容拷出来。</p><p>这里要注意：</p><ul><li>有些系统文件夹（比如 <code>Program Files</code>）没必要去动，大部分个人数据都在 <code>Users</code> 目录下；</li><li>如果系统里开了 BitLocker 磁盘加密，那么在 Linux 下可能无法直接读，需要先解锁。</li></ul><h3 id="懒猫网盘直连"><a href="#懒猫网盘直连" class="headerlink" title="懒猫网盘直连"></a>懒猫网盘直连</h3><p>更方便的一点是：懒猫微服不仅仅能在终端挂载，还能在自带的 <strong>网盘界面</strong>里直接显示这个外接硬盘。</p><p>我打开懒猫的 Web 网盘页面，果然多出了一个名为“系统”的磁盘目录。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251003204136190.png" alt="image-20251003204136190"><br>点进去以后，就能直接看到原来 Windows 里的 <code>Users</code> 文件夹。</p><p>这意味着：</p><ul><li>不用进终端命令行；</li><li>直接用浏览器就能访问、下载文件；</li><li>大文件也能在局域网里高速传输。</li></ul><p>对于普通用户来说，这个功能基本上等于“即插即用”。坏掉的 Windows 硬盘，插上懒猫就能当网盘用。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>整个过程体验下来，其实有点像“数据救援”。<br>一台电脑坏了，换做以前，大家可能要找专业修理店，或者买 制作 Windows PE 启动盘去折腾。</p><p>但有了懒猫微服，这件事变得非常简单：</p><ol><li>硬盘拆下来，接上懒猫；</li><li>系统自动挂载，网盘直接显示；</li><li>打开 Users 文件夹，把需要的数据拷出来。</li></ol><p>从头到尾几乎不需要额外的软件，Linux 自带的 FUSE 驱动就能把 NTFS 分区挂上去。</p><p>即使完全不会 Linux 命令行，也能通过懒猫的网盘访问到文件。</p><p><strong>“坏掉的 Windows 不要扔，插在懒猫上还能用。”</strong></p>]]></content>
    
    
    <summary type="html">坏掉的 Windows 电脑硬盘插到懒猫微服上，数据照样能读取使用。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十九）：域名解析与 2.5G 测速体验</title>
    <link href="https://blog.no-claw.com/posts/63e9bc75/"/>
    <id>https://blog.no-claw.com/posts/63e9bc75/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多朋友在用 NAS 或者家用服务器的时候，都会习惯直接用 <strong>IP 地址</strong>来访问设备。但当我开始深入折腾 <strong>懒猫微服</strong>时，发现它的逻辑和传统 NAS 有点不一样：懒猫不是给你一个裸 IP，而是直接分配一个 **域名 <code>&lt;机器名&gt;.heiyu.space</code>**。</p><p>这就意味着，不管你是在内网还是外网，访问方式都是一致的——直接用域名即可。对习惯了公网&#x2F;私网切换的我来说，这个设计一开始挺新奇，也忍不住想探索背后的机制和性能表现。于是就有了这篇文章：一次围绕 <strong>懒猫微服网络访问和测速表现</strong>的实战记录。</p><span id="more"></span><p>你可以使用 dig AAAA &lt;机器名&gt;.heiyu.space 来解析这个域名，背后是一个 IPV6 的地址。尝试破解了很久也没弄懂这个这个具体的转发机制，不过结论就是，不管你在内网还是互联网上访问这个域名都没问题。当然如果你内网有代理的话，流量很可能通过代理从外边转一圈再回来，这个需要手动改下规则。</p><h2 id="设备环境"><a href="#设备环境" class="headerlink" title="设备环境"></a>设备环境</h2><ol><li>Macbook Pro M2 Pro，网卡支持 WiFi 6E</li><li>懒猫微服，内置 AX210 无线网卡</li><li>中兴路由器 1G，小米路由器 2.5G</li></ol><p>先说结论，懒猫微服的无线网卡还是很好的。不过也遇到了在 UPS 附近出现严重干扰的情况。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0a0396473ba1a166d23e2cbd46a6148c.jpg" alt="0a0396473ba1a166d23e2cbd46a6148c"></p><h2 id="访问懒猫微服的方式"><a href="#访问懒猫微服的方式" class="headerlink" title="访问懒猫微服的方式"></a>访问懒猫微服的方式</h2><ol><li>域名访问：<code>&lt;设备名&gt;.heiyu.space</code></li><li>有线 IP 地址</li><li>无线 IP 地址</li></ol><p>为什么要把有线和无线 IP 分开说？因为逻辑上它和云服务器一样：<strong>用公网 IP 就是走公网，用私有 IP 就是走内网</strong>。</p><h2 id="查看协商速率"><a href="#查看协商速率" class="headerlink" title="查看协商速率"></a>查看协商速率</h2><p>在 Linux 环境下，可以使用 <code>iw wlp4s0 link</code> 查看 WiFi 的连接情况：</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">watch -n 1 iw wlp4s0 link</span><br></pre></td></tr></table></figure><p>如果提示 <code>Not connected.</code>，说明当前 WiFi 没有连接。</p><h2 id="测速工具与对比"><a href="#测速工具与对比" class="headerlink" title="测速工具与对比"></a>测速工具与对比</h2><p>为了保证测试结果客观，我前后测试了很久，还顺带拿黑群晖做了对比。测试环境涵盖了 1G 和 2.5G 的场景，工具主要用了 <strong>iperf3</strong> 和 <strong>LibreSpeed</strong>。</p><p>不过在 2.5G 的小米路由器环境下，经常遇到抽风的情况，这点要单独吐槽一下。</p><h3 id="网络测速"><a href="#网络测速" class="headerlink" title="网络测速"></a>网络测速</h3><p>有限可以跑满 1G，WIFI6 协商速率可以跑满 2401Mbps。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627180950640.png" alt="image-20250627180950640"></p><p>基本也是 WIFI6 协商满速 2401Mbps</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627175916845.png" alt="image-20250627175916845"></p><h4 id="纯有线测速"><a href="#纯有线测速" class="headerlink" title="纯有线测速"></a>纯有线测速</h4><p>使用 IP 测速度</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec   113 MBytes   948 Mbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec   113 MBytes   946 Mbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec   113 MBytes   944 Mbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec   113 MBytes   944 Mbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec   113 MBytes   945 Mbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec   112 MBytes   943 Mbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec   112 MBytes   936 Mbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec   112 MBytes   943 Mbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec   112 MBytes   943 Mbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec   112 MBytes   942 Mbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-10.00  sec  1.10 GBytes   943 Mbits/sec                  sender</span><br><span class="line">[  5]   0.00-10.00  sec  1.10 GBytes   941 Mbits/sec                  receiver</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>基本稳定在 940Mbps 左右。</p><h4 id="纯无线测速"><a href="#纯无线测速" class="headerlink" title="纯无线测速"></a>纯无线测速</h4><h4 id="Macbook-Pro-无线-对-懒猫有线"><a href="#Macbook-Pro-无线-对-懒猫有线" class="headerlink" title="Macbook Pro 无线 对 懒猫有线"></a>Macbook Pro 无线 对 懒猫有线</h4><h4 id="Macbook-Pro-有线对懒猫无线"><a href="#Macbook-Pro-有线对懒猫无线" class="headerlink" title="Macbook Pro 有线对懒猫无线"></a>Macbook Pro 有线对懒猫无线</h4><h3 id="2-5G-测速"><a href="#2-5G-测速" class="headerlink" title="2.5G 测速"></a>2.5G 测速</h3><p>可以看到这里已经是协商到了 2500Mbps。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627163544855.png" alt="image-20250627163544855"></p><p>即便是 WiFi7 路由器，由于终端限制，TX 协商依旧只有 2401Mbps。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cd325443fcce4d48fb870890d6b1a38c.png" alt="cd325443fcce4d48fb870890d6b1a38c"></p><p>Macbook Pro 和懒猫微服有线对打（2.5G）</p><p>在商店里的 Libre Speed 中测速如下：（APP 内部测速：）<img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627163853892.png" alt="image-20250627163853892"></p><p>浏览器测速（似乎是有些损失）：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627130407977.png" alt="image-20250627130407977"></p><p>不清楚商店是不是有什么限制，但是如果自己用 playground dokcer 部署就没啥问题：</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">pg-docker run -p 2333:80 registry.lazycat.cloud/librespeed:23.5.12</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627130350614.png" alt="image-20250627130350614"></p><p>而在 openspeetest 似乎是有 bug：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627130708521.png" alt="image-20250627130708521"></p><p>使用域名（全有线）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">iperf3 -c micro.heiyu.space                                                         (base) 12:15:39</span><br><span class="line">Connecting to host micro.heiyu.space, port 5201</span><br><span class="line">[  6] local fc03:1136:3803:e43f:1f7e:9149:3f1f:0 port 59188 connected to fc03:1136:384f:313:a637:437:d22b:0 port 5201</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  6]   0.00-1.00   sec  87.2 MBytes   732 Mbits/sec</span><br><span class="line">[  6]   1.00-2.00   sec  91.2 MBytes   765 Mbits/sec</span><br><span class="line">[  6]   2.00-3.00   sec  90.6 MBytes   760 Mbits/sec</span><br><span class="line">[  6]   3.00-4.00   sec  91.2 MBytes   765 Mbits/sec</span><br><span class="line">[  6]   4.00-5.00   sec  92.0 MBytes   772 Mbits/sec</span><br><span class="line">[  6]   5.00-6.00   sec  92.6 MBytes   777 Mbits/sec</span><br><span class="line">[  6]   6.00-7.00   sec  90.9 MBytes   763 Mbits/sec</span><br><span class="line">[  6]   7.00-8.00   sec  90.8 MBytes   761 Mbits/sec</span><br><span class="line">[  6]   8.00-9.00   sec  91.4 MBytes   767 Mbits/sec</span><br><span class="line">[  6]   9.00-10.00  sec  89.5 MBytes   751 Mbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  6]   0.00-10.00  sec   907 MBytes   761 Mbits/sec                  sender</span><br><span class="line">[  6]   0.00-10.00  sec   906 MBytes   760 Mbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br><span class="line">❰xu❙~❱✔≻ iperf3 -c micro.heiyu.space -R                                                      (base) 12:16:12</span><br><span class="line">Connecting to host micro.heiyu.space, port 5201</span><br><span class="line">Reverse mode, remote host micro.heiyu.space is sending</span><br><span class="line">[  6] local fc03:1136:3803:e43f:1f7e:9149:3f1f:0 port 59310 connected to fc03:1136:384f:313:a637:437:d22b:0 port 5201</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  6]   0.00-1.00   sec   168 MBytes  1.41 Gbits/sec</span><br><span class="line">[  6]   1.00-2.00   sec   178 MBytes  1.49 Gbits/sec</span><br><span class="line">[  6]   2.00-3.00   sec   184 MBytes  1.55 Gbits/sec</span><br><span class="line">[  6]   3.00-4.00   sec   182 MBytes  1.52 Gbits/sec</span><br><span class="line">[  6]   4.00-5.00   sec   182 MBytes  1.52 Gbits/sec</span><br><span class="line">[  6]   5.00-6.00   sec   181 MBytes  1.52 Gbits/sec</span><br><span class="line">[  6]   6.00-7.00   sec   168 MBytes  1.41 Gbits/sec</span><br><span class="line">[  6]   7.00-8.00   sec   176 MBytes  1.47 Gbits/sec</span><br><span class="line">[  6]   8.00-9.00   sec   185 MBytes  1.55 Gbits/sec</span><br><span class="line">[  6]   9.00-10.00  sec   177 MBytes  1.49 Gbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate         Retr</span><br><span class="line">[  6]   0.00-10.00  sec  1.74 GBytes  1.49 Gbits/sec  122             sender</span><br><span class="line">[  6]   0.00-10.00  sec  1.74 GBytes  1.49 Gbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br></pre></td></tr></table></figure><p>如果换 IP 可以跑满</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">❰xu❙~❱✔≻ iperf3 -c 192.168.31.9 -R                               (base) 12:15:02</span><br><span class="line">Connecting to host 192.168.31.9, port 5201</span><br><span class="line">Reverse mode, remote host 192.168.31.9 is sending</span><br><span class="line">[  5] local 192.168.31.27 port 58702 connected to 192.168.31.9 port 5201</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec   278 MBytes  2.33 Gbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate         Retr</span><br><span class="line">[  5]   0.00-10.00  sec  2.73 GBytes  2.35 Gbits/sec    0             sender</span><br><span class="line">[  5]   0.00-10.00  sec  2.73 GBytes  2.35 Gbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br><span class="line">❰xu❙~❱✔≻ iperf3 -c 192.168.31.9                                  (base) 12:15:20</span><br><span class="line">Connecting to host 192.168.31.9, port 5201</span><br><span class="line">[  5] local 192.168.31.27 port 58824 connected to 192.168.31.9 port 5201</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec   283 MBytes  2.37 Gbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec   279 MBytes  2.34 Gbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-10.00  sec  2.73 GBytes  2.35 Gbits/sec                  sender</span><br><span class="line">[  5]   0.00-10.00  sec  2.73 GBytes  2.35 Gbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br></pre></td></tr></table></figure><h4 id="Macbook-Pro-无线对-懒猫-2-5G-有线"><a href="#Macbook-Pro-无线对-懒猫-2-5G-有线" class="headerlink" title="Macbook Pro 无线对 懒猫 2.5G 有线"></a>Macbook Pro 无线对 懒猫 2.5G 有线</h4><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627195601754.png" alt="image-20250627195601754"></p><h4 id="懒猫无线对-Macbook-Pro-2-5G-有线"><a href="#懒猫无线对-Macbook-Pro-2-5G-有线" class="headerlink" title="懒猫无线对 Macbook Pro 2.5G 有线"></a>懒猫无线对 Macbook Pro 2.5G 有线</h4><p>懒猫的无线网卡是 AX210，这么看这个吞吐量还是不错的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">iperf3 -c &lt;懒猫无线地址&gt;</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec   135 MBytes  1.13 Gbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec   181 MBytes  1.52 Gbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec   190 MBytes  1.59 Gbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec   202 MBytes  1.69 Gbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec   202 MBytes  1.70 Gbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec   206 MBytes  1.72 Gbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec   202 MBytes  1.69 Gbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec   208 MBytes  1.75 Gbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec   205 MBytes  1.72 Gbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec   204 MBytes  1.71 Gbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-10.00  sec  1.89 GBytes  1.62 Gbits/sec                  sender</span><br><span class="line">[  5]   0.00-10.01  sec  1.89 GBytes  1.62 Gbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br><span class="line"></span><br><span class="line"> iperf3 -c &lt;懒猫无线地址&gt;  -R</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec   271 MBytes  2.27 Gbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec   280 MBytes  2.35 Gbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate         Retr</span><br><span class="line">[  5]   0.00-10.00  sec  2.73 GBytes  2.34 Gbits/sec    0             sender</span><br><span class="line">[  5]   0.00-10.00  sec  2.72 GBytes  2.34 Gbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br><span class="line">❰xu❙~❱✔≻</span><br></pre></td></tr></table></figure><h4 id="无线对打"><a href="#无线对打" class="headerlink" title="无线对打"></a>无线对打</h4><p>Macbook Pro 和懒猫微服无线对打(WIFI7)，这个数字显然没有那么好看。咨询了路由器的售后，得到了这几个结论：</p><blockquote><ol><li>民用的 WIFI 都是半双工，所以一个路由器下两个设备对打速度减半</li><li>民用的 WIFI 网卡上传可能有阉割</li><li>即使用 4 收 4 发的路由器无法避免这个问题，因为通道不是独立的</li><li>RX 协商速率偶尔降到 6，可能与 beacon 帧有关（通信的东西不懂，先记下来）</li></ol></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">❰xu❙~❱✘≻ iperf3 -c &lt;无线私有 IP&gt;                                                         (base) 12:45:16</span><br><span class="line"></span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec  74.0 MBytes   619 Mbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec  78.4 MBytes   659 Mbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec  73.8 MBytes   619 Mbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec  69.3 MBytes   582 Mbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec  64.4 MBytes   540 Mbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec  63.2 MBytes   530 Mbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec  68.9 MBytes   578 Mbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec  65.6 MBytes   551 Mbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec  68.8 MBytes   577 Mbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec  63.2 MBytes   530 Mbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-10.00  sec   690 MBytes   578 Mbits/sec                  sender</span><br><span class="line">[  5]   0.00-10.01  sec   689 MBytes   578 Mbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">iperf3 -c &lt;无线私有 IP&gt; -R</span><br><span class="line"></span><br><span class="line">[ ID] Interval           Transfer     Bitrate</span><br><span class="line">[  5]   0.00-1.00   sec  45.2 MBytes   379 Mbits/sec</span><br><span class="line">[  5]   1.00-2.00   sec  46.1 MBytes   387 Mbits/sec</span><br><span class="line">[  5]   2.00-3.00   sec  46.1 MBytes   387 Mbits/sec</span><br><span class="line">[  5]   3.00-4.00   sec  46.0 MBytes   386 Mbits/sec</span><br><span class="line">[  5]   4.00-5.00   sec  45.8 MBytes   384 Mbits/sec</span><br><span class="line">[  5]   5.00-6.00   sec  46.2 MBytes   388 Mbits/sec</span><br><span class="line">[  5]   6.00-7.00   sec  45.8 MBytes   385 Mbits/sec</span><br><span class="line">[  5]   7.00-8.00   sec  45.3 MBytes   380 Mbits/sec</span><br><span class="line">[  5]   8.00-9.00   sec  45.8 MBytes   383 Mbits/sec</span><br><span class="line">[  5]   9.00-10.00  sec  46.0 MBytes   387 Mbits/sec</span><br><span class="line">- - - - - - - - - - - - - - - - - - - - - - - - -</span><br><span class="line">[ ID] Interval           Transfer     Bitrate         Retr</span><br><span class="line">[  5]   0.00-10.01  sec   462 MBytes   387 Mbits/sec    0             sender</span><br><span class="line">[  5]   0.00-10.00  sec   458 MBytes   384 Mbits/sec                  receiver</span><br><span class="line"></span><br><span class="line">iperf Done.</span><br></pre></td></tr></table></figure><h2 id="结尾（总结-推荐）"><a href="#结尾（总结-推荐）" class="headerlink" title="结尾（总结 + 推荐）"></a>结尾（总结 + 推荐）</h2><p>整体折腾下来，我的结论是：</p><ol><li>懒猫微服在域名接入上做了很贴心的优化，省去了公网&#x2F;内网地址切换的麻烦；</li><li>不论是 1G 还是 2.5G 的场景，<strong>有线和无线的表现都相当稳定</strong>，无线网卡（AX210）在合适环境下也能发挥出很不错的吞吐；</li><li>测速中遇到的瓶颈，多数是路由器或测试环境的限制，而不是懒猫本身的问题。</li></ol><p>如果你之前习惯用 NAS 或开发机折腾网络配置，那么上手懒猫微服一定会觉得“省心很多”。它不仅能满足日常访问需求，在高带宽场景下也能撑得住。</p><p>一句话总结：<strong>懒猫微服不仅仅是“内网穿透神器”，更是一台性能靠谱的迷你服务器。</strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;很多朋友在用 NAS 或者家用服务器的时候，都会习惯直接用 &lt;strong&gt;IP 地址&lt;/strong&gt;来访问设备。但当我开始深入折腾 &lt;strong&gt;懒猫微服&lt;/strong&gt;时，发现它的逻辑和传统 NAS 有点不一样：懒猫不是给你一个裸 IP，而是直接分配一个 **域名 &lt;code&gt;&amp;lt;机器名&amp;gt;.heiyu.space&lt;/code&gt;**。&lt;/p&gt;
&lt;p&gt;这就意味着，不管你是在内网还是外网，访问方式都是一致的——直接用域名即可。对习惯了公网&amp;#x2F;私网切换的我来说，这个设计一开始挺新奇，也忍不住想探索背后的机制和性能表现。于是就有了这篇文章：一次围绕 &lt;strong&gt;懒猫微服网络访问和测速表现&lt;/strong&gt;的实战记录。&lt;/p&gt;</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十八）：如何获取懒猫微服的私有地址</title>
    <link href="https://blog.no-claw.com/posts/4c7e3909/"/>
    <id>https://blog.no-claw.com/posts/4c7e3909/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日常使用 <strong>懒猫微服（LazyCat Micro-Service）</strong> 的过程中，我们习惯于使用域名访问。如果你经常使用其他 NAS，就会问这个问题：<strong>如何知道一台机器在局域网里的私有地址？</strong></p><p>很多朋友刚接触 NAS、Docker 或容器化环境时，都会觉得 IP 地址这种东西似乎理所当然。但等你真的要远程访问、配置代理或者排查网络时，就会发现，知道机器的私有地址是一件非常关键的事。</p><p>举几个典型的场景：</p><ul><li>想通过 <strong>SSH、HTTP</strong> 等方式访问运行在懒猫微服上的服务；</li><li>需要进行 <strong>端口转发</strong> 或者把服务 <strong>内网穿透</strong>到公网；</li><li>在 <strong>Dockge</strong> 里启动容器后，访问的时候必须指定 IP 地址，否则反向代理会失败；</li><li>排查网络时，需要确认到底是哪一个 IP 地址在被使用。<span id="more"></span>换句话说，掌握如何获取私有地址，可以来排除一些域名上转发损失的问题。</li></ul><hr><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250928201444075.png" alt="image-20250928201444075"></p><p>虽然在客户端中可以看到网络地址，但是有阵子我修改 br-lan 网桥之后这里就不再显示了。所以才想了个办法来记录网络地址。下面将结合 Python 代码，介绍几种常见的获取私有地址的方法。</p><p>下面我会结合 Python 代码和一些常见的工具，介绍几种获取私有地址的常见做法，并分析它们的优缺点。</p><hr><h2 id="1-为什么是“私有地址”"><a href="#1-为什么是“私有地址”" class="headerlink" title="1. 为什么是“私有地址”"></a>1. 为什么是“私有地址”</h2><p>首先我们要搞清楚：什么是 <strong>私有地址</strong>？</p><p>私有地址是 <strong>RFC1918</strong> 标准定义的局域网网段，常见的有：</p><ul><li><code>10.0.0.0/8</code></li><li><code>172.16.0.0/12</code></li><li><code>192.168.0.0/16</code></li></ul><p>这些 IP 地址不会直接出现在公网，而是专门留给局域网使用。比如：</p><ul><li>你家路由器分配给手机的 <code>192.168.31.2</code>；</li><li>NAS 设备的 <code>192.168.1.103</code>；</li><li>Docker 创建虚拟网络时分配的 <code>172.18.0.2</code>。</li></ul><p>所以，获取懒猫微服的“私有地址”，本质上就是找到它在局域网环境下的真实 IP。只有掌握了这个 IP，才能在排查问题的时候得心应手。</p><hr><h2 id="2-最常用的快速方法：socket-连接法"><a href="#2-最常用的快速方法：socket-连接法" class="headerlink" title="2. 最常用的快速方法：socket 连接法"></a>2. 最常用的快速方法：socket 连接法</h2><p>在 Python 里，用 <code>socket</code> 模块就能很快拿到主要的私有 IP 地址。代码如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_primary_ip</span>():</span><br><span class="line">    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># 不会真的发请求，只是借助路由表选择出网口</span></span><br><span class="line">        s.connect((<span class="string">&#x27;8.8.8.8&#x27;</span>, <span class="number">80</span>))</span><br><span class="line">        ip = s.getsockname()[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">except</span> Exception:</span><br><span class="line">        ip = <span class="string">&#x27;127.0.0.1&#x27;</span></span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        s.close()</span><br><span class="line">    <span class="keyword">return</span> ip</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(get_primary_ip())</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">192.168.1.2</span><br></pre></td></tr></table></figure><p>这就是懒猫微服当前在局域网里的主要出网地址。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250928201025268.png" alt="image-20250928201025268"></p><h3 id="原理说明"><a href="#原理说明" class="headerlink" title="原理说明"></a>原理说明</h3><ul><li>代码里 <code>s.connect((&#39;8.8.8.8&#39;, 80))</code> 看似是去连接 Google 的 DNS，其实并不会真正发起网络请求；</li><li>系统只是会查一遍路由表，决定要走哪个网卡出去；</li><li>然后 <code>getsockname()</code> 返回的就是这个网卡对应的本地 IP。</li></ul><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><ul><li><strong>优点</strong>：跨平台通用，代码简洁，结果非常准确。</li><li><strong>缺点</strong>：依赖于有路由表，如果设备完全没有网络，可能就会返回 <code>127.0.0.1</code>。</li></ul><p>这种方法非常适合：<strong>家用网络 &#x2F; 有默认出网口 &#x2F; 多网卡机器</strong>。</p><hr><h2 id="3-另一种简单写法（但容易踩坑）"><a href="#3-另一种简单写法（但容易踩坑）" class="headerlink" title="3. 另一种简单写法（但容易踩坑）"></a>3. 另一种简单写法（但容易踩坑）</h2><p>很多人还会写成：</p><figure class="highlight python"><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="keyword">import</span> socket</span><br><span class="line"><span class="built_in">print</span>(socket.gethostbyname(socket.gethostname()))</span><br></pre></td></tr></table></figure><p>这段代码通过 <strong>主机名解析</strong> 来获取 IP 地址，看似简单，但问题不少：</p><ul><li>在很多系统里，它直接返回 <code>127.0.0.1</code>；</li><li>依赖 <code>/etc/hosts</code> 或 DNS 配置，如果没配好，就会失效；</li><li>在复杂网络环境下，很可能并不是你真正想要的地址。</li></ul><p>所以，这种方法虽然一行就能搞定，但更适合在 <strong>主机名规范配置过的服务器</strong>，不太推荐在 NAS、容器、虚拟机这些环境里依赖。</p><hr><h2 id="4-列出所有网卡的地址"><a href="#4-列出所有网卡的地址" class="headerlink" title="4. 列出所有网卡的地址"></a>4. 列出所有网卡的地址</h2><p>如果环境比较复杂，比如同时有 <strong>Wi-Fi、以太网、虚拟网卡、容器网桥</strong>，只拿一个主要地址就不够了。这时候就需要直接列出所有接口的 IP，再从中筛选。</p><h3 id="使用-ifaddr"><a href="#使用-ifaddr" class="headerlink" title="使用 ifaddr"></a>使用 <code>ifaddr</code></h3><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">pip install ifaddr --root-user-action=ignore</span><br></pre></td></tr></table></figure><p>代码：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> ifaddr</span><br><span class="line"></span><br><span class="line">adapters = ifaddr.get_adapters()</span><br><span class="line"><span class="keyword">for</span> adapter <span class="keyword">in</span> adapters:</span><br><span class="line">    <span class="keyword">for</span> ip <span class="keyword">in</span> adapter.ips:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;adapter.nice_name&#125;</span>: <span class="subst">&#123;ip.ip&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><p>输出是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">eth0: 192.168.5.203</span><br><span class="line">docker0: 172.17.0.1</span><br><span class="line">lo: 127.0.0.1</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250928201227351.png" alt="image-20250928201227351"></p><hr><h3 id="使用-psutil"><a href="#使用-psutil" class="headerlink" title="使用 psutil"></a>使用 <code>psutil</code></h3><p><code>psutil</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">pip install psutil --root-user-action=ignore</span><br></pre></td></tr></table></figure><p>代码：</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">import</span> psutil, socket</span><br><span class="line"></span><br><span class="line">addrs = psutil.net_if_addrs()</span><br><span class="line"><span class="keyword">for</span> iface, addr_list <span class="keyword">in</span> addrs.items():</span><br><span class="line">    <span class="keyword">for</span> addr <span class="keyword">in</span> addr_list:</span><br><span class="line">        <span class="keyword">if</span> addr.family == socket.AF_INET:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;iface&#125;</span>: <span class="subst">&#123;addr.address&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><p>输出会展示每一个网卡的 IPv4 地址：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250928201319011.png" alt="image-20250928201319011"></p><p>适合在 <strong>虚拟化&#x2F;容器&#x2F;复杂网络环境</strong> 中使用。</p><hr><h2 id="5-如何区分私有地址与公网地址"><a href="#5-如何区分私有地址与公网地址" class="headerlink" title="5. 如何区分私有地址与公网地址"></a>5. 如何区分私有地址与公网地址</h2><p>如果列出来的地址很多，还混杂了公网 IP，怎么办？可以用 <code>ipaddress</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">import</span> ipaddress</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_private</span>(<span class="params">ip</span>):</span><br><span class="line">    <span class="keyword">return</span> ipaddress.ip_address(ip).is_private</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(is_private(<span class="string">&quot;192.168.5.203&quot;</span>))  <span class="comment"># True</span></span><br><span class="line"><span class="built_in">print</span>(is_private(<span class="string">&quot;8.8.8.8&quot;</span>))        <span class="comment"># False</span></span><br></pre></td></tr></table></figure><p>这样就能快速区分哪些是真正的私有地址，避免混淆。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250928202150762.png" alt="image-20250928202150762"></p><hr><h2 id="6-实战场景举例"><a href="#6-实战场景举例" class="headerlink" title="6. 实战场景举例"></a>6. 实战场景举例</h2><p>说了这么多，回到实际应用。获取私有地址到底能帮我们解决什么？</p><ol><li><p><strong>为容器配置反向代理</strong><br>在 Caddyfile 中写：</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">reverse_proxy http://192.168.1.2:11434</span><br></pre></td></tr></table></figure><p>就必须确认 <code>192.168.5.203</code> 真的是懒猫微服的地址。</p></li><li><p><strong>跨设备访问服务</strong><br>想用手机访问 NAS 提供的 Web 服务 <code>http://192.168.1.2:8080</code>，也必须提前知道 IP。</p></li><li><p><strong>排查网络问题</strong><br>当遇到容器内服务报错（403、502）时，确认是不是反向代理到了错误的 IP，就靠这一步。</p></li></ol><hr><h2 id="7-方法对比表"><a href="#7-方法对比表" class="headerlink" title="7. 方法对比表"></a>7. 方法对比表</h2><table><thead><tr><th>方法</th><th>示例代码</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>UDP 连接法</strong></td><td><code>s.connect((&#39;8.8.8.8&#39;, 80))</code></td><td>跨平台、常用、结果准确</td><td>需有路由&#x2F;网络</td><td>获取主要私有地址</td></tr><tr><td><strong>主机名解析</strong></td><td><code>gethostbyname(gethostname())</code></td><td>简单，一行搞定</td><td>常返回 <code>127.0.0.1</code>，依赖配置</td><td>主机名已配置正确</td></tr><tr><td><strong>ifaddr</strong></td><td><code>ifaddr.get_adapters()</code></td><td>列出所有接口，跨平台</td><td>需安装库</td><td>多网卡、容器环境</td></tr><tr><td><strong>psutil</strong></td><td><code>psutil.net_if_addrs()</code></td><td>常见运维库，信息全面</td><td>需安装库</td><td>系统监控、诊断</td></tr><tr><td><strong>ipaddress</strong></td><td><code>ip_address(ip).is_private</code></td><td>能区分公网&#x2F;私网</td><td>需结合其他方法</td><td>过滤私有地址</td></tr></tbody></table><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>获取懒猫微服的私有地址，其实就是获取机器在局域网里的 IP。</p><ul><li><strong>快速场景</strong>：用 <code>socket + 8.8.8.8</code> 方法，几乎百分百靠谱；</li><li><strong>一行写法</strong>：<code>gethostbyname(gethostname())</code>，但要注意经常只会返回 <code>127.0.0.1</code>；</li><li><strong>复杂环境</strong>：用 <code>ifaddr</code> 或 <code>psutil</code> 列出所有接口，再结合 <code>ipaddress</code> 做筛选。</li></ul><p>掌握这些方法，你就能在 <strong>部署服务、调试容器、配置代理</strong> 时快速定位到懒猫微服的真实 IP，避免因为地址不明导致的各种问题。</p><p>以后无论是写 Caddyfile 配置、手机访问 NAS 服务，还是排查 403&#x2F;502 错误，你都能从容地第一时间确认：**我的懒猫微服，到底在哪个 IP 上运行。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在日常使用 &lt;strong&gt;懒猫微服（LazyCat Micro-Service）&lt;/strong&gt; 的过程中，我们习惯于使用域名访问。如果你经常使用其他 NAS，就会问这个问题：&lt;strong&gt;如何知道一台机器在局域网里的私有地址？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多朋友刚接触 NAS、Docker 或容器化环境时，都会觉得 IP 地址这种东西似乎理所当然。但等你真的要远程访问、配置代理或者排查网络时，就会发现，知道机器的私有地址是一件非常关键的事。&lt;/p&gt;
&lt;p&gt;举几个典型的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想通过 &lt;strong&gt;SSH、HTTP&lt;/strong&gt; 等方式访问运行在懒猫微服上的服务；&lt;/li&gt;
&lt;li&gt;需要进行 &lt;strong&gt;端口转发&lt;/strong&gt; 或者把服务 &lt;strong&gt;内网穿透&lt;/strong&gt;到公网；&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;Dockge&lt;/strong&gt; 里启动容器后，访问的时候必须指定 IP 地址，否则反向代理会失败；&lt;/li&gt;
&lt;li&gt;排查网络时，需要确认到底是哪一个 IP 地址在被使用。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三十）：懒猫微服改装闺蜜机，一台设备的多重人生</title>
    <link href="https://blog.no-claw.com/posts/2c05ec4f/"/>
    <id>https://blog.no-claw.com/posts/2c05ec4f/</id>
    <published>2025-09-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在之前那篇文章《<a href="https://mp.weixin.qq.com/s?__biz=MzI3NTY4MjcxNg==&mid=2247486208&idx=1&sn=abce447d30ff2bbcdc3a8a5614210157&chksm=eb004ff3dc77c6e5d214274a7ddc6ca70b511b0b501d53f91748b92a2da56b5b298bb272027e&scene=178&cur_album_id=4010832321455538176&search_click_id=#rd">懒猫微服实战入门（三）：懒猫智慧屏，我以为是地表最强电视盒子，结果竟然可以改装成闺蜜机？</a>》里，我曾经抛出一个看似“不务正业”的想法——<strong>把懒猫微服改造成“闺蜜机”</strong>。</p><p>很多人看到这句话的第一反应是：“这玩意儿不是一个微服务器吗？还能当闺蜜机？”<br>没错，这正是这次改装的灵魂所在。我们要做的，不是再造一台设备，而是赋予它一个新的身份——<strong>一台有颜值、有功能、有灵魂的桌面伴侣</strong>。</p><span id="more"></span><h3 id="从微服务器到桌面灵魂伴侣"><a href="#从微服务器到桌面灵魂伴侣" class="headerlink" title="从微服务器到桌面灵魂伴侣"></a>从微服务器到桌面灵魂伴侣</h3><p>懒猫微服（LazyCat MicroServer），是一台小巧、安静、功耗低却功能极强的家庭服务器。它能跑 Docker、能跑容器、能跑 AI 模型，还能当 NAS、路由器、家庭自动化中枢……</p><p>但每当我看着它那方正的金属外壳，总觉得：这样一台有潜力的“小猫”，如果仅仅蜷缩在角落默默运算，实在太可惜了。</p><p>于是，一个脑洞渐渐浮现：</p><blockquote><p>如果我能让它“抬起头”，变成一台能与我互动的桌面设备，那它是不是可以从“后台工具”变成一个“生活伙伴”？</p></blockquote><p>这便是“闺蜜机”计划的起点。</p><h3 id="开始组装"><a href="#开始组装" class="headerlink" title="开始组装"></a>开始组装</h3><p>在真正开搞之前，我先明确了“闺蜜机”的几个核心需求：</p><ol><li><strong>颜值在线</strong> —— 看着舒服，不是那种硬件工程师风格的“裸奔主机”。</li><li><strong>交互自然</strong> —— 既能远程操作，又能直接触控或观看。</li><li><strong>功能丰富</strong> —— 能看剧、听歌、聊天、展示信息、写点代码。</li><li><strong>安静节能</strong> —— 长期开机无压力。</li></ol><p>于是，我从硬件角度进行了改造规划。</p><h4 id="购买支架：从“盒子”到“屏幕”"><a href="#购买支架：从“盒子”到“屏幕”" class="headerlink" title="购买支架：从“盒子”到“屏幕”"></a>购买支架：从“盒子”到“屏幕”</h4><p>首先要解决的，就是<strong>外观与摆放问题</strong>。</p><p>我在网上找了一个<strong>通用闺蜜机支架</strong>，价格大约 100 元出头。直接就可以把带有 VESA 接口的显示器放上去。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/249a03e50970a048e8ed4395320ebbd9.jpg" alt="249a03e50970a048e8ed4395320ebbd9"></p><h4 id="2-接线：极简而有序"><a href="#2-接线：极简而有序" class="headerlink" title="2. 接线：极简而有序"></a>2. 接线：极简而有序</h4><p>接线部分只有三条主线：</p><ul><li><strong>电源线</strong>：为智慧屏供电；</li><li><strong>HDMI 线</strong>：连接懒猫微服主机输出视频信号；</li><li><strong>USB 线</strong>（可选）：用于连接外设，比如鼠标或者键盘。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/debf6d6f66168e89bacaeac7dd7b5c02-20251005133352113.png" alt="debf6d6f66168e89bacaeac7dd7b5c02"></p><h3 id="软件篇：赋予智慧的灵魂"><a href="#软件篇：赋予智慧的灵魂" class="headerlink" title="软件篇：赋予智慧的灵魂"></a>软件篇：赋予智慧的灵魂</h3><p>硬件准备好之后，就是灵魂的部分：<strong>让懒猫“活起来”。</strong></p><h4 id="启动智慧屏"><a href="#启动智慧屏" class="headerlink" title="启动智慧屏"></a>启动智慧屏</h4><p>开机后，懒猫智慧屏会显示一个简洁的启动界面。<br>等待片刻，你就能看到主界面上已经安装好的各种应用：懒猫相册，懒猫网盘，懒猫导航等等。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251004102309439.png" alt="image-20251004102309439"></p><h4 id="远程访问与控制"><a href="#远程访问与控制" class="headerlink" title="远程访问与控制"></a>远程访问与控制</h4><p>因为没有配备实体遥控器，我采用了<strong>浏览器远程访问</strong>的方式来操作图形界面。<br>只需在电脑上输入懒猫智慧屏的地址，就能看到相同的桌面画面，并进行鼠标和键盘控制。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005131218945.png" alt="image-20251005131218945"></p><p>这一步的意义很大——意味着你可以：</p><ul><li>在笔记本上操作界面；</li><li>同时在闺蜜机上显示播放内容；</li><li>甚至远程帮家人操作他们的智慧屏。</li></ul><p>懒猫系统的远程桌面功能做得相当稳定，延迟极低，堪比本地操作体验。</p><h3 id="娱乐体验：智慧屏不只是个显示器"><a href="#娱乐体验：智慧屏不只是个显示器" class="headerlink" title="娱乐体验：智慧屏不只是个显示器"></a>娱乐体验：智慧屏不只是个显示器</h3><p>比如在 MoonTV 中搜索“陆小凤传奇”，张智霖太帅了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251005131411467.png" alt="image-20251005131411467"></p><p>播放效果如下图所示，很流畅。在那一刻，几乎会忘记这是一台服务器输出的画面。 现在它更像一台高颜值的桌面智能显示器，可以用来看剧、刷视频、听音乐。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251004101332561.png" alt="image-20251004101332561"></p><h3 id="延伸玩法：更多可能"><a href="#延伸玩法：更多可能" class="headerlink" title="延伸玩法：更多可能"></a>延伸玩法：更多可能</h3><p>当然，“闺蜜机”的定义不仅仅是娱乐工具。 它更像一个“个人助理终端”，能陪你工作、陪你放松。懒猫微服+智慧屏的组合还能解锁更多玩法：</p><ul><li><strong>AI 助理终端</strong>：接入语音识别与 ChatGPT API，做成一个真正会说话的桌面 AI。</li><li><strong>智能家居中枢</strong>：接入米家、Home Assistant，实现语音控制全屋设备。</li><li><strong>个人数据中心</strong>：利用 Nextcloud 或 Syncthing 实现文件自动同步。</li><li><strong>远程监控中心</strong>：作为摄像头视频的实时显示终端。</li><li><strong>数字相框模式</strong>：闲置时自动播放相册、日历、天气与提醒。</li></ul><p>从某种意义上说，这台改装“闺蜜机”已经超越了硬件定义，它更像一种新的<strong>桌面生活方式</strong>。</p><p>它是你写代码时的终端，是你放松时的屏幕，是你日常信息流的窗口——<br>更是一个静静陪在你身边、懂你的“小伙伴”。</p><p>当一个小小的懒猫微服，既能陪你写代码，又能陪你看剧、听歌、聊天、思考——它就不再是一台设备，而是一种生活方式的具象化。</p><p>从技术角度看，它展示了<strong>软硬件结合的极致灵活性</strong>；<br>从生活角度看，它代表了一种新的“桌面哲学”：</p><blockquote><p>技术不只是生产力，它也能成为陪伴力。</p></blockquote><p>有时候我们折腾设备，并不是因为它实用，而是因为那份<strong>创造的乐趣</strong>。<br>当一个原本冰冷的硬件，因为你的想法与双手，变得有温度、有灵魂——<br>那一刻，它就成了你的“闺蜜”。</p><p>这次改装只是个开始。</p><p>也许不久之后，“闺蜜机”会成为每个桌面爱好者的新宠——<br>一个能理解你的节奏、陪你共度时光的数字伙伴。</p><p>懒猫微服，从此有了另一种人生。</p>]]></content>
    
    
    <summary type="html">把懒猫微服改装成闺蜜机，一台设备兼顾 NAS、电视盒子和闺蜜机。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十四）：接入 Casdoor，玩转OpenID Connect（OIDC）</title>
    <link href="https://blog.no-claw.com/posts/93c9e4e6/"/>
    <id>https://blog.no-claw.com/posts/93c9e4e6/</id>
    <published>2025-09-19T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的文章中，我们演示了如何基于 <strong>懒猫自带的 OpenID Connect（OIDC）</strong> 来实现身份认证。那属于「平台内置」的简化方案，主要是帮助大家快速理解 OIDC 的基本使用场景。</p><p>这一次，我们换一个更通用、更贴近生产实践的方式：使用应用商店里的 <strong>OpenID Connect（OIDC） Provider —— Casdoor</strong>。Casdoor 是一个开源的统一身份认证平台，支持完整的 OIDC 协议，可以作为独立的 IdP（Identity Provider）对接到任何应用。通过它，我们不仅能跑通最标准的授权码流程，还能深入理解 OIDC 的关键环节：授权跳转、Token 换取、ID Token 验签以及用户信息获取。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919123209327.png" alt="image-20250919123209327"></p> <span id="more"></span><p>我们经常听到 <strong>单点登录（SSO）</strong>、<strong>OAuth2</strong>、<strong>JWT</strong> 这些词。<br>OIDC（OpenID Connect）正是基于 OAuth2 的标准化身份认证协议。它的核心作用是：</p><ul><li>帮助应用确认用户是谁（认证）</li><li>不需要你自己维护密码和用户库（交给 IdP）</li><li>与 OAuth2 完全兼容，可以同时获取访问 API 的能力（授权）</li></ul><p>一个形象的比喻：</p><ul><li>OAuth2 提供的是“门禁卡”功能（你能不能进某个房间）</li><li>OIDC 在此基础上加了“身份证”功能（你是谁）</li></ul><h3 id="OIDC-基本流程"><a href="#OIDC-基本流程" class="headerlink" title="OIDC 基本流程"></a>OIDC 基本流程</h3><p>OIDC 的标准授权码流程（Authorization Code Flow）：</p><ol><li><strong>用户访问应用</strong> → 应用把用户跳转到 IdP 登录页</li><li><strong>用户在 IdP 登录</strong> → IdP 返回一个授权码（code）</li><li><strong>应用后端用授权码换 token</strong>（包括 access_token 和 id_token）</li><li><strong>应用验证 id_token</strong> → 确认用户身份</li><li><strong>可选：调用 userinfo 接口</strong> 获取更详细的用户信息</li></ol><h4 id="进入管理后台"><a href="#进入管理后台" class="headerlink" title="进入管理后台"></a>进入管理后台</h4><ol><li>登录到你的 Casdoor 管理控制台（通常是 <a href="https://casdoor/">https://casdoor</a>.<name>.heiyu.space&#x2F; 或者部署时设定的管理地址）。</li><li>用管理员账号（admin&#x2F;123）进入后台。</li><li>OIDC 应用必须挂在某个 <strong>Organization</strong> 下。</li><li>默认有一个 <code>built-in</code> 组织，可以直接用，也可以新建一个。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919113058204.png" alt="image-20250919113058204"></p><h4 id="创建应用-Application"><a href="#创建应用-Application" class="headerlink" title="创建应用 (Application)"></a>创建应用 (Application)</h4><ol><li><p>在左侧菜单选择 <strong>Application</strong> → 点击 <strong>Add</strong>。</p></li><li><p>填写基本信息：</p><ul><li><strong>Name</strong>：应用名称（例如 <code>my-oidc-app</code>）。</li><li><strong>Display name</strong>：显示名称。</li><li><strong>Organization</strong>：选择上一步的组织。</li><li><strong>Logo</strong>：可选。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919121608631.png" alt="image-20250919121608631"></p></li><li><p>在 <strong>Authentication</strong> 部分：</p><ul><li><p>设置 <strong>Redirect URIs</strong>：</p><ul><li><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">http://localhost:5001/auth/callback</span><br></pre></td></tr></table></figure></li></ul></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919121827433.png" alt="image-20250919121827433"></p></li><li><p>在 <strong>OAuth 授权类型</strong> 部分：勾选 <code>authorization_code</code>（最标准的流程）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122059571.png" alt="image-20250919122059571"></p></li></ol><h4 id="获取-OIDC-参数"><a href="#获取-OIDC-参数" class="headerlink" title="获取 OIDC 参数"></a>获取 OIDC 参数</h4><p>保存后，在应用详情页可以看到：</p><ul><li><strong>Client ID</strong></li><li><strong>Client Secret</strong></li><li><strong>Redirect URI</strong>（你填的）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122121237.png" alt="image-20250919122121237"></p><p>同时，Casdoor 服务提供一个 OIDC Discovery 地址：</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">https://&lt;casdoor-server-domain&gt;/.well-known/openid-configuration</span><br></pre></td></tr></table></figure><p>这个地址返回 JSON，里面包括：</p><ul><li><code>issuer</code></li><li><code>authorization_endpoint</code></li><li><code>token_endpoint</code></li><li><code>userinfo_endpoint</code></li><li><code>jwks_uri</code></li><li><code>end_session_endpoint</code></li></ul><p>这些就是在后端应用里要配置的参数。</p><h4 id="验证配置"><a href="#验证配置" class="headerlink" title="验证配置"></a>验证配置</h4><ol><li><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">https://&lt;casdoor-server-domain&gt;/.well-known/openid-configuration</span><br></pre></td></tr></table></figure><p>如果能看到 JSON，说明 OIDC 服务已开启。</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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;issuer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxxx.heiyu.space&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;authorization_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxxx.heiyu.space/login/oauth/authorize&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxxx.heiyu.space/api/login/oauth/access_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;userinfo_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxxx.heiyu.space/api/userinfo&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;jwks_uri&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxx.heiyu.space/.well-known/jwks&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;introspection_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxx.heiyu.space/api/login/oauth/introspect&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;response_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;code&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;id_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;code token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;code id_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;token id_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;code token id_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;none&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;response_modes_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;query&quot;</span><span class="punctuation">,</span> <span class="string">&quot;fragment&quot;</span><span class="punctuation">,</span> <span class="string">&quot;login&quot;</span><span class="punctuation">,</span> <span class="string">&quot;code&quot;</span><span class="punctuation">,</span> <span class="string">&quot;link&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;grant_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;password&quot;</span><span class="punctuation">,</span> <span class="string">&quot;authorization_code&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;subject_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;public&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id_token_signing_alg_values_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;RS256&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;RS512&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ES256&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ES384&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ES512&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scopes_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;openid&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;email&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;profile&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;address&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;phone&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;offline_access&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;claims_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;iss&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ver&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;sub&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;aud&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;iat&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;exp&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;id&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;type&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;displayName&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;avatar&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;permanentAvatar&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;email&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;phone&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;location&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;affiliation&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;title&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;homepage&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;bio&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;tag&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;region&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;language&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;score&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ranking&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;isOnline&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;isAdmin&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;isForbidden&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;signupApplication&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ldap&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;request_parameter_supported&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;request_object_signing_alg_values_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;HS256&quot;</span><span class="punctuation">,</span> <span class="string">&quot;HS384&quot;</span><span class="punctuation">,</span> <span class="string">&quot;HS512&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;end_session_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://casdoor.xxxxx.heiyu.space/api/logout&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p>用 Postman 或者 oidc-client 测试一下授权流程，看看能不能拿到 <code>code</code>、<code>access_token</code>、<code>id_token</code>。</p></li></ol><p>✅ 至此，Casdoor 端就配置好了。剩下的就是在应用端（RP）写 OIDC 客户端代码</p><h3 id="OpenID-Connect-OIDC-代码"><a href="#OpenID-Connect-OIDC-代码" class="headerlink" title="OpenID Connect(OIDC)代码"></a>OpenID Connect(OIDC)代码</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os, requests, jwt</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> urlencode</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, redirect, request, session, url_for, jsonify, abort</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> jwt <span class="keyword">import</span> PyJWKClient</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------- Load env ----------------</span></span><br><span class="line">load_dotenv()</span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.getenv(<span class="string">&quot;FLASK_SECRET_KEY&quot;</span>, <span class="string">&quot;dev-secret&quot;</span>)</span><br><span class="line"></span><br><span class="line">ISSUER = os.getenv(<span class="string">&quot;OIDC_ISSUER&quot;</span>)</span><br><span class="line">CLIENT_ID = os.getenv(<span class="string">&quot;OIDC_CLIENT_ID&quot;</span>)</span><br><span class="line">CLIENT_SECRET = os.getenv(<span class="string">&quot;OIDC_CLIENT_SECRET&quot;</span>)</span><br><span class="line">REDIRECT_URI = os.getenv(<span class="string">&quot;OIDC_REDIRECT_URI&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------- Discover OIDC endpoints ----------------</span></span><br><span class="line">discovery = requests.get(<span class="string">f&quot;<span class="subst">&#123;ISSUER&#125;</span>/.well-known/openid-configuration&quot;</span>).json()</span><br><span class="line">AUTH_ENDPOINT = discovery[<span class="string">&quot;authorization_endpoint&quot;</span>]</span><br><span class="line">TOKEN_ENDPOINT = discovery[<span class="string">&quot;token_endpoint&quot;</span>]</span><br><span class="line">USERINFO_ENDPOINT = discovery[<span class="string">&quot;userinfo_endpoint&quot;</span>]</span><br><span class="line">JWKS_URI = discovery[<span class="string">&quot;jwks_uri&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------- Routes ----------------</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;user&quot;</span> <span class="keyword">in</span> session:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&quot;Hi, <span class="subst">&#123;session[<span class="string">&#x27;user&#x27;</span>].get(<span class="string">&#x27;name&#x27;</span>) <span class="keyword">or</span> session[<span class="string">&#x27;user&#x27;</span>][<span class="string">&#x27;sub&#x27;</span>]&#125;</span>&lt;br&gt;&lt;a href=&#x27;/logout&#x27;&gt;退出&lt;/a&gt;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&lt;a href=&#x27;/login&#x27;&gt;使用 Casdoor 登录&lt;/a&gt;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/login&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    params = &#123;</span><br><span class="line">        <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">        <span class="string">&quot;response_type&quot;</span>: <span class="string">&quot;code&quot;</span>,</span><br><span class="line">        <span class="string">&quot;scope&quot;</span>: <span class="string">&quot;openid profile email&quot;</span>,</span><br><span class="line">        <span class="string">&quot;redirect_uri&quot;</span>: REDIRECT_URI,</span><br><span class="line">        <span class="string">&quot;state&quot;</span>: <span class="string">&quot;xyz123&quot;</span>,   <span class="comment"># 可以生成随机数并存到 session</span></span><br><span class="line">        <span class="string">&quot;nonce&quot;</span>: <span class="string">&quot;abc456&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">f&quot;<span class="subst">&#123;AUTH_ENDPOINT&#125;</span>?<span class="subst">&#123;urlencode(params)&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/auth/callback&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">callback</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;error&quot;</span> <span class="keyword">in</span> request.args:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&quot;Error: <span class="subst">&#123;request.args[<span class="string">&#x27;error&#x27;</span>]&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">    code = request.args.get(<span class="string">&quot;code&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> code:</span><br><span class="line">        abort(<span class="number">400</span>, <span class="string">&quot;Missing code&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 1. 换取 Token</span></span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&quot;grant_type&quot;</span>: <span class="string">&quot;authorization_code&quot;</span>,</span><br><span class="line">        <span class="string">&quot;code&quot;</span>: code,</span><br><span class="line">        <span class="string">&quot;redirect_uri&quot;</span>: REDIRECT_URI,</span><br><span class="line">        <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">        <span class="string">&quot;client_secret&quot;</span>: CLIENT_SECRET,</span><br><span class="line">    &#125;</span><br><span class="line">    token_resp = requests.post(TOKEN_ENDPOINT, data=data).json()</span><br><span class="line">    id_token = token_resp.get(<span class="string">&quot;id_token&quot;</span>)</span><br><span class="line">    access_token = token_resp.get(<span class="string">&quot;access_token&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 2. 验证并解码 ID Token</span></span><br><span class="line">    jwks_client = PyJWKClient(JWKS_URI)</span><br><span class="line">    signing_key = jwks_client.get_signing_key_from_jwt(id_token)</span><br><span class="line">    claims = jwt.decode(</span><br><span class="line">        id_token,</span><br><span class="line">        signing_key.key,</span><br><span class="line">        algorithms=[<span class="string">&quot;RS256&quot;</span>],</span><br><span class="line">        audience=CLIENT_ID,</span><br><span class="line">        issuer=ISSUER,</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 3. 获取用户信息</span></span><br><span class="line">    userinfo = requests.get(</span><br><span class="line">        USERINFO_ENDPOINT,</span><br><span class="line">        headers=&#123;<span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;Bearer <span class="subst">&#123;access_token&#125;</span>&quot;</span>&#125;</span><br><span class="line">    ).json()</span><br><span class="line"></span><br><span class="line">    session[<span class="string">&quot;user&quot;</span>] = &#123;</span><br><span class="line">        <span class="string">&quot;sub&quot;</span>: claims[<span class="string">&quot;sub&quot;</span>],</span><br><span class="line">        <span class="string">&quot;name&quot;</span>: userinfo.get(<span class="string">&quot;name&quot;</span>, claims.get(<span class="string">&quot;name&quot;</span>)),</span><br><span class="line">        <span class="string">&quot;email&quot;</span>: userinfo.get(<span class="string">&quot;email&quot;</span>, claims.get(<span class="string">&quot;email&quot;</span>)),</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> redirect(url_for(<span class="string">&quot;profile&quot;</span>))</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/profile&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">profile</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;user&quot;</span> <span class="keyword">not</span> <span class="keyword">in</span> session:</span><br><span class="line">        <span class="keyword">return</span> redirect(<span class="string">&quot;/&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> jsonify(session[<span class="string">&quot;user&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/logout&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">logout</span>():</span><br><span class="line">    session.clear()</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">&quot;/&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run(<span class="string">&quot;0.0.0.0&quot;</span>, <span class="number">5001</span>, debug=<span class="literal">True</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这个是.env 的环境变量：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">FLASK_SECRET_KEY=replace-with-a-random-32-bytes-string</span><br><span class="line">OIDC_ISSUER=https://casdoor.name.heiyu.space</span><br><span class="line">OIDC_CLIENT_ID=</span><br><span class="line">OIDC_CLIENT_SECRET=</span><br><span class="line">OIDC_REDIRECT_URI=http://localhost:5001/auth/callback</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>下面是代码解读：</p><p>Flask 应用 &#x3D; OIDC 客户端（RP）；Casdoor &#x3D; 身份提供方（IdP）。</p><ul><li><code>/login</code>：把浏览器重定向到 IdP 的 <strong>授权端点</strong>。</li><li><code>/callback</code>：IdP 回调携带 <code>code</code> → 后端用 <code>code</code>去 <strong>令牌端点</strong> 换 <code>access_token</code> + <code>id_token</code> → 用 <strong>JWKS 公钥</strong>校验 <code>id_token</code> → 用 <code>access_token</code> 拉 <strong>userinfo</strong>。</li><li><code>/profile</code>：展示从 userinfo&#x2F;ID Token 得到的用户信息。</li><li><code>/logout</code>：清空本地会话。</li></ul><h4 id="依赖与配置"><a href="#依赖与配置" class="headerlink" title="依赖与配置"></a>依赖与配置</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os, requests, jwt</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> urlencode</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, redirect, request, session, url_for, jsonify, abort</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> jwt <span class="keyword">import</span> PyJWKClient</span><br></pre></td></tr></table></figure><ul><li><code>requests</code>：调 OIDC 的 HTTP 端点。</li><li><code>PyJWT</code> + <code>cryptography</code>：验证 <code>id_token</code> 的数字签名。</li><li><code>PyJWKClient</code>：根据 JWT 头里的 <code>kid</code>，自动从 <code>jwks_uri</code> 拉对应公钥。</li><li><code>urlencode</code>：把授权请求的参数拼到 URL 上（避免手写字符串拼接出错）。</li></ul><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">load_dotenv()</span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.getenv(<span class="string">&quot;FLASK_SECRET_KEY&quot;</span>, <span class="string">&quot;dev-secret&quot;</span>)</span><br><span class="line"></span><br><span class="line">ISSUER = os.getenv(<span class="string">&quot;OIDC_ISSUER&quot;</span>)</span><br><span class="line">CLIENT_ID = os.getenv(<span class="string">&quot;OIDC_CLIENT_ID&quot;</span>)</span><br><span class="line">CLIENT_SECRET = os.getenv(<span class="string">&quot;OIDC_CLIENT_SECRET&quot;</span>)</span><br><span class="line">REDIRECT_URI = os.getenv(<span class="string">&quot;OIDC_REDIRECT_URI&quot;</span>)</span><br></pre></td></tr></table></figure><ul><li>从 <code>.env</code> 读取 Issuer &#x2F; Client &#x2F; Secret &#x2F; Redirect URI，<strong>和 Casdoor 后台登记的必须完全一致</strong>（协议、域名、端口、路径一字不差）。</li></ul><hr><h4 id="通过-Discovery-自动找端点"><a href="#通过-Discovery-自动找端点" class="headerlink" title="通过 Discovery 自动找端点"></a>通过 Discovery 自动找端点</h4><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></pre></td><td class="code"><pre><span class="line">discovery = requests.get(<span class="string">f&quot;<span class="subst">&#123;ISSUER&#125;</span>/.well-known/openid-configuration&quot;</span>).json()</span><br><span class="line">AUTH_EP       = discovery[<span class="string">&quot;authorization_endpoint&quot;</span>]</span><br><span class="line">TOKEN_EP      = discovery[<span class="string">&quot;token_endpoint&quot;</span>]</span><br><span class="line">USERINFO_EP   = discovery[<span class="string">&quot;userinfo_endpoint&quot;</span>]</span><br><span class="line">JWKS_URI      = discovery[<span class="string">&quot;jwks_uri&quot;</span>]</span><br></pre></td></tr></table></figure><ul><li>OIDC 规范要求 IdP 暴露<strong>发现文档</strong>，里面告诉你：授权端点、令牌端点、用户信息端点、JWKS 公钥地址等。</li><li>这么做的好处：<strong>不写死 URL</strong>，换 IdP&#x2F;升级版本也不怕路径差异。</li></ul><h4 id="首页-登录"><a href="#首页-登录" class="headerlink" title="首页 &amp; 登录"></a>首页 &amp; 登录</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;user&quot;</span> <span class="keyword">in</span> session:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&quot;欢迎 <span class="subst">&#123;session[<span class="string">&#x27;user&#x27;</span>].get(<span class="string">&#x27;name&#x27;</span>) <span class="keyword">or</span> session[<span class="string">&#x27;user&#x27;</span>][<span class="string">&#x27;sub&#x27;</span>]&#125;</span> &lt;br&gt;&lt;a href=&#x27;/logout&#x27;&gt;退出&lt;/a&gt;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&lt;a href=&#x27;/login&#x27;&gt;使用 Casdoor 登录&lt;/a&gt;&quot;</span></span><br></pre></td></tr></table></figure><ul><li>简单展示：有会话就显示用户名，否则给一个“登录”链接。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122557448.png" alt="image-20250919122557448"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122545124.png" alt="image-20250919122545124"></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/login&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    params = &#123;</span><br><span class="line">        <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">        <span class="string">&quot;response_type&quot;</span>: <span class="string">&quot;code&quot;</span>,          <span class="comment"># 标准授权码流程</span></span><br><span class="line">        <span class="string">&quot;scope&quot;</span>: <span class="string">&quot;openid profile email&quot;</span>,  <span class="comment"># 至少要有 openid；加 profile/email 便于拿到名字/邮箱</span></span><br><span class="line">        <span class="string">&quot;redirect_uri&quot;</span>: REDIRECT_URI,</span><br><span class="line">        <span class="string">&quot;state&quot;</span>: <span class="string">&quot;xyz123&quot;</span>,                <span class="comment"># 防 CSRF（下面会给“随机+校验”的升级版）</span></span><br><span class="line">        <span class="string">&quot;nonce&quot;</span>: <span class="string">&quot;abc456&quot;</span>                 <span class="comment"># 防重放（也建议随机+校验）</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">f&quot;<span class="subst">&#123;AUTH_EP&#125;</span>?<span class="subst">&#123;urlencode(params)&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>state</strong>：浏览器去 IdP 再回来时要原样带回；用于确认这真是你发起的请求（防 CSRF）。</li><li><strong>nonce</strong>：IdP 会把它放进 <code>id_token</code>，回调后核对一致（防重放&#x2F;混淆响应）。</li></ul><blockquote><p>Demo 为了短小，先写了固定值。<strong>写文章时要强调：生产必须随机、并在回调里校验</strong>。</p></blockquote><h5 id="回调：换令牌-→-验签-ID-Token-→-拉用户信息"><a href="#回调：换令牌-→-验签-ID-Token-→-拉用户信息" class="headerlink" title="回调：换令牌 → 验签 ID Token → 拉用户信息"></a>回调：换令牌 → 验签 ID Token → 拉用户信息</h5><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="meta">@app.route(<span class="params"><span class="string">&quot;/auth/callback&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">callback</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;error&quot;</span> <span class="keyword">in</span> request.args:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&quot;Error: <span class="subst">&#123;request.args[<span class="string">&#x27;error&#x27;</span>]&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">    code = request.args.get(<span class="string">&quot;code&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> code: abort(<span class="number">400</span>, <span class="string">&quot;Missing code&quot;</span>)</span><br></pre></td></tr></table></figure><ul><li>IdP 会带着 <code>?code=...&amp;state=...</code> 回来。这里先取出 <code>code</code>，并处理错误场景（用户取消授权等）。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1) 用 code 换 token</span></span><br><span class="line">token_resp = requests.post(TOKEN_EP, data=&#123;</span><br><span class="line">    <span class="string">&quot;grant_type&quot;</span>: <span class="string">&quot;authorization_code&quot;</span>,</span><br><span class="line">    <span class="string">&quot;code&quot;</span>: code,</span><br><span class="line">    <span class="string">&quot;redirect_uri&quot;</span>: REDIRECT_URI,</span><br><span class="line">    <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">    <span class="string">&quot;client_secret&quot;</span>: CLIENT_SECRET,</span><br><span class="line">&#125;).json()</span><br><span class="line"></span><br><span class="line">id_token     = token_resp.get(<span class="string">&quot;id_token&quot;</span>)</span><br><span class="line">access_token = token_resp.get(<span class="string">&quot;access_token&quot;</span>)</span><br></pre></td></tr></table></figure><ul><li>这一步是<strong>服务端到 IdP</strong>的 POST。两种常见做法：<ul><li>把 <code>client_id/client_secret</code> 放表单（本例）；</li><li>或用 HTTP Basic（IdP 要求不同，文章里可顺带提一嘴）。</li></ul></li><li>返回里关键是 <code>id_token</code>（JWT，证明“你是谁”）和 <code>access_token</code>（调用 <code>userinfo</code> 的 Bearer Token）。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 2) 验证 id_token（必须做）</span></span><br><span class="line">jwks_client = PyJWKClient(JWKS_URI)</span><br><span class="line">signing_key = jwks_client.get_signing_key_from_jwt(id_token)</span><br><span class="line">claims = jwt.decode(</span><br><span class="line">    id_token,</span><br><span class="line">    signing_key.key,</span><br><span class="line">    algorithms=[<span class="string">&quot;RS256&quot;</span>],      <span class="comment"># Casdoor 也可能配 ES256/ECDSA，按实际配置来</span></span><br><span class="line">    audience=CLIENT_ID,        <span class="comment"># aud 必须包含你的 client_id</span></span><br><span class="line">    issuer=ISSUER,             <span class="comment"># iss 必须等于你的 Issuer</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li><strong>为什么一定要验签？</strong>否则任何人都可以伪造一个 “自称由 IdP 签发”的 JWT。</li><li><code>PyJWKClient</code> 会读 JWT 头部的 <code>kid</code>，去 <code>JWKS_URI</code> 拉这把钥匙的公钥。</li><li>同时校验标准字段：<code>iss/aud/exp/iat/...</code>。如果你还用了 <code>nonce</code>，建议对 <code>claims[&quot;nonce&quot;]</code> 做一致性校验。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 3) 拉取用户信息</span></span><br><span class="line">userinfo = requests.get(</span><br><span class="line">    USERINFO_EP,</span><br><span class="line">    headers=&#123;<span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;Bearer <span class="subst">&#123;access_token&#125;</span>&quot;</span>&#125;</span><br><span class="line">).json()</span><br></pre></td></tr></table></figure><ul><li>不是所有 IdP 都在 <code>id_token</code> 里带全资料，所以通常再拉一次 <code>userinfo</code>。</li><li>这一步需要前面的 <code>access_token</code>。</li></ul><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="comment"># 4) 缩小会话，只存最小字段（避免 Cookie &gt; 4KB）</span></span><br><span class="line">session[<span class="string">&quot;user&quot;</span>] = &#123;</span><br><span class="line">    <span class="string">&quot;sub&quot;</span>: claims[<span class="string">&quot;sub&quot;</span>],                                      <span class="comment"># 唯一ID</span></span><br><span class="line">    <span class="string">&quot;name&quot;</span>: userinfo.get(<span class="string">&quot;name&quot;</span>, claims.get(<span class="string">&quot;name&quot;</span>)),          <span class="comment"># 没有就退回到ID Token</span></span><br><span class="line">    <span class="string">&quot;email&quot;</span>: userinfo.get(<span class="string">&quot;email&quot;</span>, claims.get(<span class="string">&quot;email&quot;</span>)),</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> redirect(url_for(<span class="string">&quot;profile&quot;</span>))</span><br></pre></td></tr></table></figure><ul><li><strong>强烈建议</strong>：只把少量字段放进 session（Flask 默认把 session 放加密 Cookie，4KB 有上限）。不要把整个 token&#x2F;JWT&#x2F;raw 塞进去。</li></ul><h5 id="查看资料-退出"><a href="#查看资料-退出" class="headerlink" title="查看资料 &amp; 退出"></a>查看资料 &amp; 退出</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/profile&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">profile</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;user&quot;</span> <span class="keyword">not</span> <span class="keyword">in</span> session: <span class="keyword">return</span> redirect(<span class="string">&quot;/&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> jsonify(session[<span class="string">&quot;user&quot;</span>])</span><br></pre></td></tr></table></figure><ul><li>只从会话里取“精简后的用户档案”返回给前端。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122524135.png" alt="image-20250919122524135"></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&quot;/logout&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">logout</span>():</span><br><span class="line">    session.clear()</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">&quot;/&quot;</span>)</span><br></pre></td></tr></table></figure><ul><li><p>清本地会话即可。若要<strong>单点登出</strong>，可以再调用 IdP 的 <code>end_session_endpoint</code>（用 Discovery 取到）并带 <code>post_logout_redirect_uri</code>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919122557448.png" alt="image-20250919122557448"></p></li></ul><h3 id="小结：懒猫微服-Casdoor-OIDC"><a href="#小结：懒猫微服-Casdoor-OIDC" class="headerlink" title="小结：懒猫微服 + Casdoor OIDC"></a>小结：懒猫微服 + Casdoor OIDC</h3><p>通过这次实战，我们完成了一个从 Casdoor 配置 到 应用端代码实现 的 OIDC 流程。</p><p>这说明在懒猫微服中，大家不仅可以直接用内置的 OIDC 功能，还可以自由选择商店里的 OIDC Provider（如 Casdoor） 来扩展身份认证能力。</p><p>懒猫微服不仅能用自带的 OIDC，更能灵活调用商店里的 Casdoor 等 Provider，满足更灵活的认证与单点登录需求。</p>]]></content>
    
    
    <summary type="html">在懒猫微服上接入 Casdoor 实现 OIDC 单点登录，统一管理应用认证。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="单点登录" scheme="https://blog.no-claw.com/tags/%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服故事篇（四）：不折腾内网穿透了，懒猫微服一站式全搞定！</title>
    <link href="https://blog.no-claw.com/posts/9806a8cb/"/>
    <id>https://blog.no-claw.com/posts/9806a8cb/</id>
    <published>2025-09-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为一个开发人员，可谓在服务器环境上操碎了心。除了环境搭建，最头疼的就是远程访问与内网穿透的问题。<br>这些年来，我更换了一代又一代的工具与方案，也见证了从“草台班子”到“优雅简洁”的进化过程。</p><p>虽然现在有了 <strong>懒猫微服</strong> 的客户端就能直接解决，只需要在手机或者电脑上安装客户端，就可以无缝连回家里，自动切换内外网，无需额外配置。回头看看这一路的折腾，依然让人感慨良多。决定记录下来。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919194005505.png" alt="image-20250919194005505"></p><span id="more"></span><h2 id="阶段一：初出茅庐-——-TeamViewer-ToDesk"><a href="#阶段一：初出茅庐-——-TeamViewer-ToDesk" class="headerlink" title="阶段一：初出茅庐 —— TeamViewer &#x2F; ToDesk"></a>阶段一：初出茅庐 —— TeamViewer &#x2F; ToDesk</h2><p>最早接触远程桌面，大多数人用的就是 <strong>TeamViewer、ToDesk</strong> 这类软件。</p><p>它们的优点是：安装简单、即开即用，不需要额外配置公网。对于刚入行的新人来说，能在办公室远程操控家里的电脑，那就是神技。</p><p>但是，缺点也很明显：</p><ul><li><strong>只能图形连接</strong>：延迟较大，对命令行和运维操作带来额外开销。</li><li><strong>速度受限</strong>：免费版带宽瓶颈明显，卡顿严重，体验不佳。</li><li><strong>安全性存疑</strong>：所有数据都走第三方服务器，中途链路不透明。</li><li><strong>出海限制</strong>：连接海外设备时经常掉线，稳定性不足。</li></ul><p>可以说，这个阶段只是“远程控制的启蒙”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9aba8b4117fdff3de618f04f4432c37a-20250919202029491.jpg" alt="9aba8b4117fdff3de618f04f4432c37a"></p><h2 id="阶段二：崭露头角-——-花生壳"><a href="#阶段二：崭露头角-——-花生壳" class="headerlink" title="阶段二：崭露头角 —— 花生壳"></a>阶段二：崭露头角 —— 花生壳</h2><p><strong>花生壳</strong> 算是让我第一次接触到了“内网穿透”这个的启蒙开始。<br>他的出现，让我意识到：</p><ul><li>原来可以不用公网 IP</li><li>通过 NAT 穿透技术，也能让外网访问到内网服务</li></ul><p>那时用花生壳搭建博客、Web 服务，甚至把实验室的算力卡、NAS 暴露到外网，已经让人兴奋不已。</p><p>遗憾的是，免费额度有限，带宽不高，延迟非常大，有时候甚至打洞不成功。</p><p>虽然限制不少，但花生壳无疑为内网穿透的发展“埋下了种子”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5e455766446eaa7ddbc32b63e6987263.jpg" alt="5e455766446eaa7ddbc32b63e6987263"></p><h2 id="阶段三：锋芒渐露-——-云服务器搭建-FRP"><a href="#阶段三：锋芒渐露-——-云服务器搭建-FRP" class="headerlink" title="阶段三：锋芒渐露 —— 云服务器搭建 FRP"></a>阶段三：锋芒渐露 —— 云服务器搭建 FRP</h2><p>后来在晚上搜索方案。得知了一个叫做<strong>FRP（Fast Reverse Proxy）</strong>的项目。</p><p>后来我选择在阿里云学生机上自建 FRP，实现公网访问。</p><p>这一阶段的特点是：</p><ul><li><strong>灵活性大幅提升</strong>：可以自定义端口、协议，搭建专属穿透服务。</li><li><strong>更贴近开发者</strong>：可以自由配置内外网服务。</li></ul><p>但问题也随之而来：</p><ul><li>配置文件相对繁琐</li><li>维护成本高，不论是人力还是云服务器的费用是一笔不小的开销。</li><li>阿里云基础服务器带宽很少，经常会造成访问延迟。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/fee02db69cae02dc4c280f8858dc6425.jpg" alt="fee02db69cae02dc4c280f8858dc6425"></p><h2 id="阶段四：乘风破浪-——-蒲公英盒子"><a href="#阶段四：乘风破浪-——-蒲公英盒子" class="headerlink" title="阶段四：乘风破浪 —— 蒲公英盒子"></a>阶段四：乘风破浪 —— 蒲公英盒子</h2><p>一次偶然的网上冲浪，看见 B 站 UP 主推荐蒲公英盒子，于是下单购买了一个。<strong>蒲公英盒子</strong> 把穿透功能做成了“小黑盒”，即插即用，免去复杂配置。</p><p>它的优势是：</p><ul><li><strong>傻瓜化</strong>：插上网线就能用，零门槛。</li><li><strong>稳定性</strong>：设备常驻，后台运维稳定。</li><li><strong>适合小微团队</strong>：旁路由无缝侵入，智能转发流量。</li></ul><p>但它也有局限：</p><ul><li>因为是小黑盒，所以很多技术细节不透明</li><li>穿透的带宽有限制</li><li>售后体验非常不好</li></ul><p>所以最后我弃用了这个方案。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3fcec8eab2a888d13008193fe68d4ef1.png" alt="3fcec8eab2a888d13008193fe68d4ef1"></p><h2 id="阶段五：披荆斩棘-——-公网-IP"><a href="#阶段五：披荆斩棘-——-公网-IP" class="headerlink" title="阶段五：披荆斩棘 —— 公网 IP"></a>阶段五：披荆斩棘 —— 公网 IP</h2><p>联通还能给动态的公网 IP，所以可以在路由器上开端口转发了。</p><p>公网 IP 的优势不言而喻：</p><ul><li><strong>原生直连</strong>，没有中间转发</li><li><strong>速度快</strong>，延迟低</li><li><strong>自由度高</strong>，怎么玩都行</li></ul><p>但问题是：</p><ul><li>家庭宽带公网 IP 封禁常用端口（80&#x2F;8080&#x2F;443）</li><li>运营商偶尔没有缘由的封禁端口</li><li>安全风险极高，直接暴露在公网上，没有任何防火墙和防止 DDNS 的措施</li></ul><p>所以，公网 IP 是一个选择，但是并不是最终的方案。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/74faaefb01808c01ec7d55a44ff260c8-20250919202030313.jpg" alt="74faaefb01808c01ec7d55a44ff260c8"></p><p>某年某月，外出一段时间，于是用树莓派做了一个监控放在家里推流，然后通过 http 暴露在互联网上，但是没过几天就被封端口了。回来检查内网一切正常。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/43553115fdcc7cc6d21dd2bfbd810336.jpg" alt="43553115fdcc7cc6d21dd2bfbd810336"></p><h2 id="终极大法：未来可期-——-懒猫微服"><a href="#终极大法：未来可期-——-懒猫微服" class="headerlink" title="终极大法：未来可期 —— 懒猫微服"></a>终极大法：未来可期 —— 懒猫微服</h2><p>直到朋友给我推荐了 <strong>懒猫微服</strong>，这条漫长的折腾曲线才真正画上句号。<br>它带来了超稳定的内网穿透体验，让我无论身处何地，都能丝滑访问家里的设备。</p><p>硬件级双重验证，彻底杜绝黑客攻击；一线工程师贴心支持，让人不再为复杂配置而焦虑。</p><p>几乎开箱即用，自动识别并切换内外网，真正做到零额外配置。</p><p>下面这张测速图，是我在旅游途中、相距家中千里之外时的真实测得。懒猫微服本身不做任何限速，访问体验就像设备就在身边，再也不用为防火墙、端口映射这些恼人的问题烦恼。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c2a5d7a096883a6febcc18d62dbce8c0-20250919202030452.png" alt="c2a5d7a096883a6febcc18d62dbce8c0"></p><p>我现在的常用使用场景如下：</p><ol><li>通过客户端 APP 直接访问微服网盘或者商店 APP</li><li>通过懒猫转发访问 windows（RDP）</li><li>通过懒猫转发访问 windows（SSH）</li><li>通过懒猫转发访问 KVM over IP（webrtc）</li><li>通过懒猫转发访问其他 NAS（Snology，QNAP）</li><li>通过懒猫做旁路由直接通过内网访问其他设备</li><li>两个安装懒猫微服的 APP 互相访问</li></ol><p>网络入口可以选择不同的颗粒度，微服所在局域网（物理网卡），容器内（虚拟网桥） ，登陆微服的客户端或者特定的客户端，或者包括前面所有的规则。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919200905946-20250919201458599.png" alt="image-20250919200905946"></p><p>出口也可以选择不同的应用，客户端，甚至是没有登陆微服的客户端，只要网络可达，都可以做流量转发，实属一个居家必备的网关。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250919200920549-20250919202030956.png" alt="image-20250919200920549"></p><h3 id="为什么它是终极解法？"><a href="#为什么它是终极解法？" class="headerlink" title="为什么它是终极解法？"></a>为什么它是终极解法？</h3><p>它几乎集合了之前所有阶段的优点：</p><ul><li><strong>免公网 IP</strong>：自动穿透，省钱省心</li><li><strong>速度稳定</strong>：直接走互联网，延迟低</li><li><strong>安全加固</strong>：硬件双重验证，杜绝黑客攻击</li><li><strong>全场景覆盖</strong>：桌面、SSH、数据库、NAS、开发环境一个客户端搞定</li><li><strong>即插即用</strong>：媲美蒲公英盒子，但更灵活开放</li></ul><p>对我来说：</p><ul><li>不再为端口映射、防火墙折腾</li><li>不再为公网 IP 掏冤枉钱</li><li>不再担心安全风险</li></ul><p>只需一个懒猫微服，就能把 <strong>远程运维、开发调试、家庭娱乐</strong> 全部串联起来。</p><p>从 <strong>TeamViewer → 花生壳 → FRP → 蒲公英 → 公网 IP</strong>，<br>这是我的的远程访问修炼史，也是一路的折腾与妥协。</p><p>如今，<strong>懒猫微服</strong>成了真正的终结者。远程访问不再是负担，而是一件轻松、省心的事。</p><p>未来可期，组网无忧。</p>]]></content>
    
    
    <summary type="html">告别复杂的内网穿透配置，用懒猫微服一站式解决远程访问需求</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="故事" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%95%85%E4%BA%8B/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>一根网线搞定远程运维，GL-RM1PE 深度体验：远程运维、装机、开机一体化的 KVM over IP</title>
    <link href="https://blog.no-claw.com/posts/63cca403/"/>
    <id>https://blog.no-claw.com/posts/63cca403/</id>
    <published>2025-09-15T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>一直以来，我都很喜欢 Gl.iNet 的产品。它们通常设计简洁、功能实用，既能满足极客和开发者的需求，又能兼顾家庭用户的体验。无论是路由器还是 KVM 远程管理器，Gl.iNet 在“小而美”的产品线上一直有稳定发挥。</p><p>这并不是一台路由器或者 NAS，而是一款 <strong>KVM over IP 设备</strong>。在传统企业机房里，这类设备往往被用来实现服务器的远程管理：即便服务器关机、系统崩溃，管理员依然可以通过远程方式查看屏幕、控制键鼠，甚至重装系统。过去，这样的功能往往属于 <strong>Dell iDRAC、HP iLO、Supermicro IPMI</strong> 等服务器管理模块的专属领域。但现在，Gl.iNet 把它做成了一台独立设备，价格和使用门槛也被大大降低，让普通运维人员和 Homelab 爱好者也能体验到“企业级远程管理”的能力。</p><p>换句话说，如果你想远程操作一台服务器，哪怕它彻底关机、哪怕系统崩溃，甚至你要给它重装系统，这个小小的设备都能帮你完成。这类设备过去通常是企业机房里的“高端选配”，但现在，Gl.iNet 把它带到了大众可用的价位。</p><h3 id="背景与定位：KVM-over-IP-为什么重要？"><a href="#背景与定位：KVM-over-IP-为什么重要？" class="headerlink" title="背景与定位：KVM over IP 为什么重要？"></a>背景与定位：KVM over IP 为什么重要？</h3><p>在进入具体体验之前，我们先谈一谈 <strong>为什么 KVM over IP 设备重要</strong>。</p><p>传统的远程管理方式，大体可以分为两类：</p><ul><li><strong>软件级远程控制</strong>：比如 VNC、XRDP、TeamViewer、AnyDesk。这类方案依赖目标系统正常启动、网络正常可用，才能实现远程桌面。如果系统崩溃或者驱动异常，远程就失效了。</li><li><strong>硬件级远程管理</strong>：比如服务器厂商自带的 iDRAC、iLO，或者独立的 KVM over IP。它们直接劫持视频信号和输入设备，不依赖系统和驱动，即便设备黑屏、关机，也能操作。</li></ul><p>这就好比一台电脑出故障，软件方案只能“看得到桌面才管用”，而硬件方案则能“从按电源键那一刻就开始管理”。对于机房里的运维人员，这几乎是救命的功能。</p><p>过去个人用户或者中小企业要实现这种能力，往往需要购买昂贵的服务器管理卡。但如今，Gl.iNet 通过 GL-RM1PE 让这种能力变得可负担，也让 Homelab 用户有机会体验到企业级的远程管理。</p><h3 id="产品简介：KVM-PoE-极简方案"><a href="#产品简介：KVM-PoE-极简方案" class="headerlink" title="产品简介：KVM + PoE &#x3D; 极简方案"></a>产品简介：KVM + PoE &#x3D; 极简方案</h3><p><strong>GL-RM1PE</strong> 的设计理念用一句话概括就是：<strong>一根网线，搞定一切</strong>。</p><p>它把三件事情整合到了一根网线上：</p><ol><li><strong>供电</strong>：支持 PoE（Power over Ethernet），无需额外电源适配器。</li><li><strong>网络</strong>：千兆网口即插即用，自动联网。</li><li><strong>远程控制</strong>：完整的 KVM 功能，覆盖键盘、视频、鼠标。</li></ol><p>这种“单线极简”的设计，在机房和家庭实验室环境中非常有价值。机房里本来布线就复杂，减少一根电源线就意味着更高的整洁度和更低的维护成本。在家里，少一根插座电源适配器，也能让桌面和机柜更干净。</p><p>接口布局方面，设备正面依次是：</p><ul><li><strong>USB 2.0 接口</strong>：用于连接手指机器人或其他外设</li><li><strong>HDMI 接口</strong>：采集被控设备的输出</li><li><strong>USB 控制接口</strong>：模拟键盘鼠标输入</li><li><strong>RJ45 网口</strong>：千兆接口，支持 PoE</li></ul><p>侧面还有一个 <strong>Type-C 供电口</strong>，在没有 PoE 交换机的情况下，可以用外接电源。整体来看，它就像是在任何 PC&#x2F;服务器上外挂了一个“远程管理模块”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bbf8ece3f1310f19028b2bd8c9ae700d.jpg" alt="bbf8ece3f1310f19028b2bd8c9ae700d"></p><h3 id="手指机器人：物理开机的最后一环"><a href="#手指机器人：物理开机的最后一环" class="headerlink" title="手指机器人：物理开机的最后一环"></a>手指机器人：物理开机的最后一环</h3><p>KVM 能解决大部分远程管理问题，但仍有一个关键问题：<strong>如果设备彻底关机怎么办？</strong><br>这就是官方配套的 <strong>手指机器人</strong> 登场的地方。</p><p>顾名思义，它就是一个能模拟“手指按电源键”的小机械装置。</p><p>它的设计要点是：</p><ul><li><strong>模拟物理按键</strong>：通过小机械臂实际按下电源键</li><li><strong>粘贴式安装</strong>：无需拆机或改造，直接贴在电源键上方</li><li><strong>CR2 电池供电</strong>：续航超过一年，电池可更换</li><li><strong>USB 蓝牙接收器</strong>：插在 KVM 设备上，与手指机器人配对</li></ul><p>这样，即便远程主机彻底关机，你依然可以通过 KVM 下达指令，让手指机器人去“按电源键”。</p><p>我亲测的体验是：在管理后台端点击按钮，机柜里的手指机器人立刻按下动作，把服务器开机了。这种感觉很神奇，就像你拥有了一只可以随时帮你“按开机键”的远程手。对于远程运维人员来说，这意味着再也不用担心“系统挂掉后没人帮忙按电源键”的尴尬。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916205357846.png" alt="image-20250916205357846"></p><p>从外观来看，产品正面有 GL.iNet 的 logo 和 LED 指示灯，铝制外壳配合银灰色表面处理，质感相当不错，放在机柜里也很协调。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2b0fb87cb199bc2d550a151ed6723597.jpg" alt="2b0fb87cb199bc2d550a151ed6723597"></p><p>在机柜里的实际使用效果如下：</p><p>接口从左到右依次是：</p><ul><li><strong>USB 2.0 接口</strong>：连接手指机器人</li><li><strong>HDMI 接口</strong>：采集被控设备输出</li><li><strong>USB 控制接口</strong>：模拟键盘和鼠标</li><li><strong>RJ45 网口</strong>：支持 POE</li></ul><p>侧面还预留了 Type-C 供电口，如果你没有 POE 交换机，可以用外接电源。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/temp_image_for_default_share%202.png" alt="temp_image_for_default_share 2"></p><p>拆开手指机器人的外壳，可以看到一个 USB 接收器和一颗 CR2 电池。电池可替换，维护成本不高。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916140910102.png" alt="image-20250916140910102"></p><h3 id="网络与接入：mDNS-HTTPS-的细节优化"><a href="#网络与接入：mDNS-HTTPS-的细节优化" class="headerlink" title="网络与接入：mDNS + HTTPS 的细节优化"></a>网络与接入：mDNS + HTTPS 的细节优化</h3><p>我家里的网络架构是中兴晴天的 <strong>AC+AP，完全使用 POE 供电</strong>，因此 GL-RM1PE 可以直接插在 AC 上使用（装修时预留了网口）。</p><p>接上 POE 之后，设备自动上线，可以通过路由器后台查看 IP。更方便的是，它支持 mDNS，直接访问：</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">https://glkvm.local/#/</span><br></pre></td></tr></table></figure><p>就能进入后台管理页面。即使你访问的是 80 端口，那么也会被重定向到 https 的 443 端口，所以这样访问也可以。</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">glkvm.local</span><br></pre></td></tr></table></figure><p>还可以通过 <code>ping</code> 查找设备 IP：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ping glkvm.local</span><br><span class="line">PING glkvm.local (192.168.x.x): 56 data bytes</span><br><span class="line">64 bytes from 192.168.x.x: icmp_seq=0 ttl=64 time=4.539 ms</span><br><span class="line">64 bytes from 192.168.x.x: icmp_seq=1 ttl=64 time=4.180 ms</span><br><span class="line">64 bytes from 192.168.x.x: icmp_seq=2 ttl=64 time=4.200 ms</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916135528389.png" alt="image-20250916135528389"></p><h3 id="内网体验：比-VNC-更流畅的-WebRTC"><a href="#内网体验：比-VNC-更流畅的-WebRTC" class="headerlink" title="内网体验：比 VNC 更流畅的 WebRTC"></a>内网体验：比 VNC 更流畅的 WebRTC</h3><p>Web 管理界面是它的主要入口。</p><ul><li>初次访问需设置管理员密码</li><li>后续输入密码即可直连，无需复杂认证流程</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916134522564.png" alt="image-20250916134522564"></p><p>在内网环境下，即便是 KDE 桌面这种对图形要求比较高的环境，WebRTC 的画面流畅度和延迟表现也明显优于 VNC 和 XRDP。以前用 VNC，常常有卡顿和延迟，键盘输入延时明显；而在 GL-RM1PE 上，鼠标和键盘几乎是“秒响应”，就像本地操作一样。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916142937202.png" alt="image-20250916142937202"></p><p>它不仅解决了延迟问题，还提供了专业的视频设置：</p><ul><li>支持 <strong>H.264 编解码</strong></li><li>最高可达 <strong>4K 输出</strong></li><li>支持分辨率 EDID 设置和画面旋转</li></ul><p>无论是普通显示器还是特殊比例的屏幕，都能轻松适配。</p><p>声音和输入设备也做得很细致：</p><ul><li><strong>扬声器和麦克风</strong>：可远程传输音频，延迟很低</li><li><strong>键盘</strong>：支持虚拟键盘输入，触控友好</li><li><strong>鼠标</strong>：支持光标显示、防休眠抖动、滚轮速率调整、正反滚动切换</li></ul><p>此外，它还内置了 <strong>剪切板共享、快捷键模拟（如 Ctrl+Alt+Del）、Wake-on-LAN、内置终端</strong> 等功能。这些都大幅提升了远程操作的便利性，让体验接近“本地化”。</p><p>特别的是，KVM 直接采集 HDMI 输出，即便设备锁屏或休眠，也能远程操作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/03f14c2276db4a510881219f2045963a.png" alt="03f14c2276db4a510881219f2045963a"></p><h3 id="虚拟媒体与-ISO-挂载：远程装机神器"><a href="#虚拟媒体与-ISO-挂载：远程装机神器" class="headerlink" title="虚拟媒体与 ISO 挂载：远程装机神器"></a>虚拟媒体与 ISO 挂载：远程装机神器</h3><p>远程运维里，最棘手的情况之一是 <strong>系统需要重装</strong>。如果没有人现场插入 U 盘或者光盘，基本无法操作。而 GL-RM1PE 提供的 <strong>虚拟媒体挂载</strong> 功能，几乎完美解决了这一问题。</p><p>这个非常实用的功能是 <strong>虚拟媒体挂载</strong>。GL-RM1PE 提供的 <strong>虚拟媒体挂载</strong> 功能简直是神器。GL-RM1PE 内置 <strong>32G eMMC</strong>，可以存放主流操作系统的 ISO 镜像。在 Web 界面即可上传 ISO，然后将其作为虚拟光驱挂载。重装系统、文件传输都非常方便。</p><p>对于远程运维人员来说，这个功能意味着：<strong>你可以在千里之外，帮一台死机的服务器重装系统，而不需要有人在现场插 U 盘</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4d917cd84f2db3ea31052ed51adceef5.png" alt="4d917cd84f2db3ea31052ed51adceef5"></p><p>我在 EndeavourOS x86_64 上成功挂载后，能够看到 PopOS 镜像已经挂载。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916165613721.png" alt="image-20250916165613721"></p><p>设备提供了两种方式：</p><ol><li><strong>文件挂载</strong>：将 eMMC 当作光驱，从系统内部传输文件。</li><li><strong>镜像挂载</strong>：把 ISO 当作系统盘引导，用于安装新系统。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/49241b8bd223f8ac8cbf6e3465b2e3fa.png" alt="49241b8bd223f8ac8cbf6e3465b2e3fa"></p><p>这个是在 BIOS 中读到的引导项，Gl.iNet Flash Drive 就是这个 GL-RM1PE 的镜像引导，和使用 CD 或者刻录的 U 旁随身碟完全一致。选择 Gl.iNet 的引导之后就进入了我们熟悉的装机环节。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/517a26f8a31ddf99ee9cd9e5f5b41480.png" alt="517a26f8a31ddf99ee9cd9e5f5b41480"></p><h3 id="云服务集成与公网访问：在哪里都能访问"><a href="#云服务集成与公网访问：在哪里都能访问" class="headerlink" title="云服务集成与公网访问：在哪里都能访问"></a>云服务集成与公网访问：在哪里都能访问</h3><p>本地用得好，但公网场景怎么办？</p><p>Gl.iNet 直接在产品里送了一个 <strong>终身免费的内网穿透服务</strong>。</p><ul><li><p>电脑端可用 PC 客户端</p></li><li><p>手机&#x2F;iPad 可直接访问 <a href="https://www.glkvm.cn/">https://www.glkvm.cn</a></p></li></ul><p>这个网址来访问。即使在公网环境下，也能远程访问设备。</p><p>因为官方提供终身了内网穿透的功能，所以这是一个买硬件送穿透服务的。</p><p>这等于硬件买了之后，官方帮你免去了搭建反向代理、DDNS 的折腾，性价比更高。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916134157405.png" alt="image-20250916134157405"></p><p>使用云服务的话，需要注册账户，然后每次需要登录这个账号。如果你有特殊的安全需要，还可以设置二次验证的 MFA。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916135134242.png" alt="image-20250916135134242"></p><p>设备绑定完成后会显示在列表中，点击远程控制时需重新输入密码，以确保远程管理过程中的安全可靠。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916135325485.png" alt="image-20250916135325485"></p><p>更强的是，它还能接入 <strong>Tailscale</strong>，加入私有组网，这样能够更加方便的进行异地组网。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7d9f4078e64547fde1c274a859702cea.png" alt="7d9f4078e64547fde1c274a859702cea"></p><p>如果你之前使用过 Tailscale 的话，那么只需要点点鼠标就能轻松加入之前的组里面。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5c9ac0d91f81abfb714f415423f16ac4.png" alt="5c9ac0d91f81abfb714f415423f16ac4"></p><p>我把 Macbook，群晖和 GL-RM1PE 放在了一个虚拟局域网里面，这样即使没有公网 IP 也能直接互联。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916180627672.png" alt="image-20250916180627672"></p><h3 id="隐藏玩法：小型-Linux-主机"><a href="#隐藏玩法：小型-Linux-主机" class="headerlink" title="隐藏玩法：小型 Linux 主机"></a>隐藏玩法：小型 Linux 主机</h3><p>体验过程中我发现，GL-RM1PE 本身就是一台小型 Linux 机器：</p><ul><li><strong>CPU</strong>：4 核</li><li><strong>内存</strong>：1G</li><li><strong>存储</strong>：32G eMMC</li><li><strong>环境</strong>：BusyBox</li></ul><p>GLKVM 同时还支持终端访问，甚至可以看到是 BusyBox 的环境，这个对于开发者十分友好，甚至还可以 ssh 到内网的 Linux 机器。而且我们也能够看到，有线网卡的协商速率是 1000Mbps。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c227c496b0e532f4bcb62cc399b81a3d.png" alt="c227c496b0e532f4bcb62cc399b81a3d"></p><p>在 <code>htop</code> 里能看到完整的 4C1G 配置，用来跑 WebRTC 推流完全足够。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916150048453.png" alt="image-20250916150048453"></p><p>机器提供了 32G 的 eMMC,挂载在 <code>/userdata/media</code>，能存放系统 ISO 或作为文件服务器使用。</p><p>也就是说可以把这个硬件当作一台小型 Linux 服务器来使用，比如执行<code>python -m http.server</code>可以开启一个文件服务器，当然，BusyBox 环境不支持包管理和 Docker，但作为应急环境、调试环境，已经非常有价值。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250916164804135.png" alt="image-20250916164804135"></p><h3 id="竞品与行业对比：和-iDRAC、iLO、TeamViewer-的区别"><a href="#竞品与行业对比：和-iDRAC、iLO、TeamViewer-的区别" class="headerlink" title="竞品与行业对比：和 iDRAC、iLO、TeamViewer 的区别"></a>竞品与行业对比：和 iDRAC、iLO、TeamViewer 的区别</h3><p>如果把 GL-RM1PE 放到整个远程管理市场中，它的定位非常独特。</p><ul><li><strong>相比软件远程（VNC、XRDP、TeamViewer、AnyDesk）</strong>：<ul><li>优点：不依赖操作系统，哪怕系统崩溃也能操作；延迟更低。</li><li>缺点：需要额外硬件。</li></ul></li><li><strong>相比服务器管理卡（Dell iDRAC、HP iLO、Supermicro IPMI）</strong>：<ul><li>优点：价格低得多，不绑定服务器厂商，通用性强。</li><li>缺点：功能上略简单，比如缺少企业级的监控和批量管理功能。</li></ul></li></ul><p>对于个人和中小企业来说，GL-RM1PE 正好填补了中间空白：比软件方案更可靠，比企业级方案更便宜。</p><h3 id="总结：远程运维的“全能工具箱”"><a href="#总结：远程运维的“全能工具箱”" class="headerlink" title="总结：远程运维的“全能工具箱”"></a>总结：远程运维的“全能工具箱”</h3><p>体验下来，我认为 <strong>GL-RM1PE + 手指机器人</strong> 是一套完整的远程管理解决方案。它把过去属于企业级服务器的功能，带到了个人和中小企业可用的价位。</p><p>优势总结：</p><ul><li>一根网线搞定供电 + 网络</li><li>WebRTC 推流低延迟，优于 VNC&#x2F;XRDP</li><li>内置 32G eMMC，支持虚拟媒体挂载，远程装机更轻松</li><li>手指机器人解决远程物理开机痛点</li><li>内网直连 + 官方云服务双保险</li><li>支持 Tailscale，扩展性强</li><li>自身就是一台小型 Linux 主机，调试灵活</li></ul><p>无论是 <strong>IT 运维工程师</strong>，还是喜欢折腾的 <strong>开发者与 Homelab 爱好者</strong>，这套组合都非常值得一试。它不是冰冷的“机房专属”设备，而是把远程管理做到了极客友好、开箱即用。同时也期待 WIFI 版本的 KVM 远程控制器早日上市，这样就更加方便了。</p>]]></content>
    
    
    <summary type="html">GL-RM1PE KVM over IP 深度体验，一根网线实现远程运维与装机</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
    <category term="KVM" scheme="https://blog.no-claw.com/tags/KVM/"/>
    
    <category term="POE" scheme="https://blog.no-claw.com/tags/POE/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服故事篇（三）：朋友拿着一台没公网IP的服务器跑来找我，，问我能不能转发？</title>
    <link href="https://blog.no-claw.com/posts/52d0811e/"/>
    <id>https://blog.no-claw.com/posts/52d0811e/</id>
    <published>2025-09-13T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>前几天，一个朋友来找我。他说自己新买了一台云服务器，却发现厂商没有分配公网 IP。最关键的是，他已经在上面部署好了服务，还急着要对外共享。于是他问我：</p><p><strong>“这台没有公网 IP 的云服务器，还有办法让别人访问吗？”</strong></p><span id="more"></span><p>这其实是很多人都会遇到的情况：</p><ul><li>有些云厂商默认不给公网 IP，只能通过内网访问。</li><li>公网 IP 要额外收费，而且价格往往不低。</li><li>部分地区公网 IP 甚至直接被封禁。</li><li>轻量机、学生机这类实验主机，干脆就不提供公网 IP。</li></ul><p>那么，有没有办法“曲线救国”？<br>答案是：当然有。靠的就是 <strong>懒猫微服的端口转发功能</strong>。</p><p>它能让一台没有公网 IP 的云服务器，重新获得对外访问的能力，甚至还能跨懒猫账号共享。</p><p>我把这种玩法类比于 HTTPS 的证书卸载，称之为 <strong>“登录卸载”</strong>。</p><h3 id="1-解决思路"><a href="#1-解决思路" class="headerlink" title="1. 解决思路"></a>1. 解决思路</h3><p>朋友的云服务器上跑着一个 Web 服务，但因为没有公网 IP，只能在内网访问。</p><p>我的解决方案是：</p><ol><li>在这台云服务器上安装懒猫微服客户端；</li><li>找一台有公网 IP 的懒猫微服节点，作为“出口”；</li><li>把云服务器的服务端口转发到出口节点上。</li></ol><p>这样一来，即便服务器本身只有内网地址，也能借助另一台机器的公网出口，对外提供服务。</p><p>此时，只需要在这台服务器上安装懒猫微服客户端，然后记住它的机器名称。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914145004949-20250914150209748-20250914150928398.png" alt="image-20250914145004949"></p><h3 id="2-设置端口转发"><a href="#2-设置端口转发" class="headerlink" title="2. 设置端口转发"></a>2. 设置端口转发</h3><p>出于数据安全的考虑，我给他单独开了一个子账号。</p><p>因为当转发目标选择“微服客户端”时，只能获取到<strong>登录账号下的设备</strong>。这也意味着我无法直接读取他机器的域名，更不能帮他添加转发规则，所以需要让他自己登录来配置。</p><p>在懒猫微服的控制台里，他只需要新建一个端口转发规则：</p><ul><li><strong>转发类型</strong>：选择 <strong>“微服客户端”</strong>，对应的就是那台没有公网 IP 的云服务器；</li><li><strong>端口</strong>：填写要转发的服务端口（比如 8080）；</li><li><strong>出口类型</strong>：选择 <strong>0.0.0.0</strong>，绑定到有公网 IP 的微服网卡。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914144506948-20250914150210117-20250914150928669.png" alt="image-20250914144506948"></p><p>完成后，这台云服务器的服务就顺利“挂”在了公网出口上。</p><h3 id="3-转发测试"><a href="#3-转发测试" class="headerlink" title="3. 转发测试"></a>3. 转发测试</h3><p>配置好后，我先在内网直接用 <code>telnet</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">telnet a.b.c.d &lt;port&gt;</span><br><span class="line">Trying x.x.x.x...</span><br><span class="line">Connected to x.x.x.x.</span><br><span class="line">Escape character is <span class="string">&#x27;^]&#x27;</span>.</span><br></pre></td></tr></table></figure><p>可以看到，不登录的情况下直接访问懒猫的私有 IP 就能通。</p><p>我自己家里有公网 IP，于是只需要在路由器上再做一个转发，就能让这台云服务器的服务真正跑到公网去。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914152637908.png" alt="image-20250914152637908"></p><p>当他看到成功连上之后，才彻底放心：<br><strong>原来真的能让一台没有公网 IP 的云服务器，也能被公网访问！</strong></p><p>为了更方便，我还配合了 <strong>DDNS-go</strong> 使用。这样即便出口 IP 改变，也能通过域名自动解析到最新 IP，保证随时可访问。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914152508948.png" alt="image-20250914152508948"></p><h3 id="4-最终效果"><a href="#4-最终效果" class="headerlink" title="4. 最终效果"></a>4. 最终效果</h3><p>经过这套操作，这台“没有公网 IP 的云服务器”，就像普通云主机一样，可以被外网直接访问了。</p><p>关键是：全程不需要购买昂贵的公网 IP，配置好之后也不必频繁登录管理，真正做到一次设置，长期使用。</p><p>现在网站的流量数据已经很好看了：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914152406792.png" alt="image-20250914152406792"></p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ul><li><strong>传统做法</strong>：必须购买公网 IP → 成本高、限制多；</li><li><strong>懒猫微服做法</strong>：通过端口转发 → 云服务器无需公网 IP，也能轻松对外服务；</li><li><strong>额外优势</strong>：支持跨懒猫账号共享，配置好之后，朋友也能直接访问。</li></ul><p>一句话总结：<br><strong>有了懒猫微服 + DDNS-go，再“抠门”的云服务器，也能摇身一变，获得公网入口。</strong></p>]]></content>
    
    
    <summary type="html">用懒猫微服帮朋友解决没有公网 IP 的服务器端口转发问题</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="故事" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%95%85%E4%BA%8B/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>群晖如何配置 Server 酱 Webhook 事件通知？</title>
    <link href="https://blog.no-claw.com/posts/e70dd7d7/"/>
    <id>https://blog.no-claw.com/posts/e70dd7d7/</id>
    <published>2025-09-13T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>网上冲浪的时候看到给群晖配置 server 酱通知的帖子，不过看着比较老旧了。所以自己探索了一下，顺便更新一下教程。也省着下次被停电还得翻日志才能知道。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cd9625571e708b7d07967c4489126c15.jpg" alt="cd9625571e708b7d07967c4489126c15"></p> <span id="more"></span><h3 id="1-开启-Webhook-通知"><a href="#1-开启-Webhook-通知" class="headerlink" title="1. 开启 Webhook 通知"></a>1. 开启 Webhook 通知</h3><p>进入 <strong>控制面板 → 通知设置</strong>，在通知服务里选择 <strong>Webhook</strong>。这样群晖就能通过 Webhook 推送消息到 Server 酱。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c28f57266317bfea595f0a082f267102-20250914183816053.png" alt="c28f57266317bfea595f0a082f267102"></p><h3 id="2-配置-Webhook-提供商"><a href="#2-配置-Webhook-提供商" class="headerlink" title="2. 配置 Webhook 提供商"></a>2. 配置 Webhook 提供商</h3><p>选择 <strong>自定义提供商</strong>，规则我这里选择了 <strong>监听全部</strong>，这样所有通知都会转发。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914182910650.png" alt="image-20250914182910650"></p><p>这里的“提供商”和“主题”随便写，自己能认出来就行。真正关键的是 <strong>URL 和请求体</strong>。</p><p>Server 酱的接口地址是：</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">https://sctapi.ftqq.com/&lt;key&gt;.send</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">text=%40%40TEXT%40%40</span><br></pre></td></tr></table></figure><p>这里的 <code>@@TEXT@@</code> 就是一个占位符，表示群晖实际推送消息时会自动替换成通知内容。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/eee5ed6c1a3a38a0bb93e06a49015e66.png" alt="eee5ed6c1a3a38a0bb93e06a49015e66"></p><h3 id="3-修改请求体"><a href="#3-修改请求体" class="headerlink" title="3. 修改请求体"></a>3. 修改请求体</h3><p>这里是 webhook 的重头戏，所谓 webhook 其实就是 app 里预留了一个 POST API，规定好请求体，然后给用户自由发消息的权利。server 酱接受 title 和 desp 两个字段，无论是 parms 或者 body 都接受。</p><p>所以我在群晖 Webhook 的请求体里添加了对应的 JSON：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914182156202.png" alt="image-20250914182156202"></p><h3 id="4-验证效果"><a href="#4-验证效果" class="headerlink" title="4. 验证效果"></a>4. 验证效果</h3><p>配置完成后，随便触发一条系统通知，就能看到 Server 酱成功收到了推送：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914183645826.png" alt="image-20250914183645826"></p><h3 id="5-小结"><a href="#5-小结" class="headerlink" title="5. 小结"></a>5. 小结</h3><p>整个配置思路其实很简单：</p><ol><li>群晖支持 Webhook 通知；</li><li>Server 酱提供了一个接收消息的 API；</li><li>我们只需要把群晖的通知格式改成 Server 酱要求的字段，就能打通。</li></ol><p>这样一来，无论是停电、磁盘告警，还是系统更新提醒，群晖的消息都能第一时间通过 Server 酱推送到手机上。</p>]]></content>
    
    
    <summary type="html">群晖 DSM 配置 Server 酱 Webhook，实现系统事件微信推送通知。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十五）：有了懒猫微服之后，我用 Vaultwarden 平替了 1Password订阅，附送迁移方案</title>
    <link href="https://blog.no-claw.com/posts/f46ed8e7/"/>
    <id>https://blog.no-claw.com/posts/f46ed8e7/</id>
    <published>2025-09-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前用的 1Password 会员快到期了，价格也不便宜。正好在懒猫微服商店看到了 Vaultwarden，就顺势换成了这个“平替”。</p><p>相信很多人听过 Bitwarden。而 <strong>Vaultwarden</strong> 是社区开发的 <strong>Bitwarden 服务端的轻量级实现</strong>，最初叫 bitwarden_rs，后来改名为 Vaultwarden。它用 Rust 编写，资源占用小，非常适合个人用户或者轻量自建场景（树莓派、低配 VPS 等）。它是社区实现的轻量替代版，完全兼容 Bitwarden 的官方客户端，部署成本低，特别适合个人或小团队自建。</p><span id="more"></span><p>在懒猫商店可以直接下载这个应用，安装和使用指南可以参考社区的帖子：<a href="https://lazycat.cloud/playground/guideline/489">玩机攻略:一个开源的密码管理器 Vaultwarden</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250910212133032.png" alt="image-20250910212133032"></p><h3 id="从-1Password-迁移到-Vaultwarden"><a href="#从-1Password-迁移到-Vaultwarden" class="headerlink" title="从 1Password 迁移到 Vaultwarden"></a>从 1Password 迁移到 Vaultwarden</h3><p>我在 Bitwarden 的 Wiki 中找到了一份迁移指南：先从 1Password 导出数据，再在 Vaultwarden&#x2F;Bitwarden 中完成导入。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/54a1b64d1bd3ad9a6d4a6b732b3e2ab0.png" alt="54a1b64d1bd3ad9a6d4a6b732b3e2ab0"></p><p>在 1Password 中导出：文件 -&gt; 导出 -&gt; 项目名字。我这里选择导出 1PUX 格式的文件，存到本地。这样之前保存的密码、SSH Key 等信息都可以完整导出。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/02c48f7d830178e91fa279781e4d7706.png" alt="02c48f7d830178e91fa279781e4d7706"></p><p>然后在 Vaultwarden 网页端导入刚才的 1PUX 文件（需要提前新建一个文件夹）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b8f1e2c12763a073dd86d4dda81be6cc.png" alt="b8f1e2c12763a073dd86d4dda81be6cc"></p><p>操作很简单，导入后数据就都同步过来了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/952f35379701a54ca65baa4017735103.png" alt="952f35379701a54ca65baa4017735103"></p><h3 id="客户端体验"><a href="#客户端体验" class="headerlink" title="客户端体验"></a>客户端体验</h3><p>如果习惯使用客户端，也可以直接在 App Store 下载桌面端。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b966acec27e5696a3129bb209fd86869.png" alt="b966acec27e5696a3129bb209fd86869"></p><p>除了 MacOS，Vaultwarden 同样支持 Windows 和 Linux。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/311468d69514f2eeafce67972754f04a.png" alt="311468d69514f2eeafce67972754f04a"></p><p>在客户端的最下面可以切换服务器地址，我这里填的是：<a href="https://vaultwarden.name.heiyu.space/%E3%80%82">https://vaultwarden.name.heiyu.space/。</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4da0edd328dba11e840521d7db6b6a98.png"></p><p>这样在 APP 里就能直接看到刚刚导入的数据，不需要再手动复制粘贴。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bd4e8aeab8a07847fc7a206e0f56e0ba.png" alt="bd4e8aeab8a07847fc7a206e0f56e0ba"></p><h3 id="浏览器插件"><a href="#浏览器插件" class="headerlink" title="浏览器插件"></a>浏览器插件</h3><p>除了 APP，还可以使用浏览器插件，Vaultwarden 支持了大多数主流浏览器。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/167944375459d4d66c56df40d0ff3fd2.png" alt="167944375459d4d66c56df40d0ff3fd2"></p><p>我平时用 Chrome 最多，所以直接在 Chrome 商店下载了插件。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e03a62f507d4dc885f786a93d9fca1b0.png" alt="e03a62f507d4dc885f786a93d9fca1b0"></p><p>和桌面客户端一样，需要先设置好自己部署的服务器地址。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/aac98d84a80e062b58f6148fcf422f33.png" alt="aac98d84a80e062b58f6148fcf422f33"></p><p>登录流程和桌面端一致。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/caa659cb3482e34cded425905394f589.png" alt="caa659cb3482e34cded425905394f589"></p><h4 id="自动填充与使用体验"><a href="#自动填充与使用体验" class="headerlink" title="自动填充与使用体验"></a>自动填充与使用体验</h4><p>我在设置里打开了自动填充功能，这样日常使用方便了不少。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1e399c43c98fba6f3a8729bff106cea8.png" alt="1e399c43c98fba6f3a8729bff106cea8"></p><p>例如登录路由器后台时，Vaultwarden 就能自动填充密码，点击登录即可。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/57404d0fc28408777bfeff503fde0c84.png" alt="57404d0fc28408777bfeff503fde0c84"></p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>整体体验下来，Vaultwarden 已经完全替代了我之前的 1Password：功能齐全、部署轻量，还能节省一笔订阅费用。对于个人或小团队来说，确实是一个非常值得的平替方案。</p>]]></content>
    
    
    <summary type="html">用 Vaultwarden 自托管密码管理器替代 1Password 订阅，含完整迁移方案。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="Vaultwarden" scheme="https://blog.no-claw.com/tags/Vaultwarden/"/>
    
    <category term="1Password" scheme="https://blog.no-claw.com/tags/1Password/"/>
    
  </entry>
  
  <entry>
    <title>早上起来，Chrome 白屏了……</title>
    <link href="https://blog.no-claw.com/posts/a3db028b/"/>
    <id>https://blog.no-claw.com/posts/a3db028b/</id>
    <published>2025-09-07T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天一早打开电脑，习惯性点开 Chrome，结果迎面而来的是一片空白。<br>不是网络卡顿的那种，而是整个浏览器彻底“黑化”：</p><ul><li>网页打不开</li><li>设置页一片白</li><li>就连 F12 的开发者工具也完全空白</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c207a233d51a7dc81f1a54c15230b678.png" alt="c207a233d51a7dc81f1a54c15230b678"></p><p>当时整个人都愣住了，Chrome 好像一夜之间“失魂落魄”。</p><h3 id="怀疑的方向"><a href="#怀疑的方向" class="headerlink" title="怀疑的方向"></a>怀疑的方向</h3><p>重启浏览器、重启电脑都没用。只好先切到 Safari 搜了下，结合我前几天的操作，很快锁定了问题。</p><p>几天前为了玩《植物大战僵尸 2》网页版，我调整过 <strong>Chrome 的 ANGLE 渲染引擎</strong>。没想到新版 Chrome 对这个配置并不兼容，直接导致渲染器初始化失败，从而整机白屏。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ee3e914b38cd4dbe36d1acee7f7dd4ab.png" alt="ee3e914b38cd4dbe36d1acee7f7dd4ab"></p><h3 id="临时解救办法"><a href="#临时解救办法" class="headerlink" title="临时解救办法"></a>临时解救办法</h3><p>既然问题出在 GPU 渲染，那就先让 Chrome 绕开 GPU，加速器禁用掉再说。<br>在 macOS 上，可以用命令行启动：</p><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">open -a <span class="string">&quot;Google Chrome&quot;</span> --args --disable-gpu</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">/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-gpu</span><br></pre></td></tr></table></figure><p>这样 Chrome 会强制禁用 GPU，页面就能正常显示了。</p><h3 id="根治步骤"><a href="#根治步骤" class="headerlink" title="根治步骤"></a>根治步骤</h3><p>进入浏览器后，把图形渲染器改回默认模式，再重启 Chrome，一切恢复正常。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250913105536049.png" alt="image-20250913105536049"></p>]]></content>
    
    
    <summary type="html">Chrome 浏览器突然白屏的排查与修复过程记录</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="软件" scheme="https://blog.no-claw.com/tags/%E8%BD%AF%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>米其林 * 黑珍珠 ：金陵饭店 - 梅苑 用餐体验</title>
    <link href="https://blog.no-claw.com/posts/c6f5f74f/"/>
    <id>https://blog.no-claw.com/posts/c6f5f74f/</id>
    <published>2025-09-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>去南京玩的时候，找酒店老板推荐一些正宗的淮扬菜。由于小厨娘在北京都吃过了，所以他给我推荐了三家本地的黑珍珠。金陵饭店坐落于繁华的新街口，地铁口出去很明显就能看到，据说梅苑一直是南京人心中的白月光。现在也是我的淮扬菜白月光了。</p><span id="more"></span><p>总体来说，梅苑对一人食很友好，很多菜可以点半份，也只收半份的钱，相对来说算是比较轻奢。但是，想想疫情那阵子馋了四季民福的烤鸭，半只也要 150 元（还吃不饱）。那么梅苑的性价比一定比这个要高。</p><p>首先门口摆放的就是米其林和黑珍珠的认证，首先这个仪式感就拉满了。没想到第一次黑珍珠给了南京。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/70753600a25a0c8319f2a9ef59110e1a.jpg" alt="70753600a25a0c8319f2a9ef59110e1a"></p><p>整体装修偏古风，可能也是老店的原因吧。并不像是现在一些网红店很华丽的装修。总体是重剑无锋的路线，用菜品说话。东西是好东西，都说他家不怎么做营销。虽然我也反对那种给鸭子听音乐喂牛奶式的广告。金陵饭店目前靠口口相传也不错了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ec6c42fb4b443eb34048686c50746dca.jpg" alt="ec6c42fb4b443eb34048686c50746dca"></p><p>第一天点的半份金陵烤鸭，没想到在南京这个菜属于凉菜。在北京经常吃小厨娘的烤鸭，现在想来有些不正宗了。因为它确实是烤制，然后放在砂锅里。属于在朱棣之后有改良了一次做法。而我还照猫画虎学着美食博主老高给饭店建议 – 这个热菜一定要把容器加热，这样吃到最后才不会有腥味。。。虽然饭店照做了，但是从南京回来，感觉它那个在分歧的路上越走越远了。倒是梅苑这种蘸着调味汁的方式更加好吃。别看它分量小，但是很扎实。这一顿还吃得饱。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/acfd81bc51da13d6fef761f43b483489.jpg" alt="acfd81bc51da13d6fef761f43b483489"></p><p>鸭血粉丝汤，这一碗 39 元。这肯定是真的鸭血了。能少见的感觉到对鲜血的渴望。豆腐泡和鸭肠这种配菜都很新鲜，粉丝也入口即化。相比之下，不管北京的还是夫子庙的小厨娘都差了很多。江苏这边的鸭血粉丝汤都是大碗的，和北京的小碗不同，所以甚至感觉黑珍珠的价格甚至要比北京的很多商场性价比要高一些，食材更新鲜一些，烹饪手法更好一些。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7f8f04e21bb8ff9b36520a00609a6d0a.jpg" alt="7f8f04e21bb8ff9b36520a00609a6d0a"></p><p>第一顿整体的样貌，然后被本地人吐槽没吃到精髓。丢美食博主的脸了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5ec7aaf7cecfa4ad0d58f01b6d4f3c82.jpg" alt="5ec7aaf7cecfa4ad0d58f01b6d4f3c82"></p><p>白色的公筷上写着金陵饭店。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/354c102879d647d067d2b97b60419b82.jpg" alt="354c102879d647d067d2b97b60419b82"></p><p>隔了一天去二刷饭店。都说盐水鸭是精髓，所以特意点了一份，也带了一份在冯记买的盐水鸭进行对比。虽然说在冯记买的已经很好吃了，但还要说一说区别。首先从鸭肉的品质上讲，梅苑要更胜一筹，但冯记也很好，这绝不是四季民福和地毯烤鸭的区别，而是一个肉质紧实而有弹性，一个香腻而软烂。这似乎在印证没有一个鸭子能够飞出南京的玩笑。</p><p>盐水鸭用的应该是几十年的卤水，然后就很容易联想到李碧华《吃卤水鹅的女人》里面的桥段。越是老卤则味道越浓厚。</p><blockquote><p>那是一大桶半人高，浸淫过数十万只鹅，乌黑泛亮香浓无比的卤汁。面层铺着一块薄薄的油布似的，保护那四十七年的岁月。它天天不断吸收鹅肉精髓，循环再生，天天比昨日更鲜更浓更香，煮了又煮，卤了又卤，熬了又熬，从未更换改变。这是一大桶“心血”。</p></blockquote><p>不过一个人去吃确实有些咸，只能不断的喝水，吃饭，然后慢慢吃不下其他东西。虽然看着不多，最后还是勉强吃完。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d711ea2e25bb06edc94d76abb7f60b48.jpg" alt="d711ea2e25bb06edc94d76abb7f60b48"></p><p>东坡肉是我在任何饭店必点的一道菜。其中最让我惊艳的是北京清水亭的黄州东坡肉。清水亭在我心中的地位比湖北当地很多必吃榜还要好吃。而梅苑的东坡肉又是另外一种滋味，五花三层，香而不腻，尤其这过程中还慢慢用小火去煨肉，肉皮的软烂和肥瘦融为一体。也算弥补了在杭州吃东坡肉的遗憾吧。而在北京小厨娘的东坡肉或者红烧肉性价比堪比和平饭店。所以这就是没有感觉到黑珍珠很贵很贵的原因吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3b90748dab277e0555bedcb086f74f94.jpg" alt="3b90748dab277e0555bedcb086f74f94"></p><p>看着墙上这一堆荣誉，感觉没白来吧。或者说，来都来了，干脆吃好一点。哦对，米饭是免费吃的。真的很友好了。下次要带着朋友一起去吃松鼠桂鱼。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e258ebaf35087ed2ebc015d2575771f7.jpg" alt="e258ebaf35087ed2ebc015d2575771f7"></p>]]></content>
    
    
    <summary type="html">南京金陵饭店梅苑米其林黑珍珠淮扬菜用餐体验</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="吃货风云" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%90%83%E8%B4%A7%E9%A3%8E%E4%BA%91/"/>
    
    
  </entry>
  
  <entry>
    <title>今天起，不用下载飞书也能用飞书多维表格了！</title>
    <link href="https://blog.no-claw.com/posts/c8aadcda/"/>
    <id>https://blog.no-claw.com/posts/c8aadcda/</id>
    <published>2025-08-29T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="今天起，不用下载飞书也能用飞书多维表格了！"><a href="#今天起，不用下载飞书也能用飞书多维表格了！" class="headerlink" title="今天起，不用下载飞书也能用飞书多维表格了！"></a>今天起，不用下载飞书也能用飞书多维表格了！</h1><p>在效率工具圈子里，飞书多维表格一直被公认是“隐藏神器”。<br>但尴尬的是，它原本只能在飞书 App 里用。</p><p>多少人因为要下载飞书、注册账号，被拦在门外：</p><ul><li>自由职业者：我就做个客户表，还得多装一个 App？</li><li>小团队：光解释注册流程就得半天，协作热情瞬间熄灭。</li><li>合作方：我们用企微&#x2F;钉钉，不想折腾飞书账号。</li></ul><p>所以，飞书多维表格虽然口碑好，却始终没能“全民普及”。</p> <span id="more"></span><p>但今天，它终于等来了历史性的一刻：</p><p>✨ <strong>飞书多维表格支持“单飞”！</strong><br>👉 不需要下载飞书<br>👉 不需要注册账号<br>👉 打开链接就能直接用</p><p>入口直达：<a href="https://v2ig.cn/srs2Zi5kVY8/">点击免费体验飞书多维表格</a></p><h3 id="01-为什么这件事值得大书特书？"><a href="#01-为什么这件事值得大书特书？" class="headerlink" title="01 为什么这件事值得大书特书？"></a>01 为什么这件事值得大书特书？</h3><p>先别小看这一步。<br>这意味着：飞书多维表格从一个“附属功能”，变成了<strong>任何人都能直接用的独立生产力工具</strong>。</p><p>更重要的是：它还能和企微、钉钉，甚至企业自研 IM 系统打通，真正做到跨平台无缝协作。</p><p>对个人，这是门槛的消失；<br>对企业，这是数字化转型的加速器。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/08/28/1756393572606-f6c2bb80-df0a-41ee-b63d-8f53d6073693.png"></p><h3 id="02-先聊聊“传统表格”的痛点"><a href="#02-先聊聊“传统表格”的痛点" class="headerlink" title="02 先聊聊“传统表格”的痛点"></a>02 先聊聊“传统表格”的痛点</h3><p>在它之前，我们用的茶传统表格有以下弊端。</p><ol><li>功能强大，但协作差，跨端体验也不够丝滑。</li><li>协作好，但在国内体验不算稳定。</li><li>轻量好用，但多维度管理和数据库能力有限。<br>4..颜值高、功能强，但对新手来说学习曲线很陡。</li></ol><p>结果就是：要么“太重”，要么“太浅”。</p><p>飞书多维表格的出现，正好填补了这个空白：<br><strong>它既轻量，又专业；既能满足个人使用，又能承载企业核心业务。</strong></p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/08/28/1756393550748-534f3d61-e4f6-4f76-a600-45ae6d47ea47.png"></p><h3 id="03-用下来，我最喜欢的四个爽点"><a href="#03-用下来，我最喜欢的四个爽点" class="headerlink" title="03 用下来，我最喜欢的四个爽点"></a>03 用下来，我最喜欢的四个爽点</h3><h6 id="1-多视图切换-一张表多种玩法"><a href="#1-多视图切换-一张表多种玩法" class="headerlink" title="1. 多视图切换 &#x3D; 一张表多种玩法"></a>1. 多视图切换 &#x3D; 一张表多种玩法</h6><ul><li>表格视图：像 Excel 一样，直观展示数据。</li><li>看板视图：任务流转一目了然。</li><li>日历视图：做内容排期简直完美。</li><li>甘特图视图：项目进度秒变可视化。</li><li>画廊视图：适合作品集、商品库。</li></ul><p>一句话：一份数据，N 种角度看。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2025/08/28/1756393565102-6dd47c34-82cc-4912-bcf8-a4f4758be87c.png"></p><h6 id="2-字段关联-表格进化成数据库"><a href="#2-字段关联-表格进化成数据库" class="headerlink" title="2. 字段关联 &#x3D; 表格进化成数据库"></a>2. 字段关联 &#x3D; 表格进化成数据库</h6><p>以前客户和订单分开记，现在只需两张表，一键关联：</p><ul><li>点击客户 → 自动列出他的所有订单</li><li>点击订单 → 跳转查看客户信息</li></ul><p>这不就是<strong>轻量版 CRM</strong>吗？</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/08/28/1756393712209-3a580666-a6a2-4a1e-bbc0-b2aec4ead2b5.png"></p><h6 id="3-自动化-表格能自己跑"><a href="#3-自动化-表格能自己跑" class="headerlink" title="3. 自动化 &#x3D; 表格能自己跑"></a>3. 自动化 &#x3D; 表格能自己跑</h6><p>场景示例：</p><ul><li>当任务改成“已完成” → 自动提醒负责人</li><li>到期时间临近 → 自动发送通知</li><li>新建记录 → 自动打标签</li></ul><p>省掉了大量重复劳动。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/08/28/1756393893411-2df4304d-45f7-48af-94fd-12803babbf58.png"></p><h6 id="4-AI-融入-不会公式也能飞"><a href="#4-AI-融入-不会公式也能飞" class="headerlink" title="4. AI 融入 &#x3D; 不会公式也能飞"></a>4. AI 融入 &#x3D; 不会公式也能飞</h6><p>飞书多维表格自带 AI：</p><ul><li>自动生成公式</li><li>一键数据分析</li><li>仪表盘智能解读</li></ul><p>我曾经花一小时写的统计函数，它直接帮我秒写好。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/08/28/1756393996382-3824d36e-29bb-49f5-801b-95d80ab46783.png"></p><h3 id="04-五大核心优势，结合场景更好理解"><a href="#04-五大核心优势，结合场景更好理解" class="headerlink" title="04 五大核心优势，结合场景更好理解"></a>04 五大核心优势，结合场景更好理解</h3><ol><li><p><strong>满血功能</strong>：不用下载飞书，也能享受百万行、AI、权限、仪表盘。<br>→ 中小企业可以低成本搭建业务系统。</p></li><li><p><strong>AI 融入</strong>：会用表格就会用 AI。<br>→ 比如销售日报，让 AI 自动总结“今天卖得最好的是哪款”。</p></li><li><p><strong>BI 驾驶舱</strong>：拖数据进去就能看。<br>→ 管理层只要打开仪表盘，就能看清业务趋势。</p></li><li><p><strong>数据库底座</strong>：稳载复杂业务。<br>→ 核心系统不怕数据爆量，性能跟得上。</p></li><li><p><strong>零代码搭建</strong>：业务人员直接动手。<br>→ 财务自己搭报销系统，运营自己做会员系统，IT 不再背锅。</p></li></ol><h3 id="05-对个人用户的价值"><a href="#05-对个人用户的价值" class="headerlink" title="05 对个人用户的价值"></a>05 对个人用户的价值</h3><p>别以为这是企业专属。对个人和小团队同样好用：</p><ul><li><strong>自由职业者</strong>：客户管理 + 项目进度，一表搞定。</li><li><strong>内容创作者</strong>：内容日历 + 数据看板，清晰可见。</li><li><strong>学生&#x2F;社团</strong>：活动排期 + 任务分配，协作顺滑。</li><li><strong>效率党</strong>：习惯打卡 + 知识管理，轻巧高效。</li></ul><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/08/28/1756393688238-fd94a71b-8706-4ca8-99a7-78c0f60f7972.png"></p><h3 id="06-为什么要现在就上车？"><a href="#06-为什么要现在就上车？" class="headerlink" title="06 为什么要现在就上车？"></a>06 为什么要现在就上车？</h3><p>飞书多维表格的“单飞”，本质上是让生产力工具走向大众化。<br>以前是“飞书用户的福利”，现在是真正的<strong>全民工具</strong>。</p><ul><li>个人：0 成本体验顶级生产力工具</li><li>小团队：低门槛协作，不怕外部伙伴掉链子</li><li>企业：跨平台、零代码、可扩展，正好赶上数字化转型潮</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>今天的飞书多维表格，已经不是一个“辅助表格工具”。<br>它是一张能动、能算、能协作的表。</p><ul><li>不需要下载飞书</li><li>不需要注册账号</li><li><strong>一张链接，就能开启全新的效率方式</strong></li></ul><p>👉 <a href="https://v2ig.cn/srs2Zi5kVY8/">立即免费体验飞书多维表格</a><br>👉 官网入口：<a href="https://base.feishu.cn/">base.feishu.cn</a></p><p>📌 建议收藏这篇文章：<br>下次你要做项目排期、客户管理、内容日历、习惯打卡时，直接打开飞书多维表格。<br>你会发现——<strong>表格还能这样玩！</strong></p>]]></content>
    
    
    <summary type="html">飞书多维表格独立使用指南，无需下载飞书 App 也能体验这款效率神器。</summary>
    
    
    
    <category term="硬广" scheme="https://blog.no-claw.com/categories/%E7%A1%AC%E5%B9%BF/"/>
    
    
  </entry>
  
  <entry>
    <title>不建 Hugo、不用 Hexo，纯 Markdown 文件也能接入 Coco-AI！</title>
    <link href="https://blog.no-claw.com/posts/df454c34/"/>
    <id>https://blog.no-claw.com/posts/df454c34/</id>
    <published>2025-08-27T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="容器运行-Coco-AI，如何访问宿主机的-localhost？"><a href="#容器运行-Coco-AI，如何访问宿主机的-localhost？" class="headerlink" title="容器运行 Coco AI，如何访问宿主机的 localhost？"></a>容器运行 Coco AI，如何访问宿主机的 localhost？</h1><p>使用容器确实方便了很多事情，但在网络访问上可能会引出一些麻烦。<br>如果你的调试服务只监听在宿主机的 <code>localhost</code>，那么在容器里访问时，会找的是<strong>容器自己的 localhost</strong>，所以无法连通。</p><p>因为无论是 Coco server 还是 Console 都是服务端发送请求，所以我统一记录下来。</p><p>下面介绍几种在不同环境下的解决方案。</p><h2 id="1-Mac-的-Orbstack"><a href="#1-Mac-的-Orbstack" class="headerlink" title="1. Mac 的 Orbstack"></a>1. Mac 的 Orbstack</h2><p>在 <strong>Orbstack</strong> 环境中，可以使用 <code>host.docker.internal</code> 代替宿主机的 <code>localhost</code>。<br>例如访问宿主机的 Hexo 服务（<code>http://localhost:4000/atom.xml</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">http://host.docker.internal:4000/atom.xml</span><br></pre></td></tr></table></figure><span id="more"></span><p><code>host.docker.internal</code> 会被解析到宿主机 IP，相当于容器内部的 “localhost”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cf47a2848159ca48875d50f232716fe2.png" alt="访问示意图"></p><h2 id="2-Linux-下单容器运行"><a href="#2-Linux-下单容器运行" class="headerlink" title="2. Linux 下单容器运行"></a>2. Linux 下单容器运行</h2><p>在 Linux 环境中，<code>host.docker.internal</code> 默认可能不可用，可以用以下方法：</p><h3 id="方法-1：-add-host"><a href="#方法-1：-add-host" class="headerlink" title="方法 1：--add-host"></a>方法 1：<code>--add-host</code></h3><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">docker run --add-host=host.docker.internal:host-gateway ...</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://host.docker.internal:4000/atom.xml</span><br></pre></td></tr></table></figure><h3 id="方法-2：-network-host"><a href="#方法-2：-network-host" class="headerlink" title="方法 2：--network host"></a>方法 2：<code>--network host</code></h3><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">docker run --network host ...</span><br></pre></td></tr></table></figure><p>这样容器里的 <code>localhost:4000</code> 就等于宿主机的 <code>localhost:4000</code>。<br>⚠️ 缺点：端口可能冲突，不建议在生产环境使用。</p><h2 id="3-Docker-Compose-下多容器访问宿主机"><a href="#3-Docker-Compose-下多容器访问宿主机" class="headerlink" title="3. Docker Compose 下多容器访问宿主机"></a>3. Docker Compose 下多容器访问宿主机</h2><p>在 <strong>Linux + docker-compose</strong> 场景下，容器访问宿主机的 <code>localhost</code> 同样需要绕过。可以使用以下几种方式（推荐优先使用前两种）：</p><h3 id="方案-1：host-docker-internal"><a href="#方案-1：host-docker-internal" class="headerlink" title="方案 1：host.docker.internal"></a>方案 1：<code>host.docker.internal</code></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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">myservice:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">your-image</span></span><br><span class="line">    <span class="attr">extra_hosts:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;host.docker.internal:host-gateway&quot;</span></span><br></pre></td></tr></table></figure><p>容器里访问：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://host.docker.internal:4000/atom.xml</span><br></pre></td></tr></table></figure><h3 id="方案-2：Docker-网桥网关-IP"><a href="#方案-2：Docker-网桥网关-IP" class="headerlink" title="方案 2：Docker 网桥网关 IP"></a>方案 2：Docker 网桥网关 IP</h3><p>Linux 默认 <code>docker0</code> 网桥的宿主机 IP 通常是 <code>172.17.0.1</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">ip addr show docker0</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://172.17.0.1:4000/atom.xml</span><br></pre></td></tr></table></figure><p>⚠️ 缺点：如果 Docker 网络结构改动，IP 可能变化。</p><h3 id="方案-3：network-mode-host"><a href="#方案-3：network-mode-host" class="headerlink" title="方案 3：network_mode: host"></a>方案 3：<code>network_mode: host</code></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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">myservice:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">your-image</span></span><br><span class="line">    <span class="attr">network_mode:</span> <span class="string">host</span></span><br></pre></td></tr></table></figure><p>容器内的 <code>localhost:4000</code> 直接访问宿主机服务。<br>⚠️ 缺点同上，失去网络隔离，端口冲突风险高。</p><h3 id="方案-4：绑定-Hexo-到-0-0-0-0-并用局域网-IP"><a href="#方案-4：绑定-Hexo-到-0-0-0-0-并用局域网-IP" class="headerlink" title="方案 4：绑定 Hexo 到 0.0.0.0 并用局域网 IP"></a>方案 4：绑定 Hexo 到 <code>0.0.0.0</code> 并用局域网 IP</h3><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://192.168.x.x:4000/atom.xml</span><br></pre></td></tr></table></figure><p>其中 <code>192.168.x.x</code> 为宿主机的局域网 IP。</p><p>💡 <strong>建议</strong>：如果 Compose 版本 ≥ 3.4，优先使用 <strong>方案 1</strong>。写死 <code>host.docker.internal</code> 后，即使宿主机 IP 变化，也能稳定访问。</p><p>通过上面的几种方式，无论是在 <strong>Orbstack</strong>、<strong>Linux 单容器</strong> 还是 <strong>Docker Compose</strong> 场景下，都能找到合适的方法让容器访问宿主机的 <code>localhost</code> 服务。<br>日常调试时，推荐优先使用 <code>host.docker.internal</code>（配合 <code>--add-host</code> 或 Compose 的 <code>extra_hosts</code>），既稳定又无需记 IP；<br>在容器之间互访，则直接使用 <strong>服务名&#x2F;容器名</strong>，让 Docker 自带的 DNS 帮你解析。</p><p>掌握这些技巧，既能让 Coco AI 的调试环境跑得顺畅，也能为后续复杂的容器网络架构打好基础。</p>]]></content>
    
    
    <summary type="html">容器运行 Coco AI 时访问宿主机 localhost 服务的解决方案</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>毕业很久了，女同学突然找来修电脑.......</title>
    <link href="https://blog.no-claw.com/posts/4b7a2922/"/>
    <id>https://blog.no-claw.com/posts/4b7a2922/</id>
    <published>2025-08-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前合作的过的文科姑娘，来问我电脑问题，是一个关于文件无法删除的问题。报错无法完成此操作，因为发生意外错误(错误代码-8062)。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/84fc8e8644d5ea13647af774283e266d.png" alt="84fc8e8644d5ea13647af774283e266d"></p><p>我起初以为很简单的事。</p><ol><li>WPS 没关？ —&gt; 关闭软件或者重启 (没用)</li><li>文件锁定 —&gt; 解锁或者 chmod （rwx</li><li>用 CLI 直接删除。 —&gt; rm xxx.pdf （提示 time out？？？？）</li><li>微信发给我看看 –&gt; 能正常删除</li></ol><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">ls -l nfs server : not responding total 3580 -rwxrwxrwx@ 1 root wheel 1832497 4 23 2009 e.pdf</span><br></pre></td></tr></table></figure><p>最后在这里找到了答案，由于之前她使用 icloud 同步了整个磁盘，icloud 很快就会满了。再次扩容之后虽然还是保持了卡死的状态。毕竟 apple 这德行。我还经历过把我的 icloud 日程自动同步到其他人的 icloud 里的操作。按照 apple 售后支持这个德行，跟他们说了也是白说。还是直接搜论坛吧：</p><p><a href="https://discussions.apple.com/thread/253122585?sortBy=rank">https://discussions.apple.com/thread/253122585?sortBy=rank</a></p><p>虽然用 ctrl+command + delete 也能删除，但是感觉是元数据层面？</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250826224754166.png" alt="image-20250826224754166"></p>]]></content>
    
    
    <summary type="html">帮女同学远程排查 Mac 文件无法删除错误代码 8062 的经历</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="MacOS" scheme="https://blog.no-claw.com/tags/MacOS/"/>
    
  </entry>
  
  <entry>
    <title>硬件探索记 Tuya 篇（一）：初识 T5 AI 开发板，先配置个环境吧</title>
    <link href="https://blog.no-claw.com/posts/ab7870f7/"/>
    <id>https://blog.no-claw.com/posts/ab7870f7/</id>
    <published>2025-08-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前因为 AI 小智才玩过 <strong>ESP32S3</strong>，摸索了一遍 <strong>ESP-IDF</strong> 的开发流程。最近又被朋友种草了 <strong>Tuya</strong>，正好申请到了一块开发板，就顺便来玩玩。板子第二天就寄到了，还额外附送了一块屏幕。因为出门只带了 <strong>联想口红电源</strong>，所以我直接用它给开发板供电（当然，直连电脑也可以完成烧录和调试）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250827231911572.png" alt="image-20250827231911572"></p><span id="more"></span><p>玩硬件，其实也是在找回一些失去的记忆。</p><h3 id="TuyaOpen-SDK-简介"><a href="#TuyaOpen-SDK-简介" class="headerlink" title="TuyaOpen SDK 简介"></a>TuyaOpen SDK 简介</h3><p>这次用到的 SDK 叫 <strong>TuyaOpen</strong>，它的定位类似 <strong>ESP-IDF CLI</strong>。</p><p>TuyaOpen 同时也支持开发者跨芯片平台、操作系统开发智能设备，代码开源。</p><p>跨芯片平台是指支持一些系列芯片，包括 T3, T5 AI，ESP32 等等开发板。</p><p>跨操作系统是支持 MacOS，Windows，Linux 全平台开发。对 Mac 党超级友好。</p><ul><li>一样是从 GitHub 拉取开发工具链</li><li>支持编译、烧录、串口调试</li><li>提供了官方示例（apps&#x2F;tuya_cloud 下有很多 demo）</li></ul><p>这套工具链对习惯了 ESP-IDF 的人来说几乎是“无缝迁移”。</p><h3 id="开发环境配置（macOS-示例）"><a href="#开发环境配置（macOS-示例）" class="headerlink" title="开发环境配置（macOS 示例）"></a>开发环境配置（macOS 示例）</h3><p>我这边使用的是 <strong>macOS</strong> 环境，常用命令如下：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 克隆源码</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/tuya/TuyaOpen.git</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 加载环境变量（类似 ESP-IDF 的 export.sh）</span></span><br><span class="line">. ./export.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 进入示例工程</span></span><br><span class="line"><span class="built_in">cd</span> apps/tuya_cloud/switch_demo</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 配置工程（类似 idf.py set-target xxx）</span></span><br><span class="line">tos.py config choice</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 编译工程</span></span><br><span class="line">tos.py build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 烧录到开发板</span></span><br><span class="line">tos.py flash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 7. 打开串口监控</span></span><br><span class="line">tos.py monitor</span><br></pre></td></tr></table></figure><h6 id="注释说明"><a href="#注释说明" class="headerlink" title="注释说明"></a>注释说明</h6><ul><li><code>tos.py config choice</code>：配置芯片型号或项目参数，和 <code>idf.py set-target</code> 类似</li><li><code>tos.py build</code>：编译整个工程，输出 bin 文件</li><li><code>tos.py flash</code>：将编译好的程序写入开发板</li><li><code>tos.py monitor</code>：打开串口调试，实时查看日志</li></ul><p>这个过程第一次会拉一堆开发工具链。</p><blockquote><p>✅ 小贴士：<code>tos.py</code> 本质上和 <code>idf.py</code> 是同一个思路，习惯了 IDF 的命令行，就能很快上手。</p></blockquote><p>不过需要注意的是，这三个命令并不能连在一起使用，这个是和 ESP IDF 不同的地方。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250827234004157.png" alt="image-20250827234004157"></p><p>在 <strong>Linux</strong> 下大家习惯用 <code>lsusb</code>，而在 <strong>macOS</strong> 上则需要借助系统自带命令。</p><h6 id="方式一：system-profiler"><a href="#方式一：system-profiler" class="headerlink" title="方式一：system_profiler"></a>方式一：<code>system_profiler</code></h6><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">system_profiler SPUSBDataType</span><br></pre></td></tr></table></figure><p>能看到所有 USB 总线和设备的详细信息。</p><h6 id="方式二：ioreg"><a href="#方式二：ioreg" class="headerlink" title="方式二：ioreg"></a>方式二：<code>ioreg</code></h6><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">ioreg -p IOUSB</span><br></pre></td></tr></table></figure><p>会以树状结构列出 USB 设备。</p><h6 id="方式三：安装-usbutils（提供-lsusb-命令）"><a href="#方式三：安装-usbutils（提供-lsusb-命令）" class="headerlink" title="方式三：安装 usbutils（提供 lsusb 命令）"></a>方式三：安装 usbutils（提供 lsusb 命令）</h6><p>我自己装了 <code>usbutils</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">➜  ~  watch -n 1 lsusb</span><br><span class="line">➜  ~ lsusb</span><br><span class="line">Bus 000 Device 001: ID 1a86:55d2 1a86 USB Dual_Serial  Serial: 5AAE169301</span><br><span class="line">Bus 000 Device 000: ID 1a86:55d2 1a86 USB 3.1 Bus</span><br></pre></td></tr></table></figure><blockquote><p>✅ 小贴士：串口设备在 macOS 下一般表现为 <code>/dev/cu.usbserial-xxxx</code> 或 <code>/dev/cu.usbmodemxxxx</code>，后面烧录和 <code>monitor</code> 要用到。</p></blockquote><h3 id="编译与运行"><a href="#编译与运行" class="headerlink" title="编译与运行"></a>编译与运行</h3><p>在 <strong>M2 MacbookPro</strong> 上编译速度还不错，只是偶尔 CPU 会满载：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ede5b62aca1c8531f3782372d587758a.png" alt="ede5b62aca1c8531f3782372d587758a"></p><p>等终端出现 <strong>编译成功 (Build done)</strong> 的提示，就说明一切正常，可以进入下一步。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/12891067cd795bee9559cd034766c993.png" alt="12891067cd795bee9559cd034766c993"></p><h3 id="串口通信"><a href="#串口通信" class="headerlink" title="串口通信"></a>串口通信</h3><p>最后就是进入串口调试：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250827231201445.png" alt="image-20250827231201445"></p><p>此时如果开发板正常启动，就能在日志里看到打印输出。后续无论是控制外设还是调试联网，串口监控都是必不可少的。</p><p>谁说只有 windows 才能玩单片机的。。。</p>]]></content>
    
    
    <summary type="html">Tuya T5 AI 开发板开箱与开发环境配置入门</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="纯硬件" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%BA%AF%E7%A1%AC%E4%BB%B6/"/>
    
    
    <category term="单片机" scheme="https://blog.no-claw.com/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    
    <category term="Tuya" scheme="https://blog.no-claw.com/tags/Tuya/"/>
    
  </entry>
  
  <entry>
    <title>硬件黑客松的感受</title>
    <link href="https://blog.no-claw.com/posts/9a6d478a/"/>
    <id>https://blog.no-claw.com/posts/9a6d478a/</id>
    <published>2025-08-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>参加了 Rebuild-z 的硬件黑客松，马上结束了觉得基本没怎么睡好。本来想来旅游玩玩散心的。结果也不比上班轻松。写点感受和教训。</p> <span id="more"></span><h3 id="先说教训"><a href="#先说教训" class="headerlink" title="先说教训"></a>先说教训</h3><ol><li>使用了自己不熟悉的技术栈，比如之前用过 stm32，感觉 esp32 在物联网智能上比较成熟。结果下载固件就浪费了一天（缺少 CH343 驱动）</li><li>临时组队，一直在讨论做什么，怎么做，以及在软件上花费的时间过多。</li><li>倒反天罡，使用 MacOS 搞硬件，抛开生态不谈，应该是全场唯一一个，遇到问题不好找人。</li><li>步子迈得太大了。打算从零画 PCB，然后部署 LLM 端到端的流程。结果哪哪都是坑。。。</li><li>组队成员背景差异巨大，很多事情没对齐。任务进度跟踪不好。</li></ol><h3 id="再说说感受"><a href="#再说说感受" class="headerlink" title="再说说感受"></a>再说说感受</h3><ol><li>很多 00 后，技术不错，也看不出来是 00 后。年轻人很有热情，打工人眼里没有光了。</li><li>很久不正八经写代码了。手生了。</li><li>见到了很好玩的东西，能够解答之前的疑惑。算是没白来，比如和队友跑了 AI 小智，也在隔壁组找到了电视盒子的平替方案。</li><li>创业的氛围和进厂螺丝钉打工真的很不一样。（除了把产品卖出去）</li><li>爱好是爱好，把事情做成还得好好磨练下。</li></ol>]]></content>
    
    
    <summary type="html">参加 Rebuild-z 硬件黑客松的感受与经验教训</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="纯硬件" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%BA%AF%E7%A1%AC%E4%BB%B6/"/>
    
    
    <category term="单片机" scheme="https://blog.no-claw.com/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>高铁上错车以及误车怎么办？</title>
    <link href="https://blog.no-claw.com/posts/9ff7e5aa/"/>
    <id>https://blog.no-claw.com/posts/9ff7e5aa/</id>
    <published>2025-08-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>苏州到高铁的车很多，也很密。才过安检就看到车在等着，03 车 14 看成了 14 车，可能熬夜后遗症，不过也不是第一次这么干了。然后打算从中间穿过去，因为两节车厢拼接，这边只能到，能到 9 车。于是在无锡下车换乘，再一路狂奔到指定位置。询问列车员后，才知道这车比原来的车快了 5 分钟，前车走，后车止，于是眼花缭乱，上错花轿。算是一个经典案例了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/654cdf6ea2fe87bd349c8919e0478228.jpg" alt="654cdf6ea2fe87bd349c8919e0478228"></p><p>但是吧，也不是什么严重问题，经常有人搞错，所以正常出站就好了。万一真的错过呢？还可以改签，如果改签过了，找铁路工作人员呗。。。。。。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/93b388a5816b5dbafdd749f4ae7fc230.jpg" alt="93b388a5816b5dbafdd749f4ae7fc230"></p><p>PS: 吐槽苏州火车站不能刷 12306 二维码进站，然后和复读机吵起来了。我能有什么坏心思呢，单纯的包太多，身份证不好找而已。</p>]]></content>
    
    
    <summary type="html">分享高铁上错车和误车的真实经历及应对方法</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    
  </entry>
  
  <entry>
    <title>如何白嫖嘉立创每个月免费打板？</title>
    <link href="https://blog.no-claw.com/posts/de1fd60a/"/>
    <id>https://blog.no-claw.com/posts/de1fd60a/</id>
    <published>2025-08-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>首先，搜索嘉立创下单助手，需要用这个 APP 来领取优惠券。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812185545780.png" alt="image-20250812185545780"></p><p>左下角 - 优惠群中心 - 免费 PCB 券。每个月可以领取两张。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/48dabeb1-2140-4350-9f07-48d85d39aa79.png" alt="48dabeb1-2140-4350-9f07-48d85d39aa79"></p><span id="more"></span><p>然后领取这个 1-4 层专用券，可以抵扣 20 元。不完全测试下仅限标准套餐，打磨什么的需要额外加钱，不能抵扣。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ae8bbf60aa7904e6ec1a566cf896205a.png" alt="ae8bbf60aa7904e6ec1a566cf896205a"></p><p>然后回到 PCB，有两种办法</p><ol><li>直接在软件页面下单</li><li>导出 gerber 文件下单</li></ol><p>首先确定 DRC 通过，没有问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812190433561.png" alt="image-20250812190433561"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812190659887.png" alt="image-20250812190659887"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812190713210.png" alt="image-20250812190713210"></p><p>或者导出还是嘉立创主页：<a href="https://www.jlc.com/">https://www.jlc.com/</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812190601680.png" alt="image-20250812190601680"></p><p>然后就会跳转到下单的平台，我们可以选择 PCB 的类别和参数，数量就选择 5 个，右边显示价格的地方可以选择优惠券。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812191150619.png" alt="image-20250812191150619"></p><p>选择了优惠券的后，价格直接变成免费。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812191110649.png" alt="image-20250812191110649"></p><p>出货方式选择单片就行，然后板厚 1.0 - 1.6。颜色除了黄色这种冷门工艺，基本都是在免费范围。（可能会有调整）</p><p>绿色的板子是最快的，其他颜色会慢 1-2 天。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812193640555.png" alt="image-20250812193640555"></p><p>个性化服务大多数都需要额外付费，所以保持默认就好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812191658730.png" alt="image-20250812191658730"></p><p>还有一个加 PCB 添加标识的。按照需要使用就好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812191959578.png" alt="image-20250812191959578"></p><p>SMT 和刚网通通不需要。然后选择快递下单就可以了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250812193953001.png" alt="image-20250812193953001"></p><p>剩下就是收板子。</p>]]></content>
    
    
    <summary type="html">教你每月免费领取嘉立创 PCB 打板优惠券的方法</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="纯硬件" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%BA%AF%E7%A1%AC%E4%BB%B6/"/>
    
    
    <category term="硬件" scheme="https://blog.no-claw.com/tags/%E7%A1%AC%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>Coco AI 服务端文件系统检索</title>
    <link href="https://blog.no-claw.com/posts/ca3451f/"/>
    <id>https://blog.no-claw.com/posts/ca3451f/</id>
    <published>2025-08-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着企业和个人数据量的激增，如何高效管理与搜索文件资料，已成为提升工作效率的关键。</p><p><strong>Coco AI</strong> 新增的 <strong>本地文件连接器</strong>，可以直接接入服务端文件系统，实现秒级搜索、即时访问，让服务器上的文件像本地文档一样触手可及。</p><p>本文将介绍如何通过 <strong>Docker 快速部署 Coco Server</strong>，并配置本地文件连接器，实现服务端文件的智能检索。</p><h3 id="一、快速部署-Coco-Server"><a href="#一、快速部署-Coco-Server" class="headerlink" title="一、快速部署 Coco Server"></a>一、快速部署 Coco Server</h3><p>Coco Server 是连接器功能的核心组件，部署完成后即可接入本地文件、RSS 等多种数据源。<br>生产环境建议使用持久化存储，避免数据丢失。</p><span id="more"></span><h4 id="1-推荐部署方式（生产环境）"><a href="#1-推荐部署方式（生产环境）" class="headerlink" title="1. 推荐部署方式（生产环境）"></a>1. 推荐部署方式（生产环境）</h4><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 cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  -v data:/app/easysearch/data \</span><br><span class="line">  -v config:/app/easysearch/config \</span><br><span class="line">  -v logs:/app/easysearch/logs \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><h4 id="2-测试部署方式（非持久化）"><a href="#2-测试部署方式（非持久化）" class="headerlink" title="2. 测试部署方式（非持久化）"></a>2. 测试部署方式（非持久化）</h4><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">docker run -d \</span><br><span class="line">  --name cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><blockquote><p>建议生产环境使用持久化部署（第一种方式），测试环境可选择非持久化部署（第二种方式）。</p></blockquote><h3 id="二、配置-AI-模型"><a href="#二、配置-AI-模型" class="headerlink" title="二、配置 AI 模型"></a>二、配置 AI 模型</h3><p>创建用户后，我选择 <strong>Ollama</strong> 作为模型提供商：</p><ul><li><strong>地址</strong>：<code>http://localhost:11434</code></li><li><strong>模型</strong>：<code>deepseek-r1:7b</code></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/35d9bf40d93482edcfd1cac26bd0f557.png" alt="设置模型"></p><p>在「模型提供商」界面可以看到默认启用的 <strong>Coco AI</strong>，它会直接调用已配置的 Ollama，也支持任何兼容 OpenAI API 协议的 LLM。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b84c85218a471729a646fc47bc899838.png" alt="模型提供商"><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b86711e540165b68ad9f77f5c9f7e4c7.png" alt="Coco AI 设置"></p><h3 id="三、数据源概览"><a href="#三、数据源概览" class="headerlink" title="三、数据源概览"></a>三、数据源概览</h3><p>Coco AI 默认内置了 <strong>官方文档</strong> 和 <strong>Hacker News</strong> 数据源，并在近期新增：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/45419dc868e753402576504aedb4be6f.png" alt="数据源连接器"></p><ul><li><strong>本地文件连接器</strong>（本文重点）</li><li>RSS 连接器</li><li>S3 连接器</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8cab32aeab3b966b11d0370372982415.png" alt="连接器选择"></p><h3 id="四、配置本地文件连接器"><a href="#四、配置本地文件连接器" class="headerlink" title="四、配置本地文件连接器"></a>四、配置本地文件连接器</h3><p>本地连接器的配置非常简单，只需：</p><ol><li>选择文件路径</li><li>设置需要索引的文件后缀</li><li>等待系统从 <strong>localFS</strong> 中智能提取内容</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5415d175c5e1d5406d863adb1c8e681b.png" alt="本地文件设置"></p><h3 id="使用-Docker-时的注意事项"><a href="#使用-Docker-时的注意事项" class="headerlink" title="使用 Docker 时的注意事项"></a>使用 Docker 时的注意事项</h3><p>如果通过 Docker 部署 Coco Server，需要将本地目录映射到容器内，因为连接器读取的是<strong>容器内部路径</strong>，而非主机路径。</p><p>当然，也可以像我这样直接在 <strong>Orbstack</strong> 等容器平台上传文件，省去目录映射的步骤。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811054426758.png" alt="文件上传示例"></p><p>添加完成后，可以在连接器列表中看到新建的服务端本地文件连接器：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1d685c561ee0ea233fcf8f92846d5c99.png" alt="连接器列表"></p><h3 id="五、在-Coco-App-中查看与检索"><a href="#五、在-Coco-App-中查看与检索" class="headerlink" title="五、在 Coco App 中查看与检索"></a>五、在 Coco App 中查看与检索</h3><p>登录 <strong>Coco App</strong> 后，可以看到刚刚添加的 <strong>本地文件</strong> 数据源，并直接进行搜索。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/facdce6af59da887be3227b663e2eae9.png" alt="Coco App 数据源"></p><p>这是刚才添加的服务端文件的搜索结果：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811054929034.png" alt="搜索结果"></p><p>此外，Coco AI 还支持客户端本地文件搜索，但本文重点展示的是<strong>服务端文件检索</strong>功能：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811055911880.png" alt="客户端本地文件"></p><h3 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h3><p>通过本地文件连接器，Coco AI 不仅能帮助你把服务端的文档、日志、配置文件快速接入 AI 检索，还能结合多数据源进行统一搜索，大幅减少人工查找和信息碎片化的时间成本。</p><p>未来你还可以将 本地文件检索 与 RSS、API、数据库连接器组合使用，让企业级信息管理更智能、更实时、更高效。<br>如果你也想让服务器里的海量资料触手可及，不妨部署一个试试——也许你的检索方式，从今天就会不一样。</p>]]></content>
    
    
    <summary type="html">使用 Coco AI 检索服务端文件系统中的文档内容</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>在 Coco AI 中接入 WordPress RSS，实现文章秒级搜索</title>
    <link href="https://blog.no-claw.com/posts/2bdb8c4a/"/>
    <id>https://blog.no-claw.com/posts/2bdb8c4a/</id>
    <published>2025-08-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着内容创作者不断积累文章，如何让自己的内容被快速检索、精准找到，成为提升网站体验的重要一环。</p><p>尤其是对于使用 <strong>WordPress</strong> 搭建博客或官网的朋友来说，文章虽多，但用户往往需要翻页或依赖站内搜索才能找到所需内容。而如果能把 WordPress 的文章源接入 <strong>Coco AI</strong>，不仅能实现<strong>秒级检索</strong>，还可以<strong>结合 AI 进行智能问答、聚合分析</strong>，让你的内容价值成倍提升。</p><p>今天我就来分享一下，如何用 <strong>WordPress 自带的 RSS 功能</strong>，把文章无缝接入到 Coco AI 中，实现一键搜索全站文章。</p><span id="more"></span><h3 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1. 准备工作"></a>1. 准备工作</h3><p>在开始之前，你需要：</p><ul><li>已经安装好的 <strong>WordPress 网站</strong>（如果不会安装，可以参考我之前的文章）<br>👉 <a href="https://mp.weixin.qq.com/s/y-XqALLOhMYjkUZNXFvU7Q">WordPress 安装教程</a></li><li>有一个可用的 Coco AI 服务端（本地部署或云端均可）</li></ul><h3 id="2-获取-WordPress-RSS-链接"><a href="#2-获取-WordPress-RSS-链接" class="headerlink" title="2. 获取 WordPress RSS 链接"></a>2. 获取 WordPress RSS 链接</h3><p>WordPress 默认的 RSS 地址格式非常简单：</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">http(s)://你的域名/feed</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811071134710.png" alt="image-20250811071134710"></p><p>为了确保 RSS 链接正常工作，你可以使用 <strong>Fluent Reader</strong> 这样的本地 RSS 阅读器测试一下。<br>如果能正常读取文章列表，说明 RSS 输出正常：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811071207027.png" alt="image-20250811071207027"></p><h3 id="3-设置-RSS-输出数量与摘要长度"><a href="#3-设置-RSS-输出数量与摘要长度" class="headerlink" title="3. 设置 RSS 输出数量与摘要长度"></a>3. 设置 RSS 输出数量与摘要长度</h3><p>为了让 Coco AI 一次获取更多的文章，你可以在 WordPress 后台调整 RSS 输出数量：</p><ul><li><strong>文章数量</strong>：一次输出多少篇文章（根据需求设置，建议 20~50 篇）</li><li><strong>摘要长度</strong>：决定 AI 抓取的内容字数，摘要越长，AI 的理解能力越强</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811071341574.png" alt="image-20250811071341574"></p><h3 id="4-在-Coco-AI-中添加-RSS-连接器"><a href="#4-在-Coco-AI-中添加-RSS-连接器" class="headerlink" title="4. 在 Coco AI 中添加 RSS 连接器"></a>4. 在 Coco AI 中添加 RSS 连接器</h3><p>接下来进入 <strong>Coco AI 管理后台</strong>，新建一个 RSS 连接器：</p><ol><li>在“数据源”中选择 <strong>RSS</strong></li><li>填写刚刚获取的 <strong>WordPress RSS 链接</strong></li><li>设置刷新频率（例如每 5 分钟、15 分钟）</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811071648993.png" alt="image-20250811071648993"></p><p>配置完成后，你会在连接器列表中看到刚添加的 WordPress RSS 源：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811073351407.png" alt="image-20250811073351407"></p><h3 id="5-刷新并查看文章"><a href="#5-刷新并查看文章" class="headerlink" title="5. 刷新并查看文章"></a>5. 刷新并查看文章</h3><p>等待 Coco AI 进行第一次数据抓取，稍后就能在文章列表中看到你的 WordPress 文章：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811073332601.png" alt="image-20250811073332601"></p><p>在 Coco AI 客户端的搜索栏中，直接输入关键词，就能实时搜索 WordPress 全站文章：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811073456219.png" alt="image-20250811073456219"></p><p>点击搜索结果，Coco AI 会直接跳转到对应的 WordPress 文章主页：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250811073523650.png" alt="image-20250811073523650"></p><hr><h3 id="6-使用场景与优势"><a href="#6-使用场景与优势" class="headerlink" title="6. 使用场景与优势"></a>6. 使用场景与优势</h3><p>通过这种方式接入 RSS，你可以：</p><ul><li><strong>快速检索</strong>：秒级找到 WordPress 上的历史文章</li><li><strong>跨平台聚合</strong>：和其他数据源一起接入 Coco AI，实现跨网站统一搜索</li><li><strong>AI 智能问答</strong>：基于你的网站内容，直接生成读者提问的答案</li><li><strong>知识库构建</strong>：将博客内容沉淀为长期可用的知识库</li></ul><p>只需几个步骤，就能让 WordPress 与 Coco AI 打通，实现全站文章的<strong>智能化搜索与访问</strong>。<br>如果你的网站内容很多、更新频繁，这种方法能极大提升读者和你的检索效率，让你的文章真正“活”起来。</p>]]></content>
    
    
    <summary type="html">将 WordPress 博客通过 RSS 接入 Coco AI 实现智能文章检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你使用 Coco AI 订阅RSS，智能检索Hexo博客</title>
    <link href="https://blog.no-claw.com/posts/47752b69/"/>
    <id>https://blog.no-claw.com/posts/47752b69/</id>
    <published>2025-08-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近 Coco AI 上线了几个新功能：<strong>S3 连接器、本地文件连接器、RSS 连接器</strong>。本篇先重点讲 RSS 连接器检索 HEXO 博客的接入方法。</p><h3 id="一、安装-Coco-Server"><a href="#一、安装-Coco-Server" class="headerlink" title="一、安装 Coco Server"></a>一、安装 Coco Server</h3><p>使用 Docker 部署是最省心的方式。</p><h4 id="方式-1：映射数据目录（推荐）"><a href="#方式-1：映射数据目录（推荐）" class="headerlink" title="方式 1：映射数据目录（推荐）"></a>方式 1：映射数据目录（推荐）</h4><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 cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  -v data:/app/easysearch/data \</span><br><span class="line">  -v config:/app/easysearch/config \</span><br><span class="line">  -v logs:/app/easysearch/logs \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><span id="more"></span><h4 id="方式-2：快速测试（不映射目录）"><a href="#方式-2：快速测试（不映射目录）" class="headerlink" title="方式 2：快速测试（不映射目录）"></a>方式 2：快速测试（不映射目录）</h4><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">docker run -d \</span><br><span class="line">  --name cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><blockquote><p>测试环境可以用方式 2，生产环境建议使用方式 1，避免数据丢失。</p></blockquote><h3 id="二、模型配置"><a href="#二、模型配置" class="headerlink" title="二、模型配置"></a>二、模型配置</h3><p>创建完用户后，我直接设置了 <strong>Ollama</strong> 作为模型提供商：</p><ul><li>地址：<code>http://localhost:11434</code></li><li>模型：<code>deepseek-r1:7b</code></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/35d9bf40d93482edcfd1cac26bd0f557.png" alt="设置模型"></p><p>在「模型提供商」界面可以看到默认开启的 <strong>Coco AI</strong>，它会直接调用我配置的 Ollama，也支持其他兼容 OpenAI API 的 LLM。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b84c85218a471729a646fc47bc899838.png" alt="模型提供商"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b86711e540165b68ad9f77f5c9f7e4c7.png" alt="Coco AI 设置"></p><hr><h3 id="三、数据源概览"><a href="#三、数据源概览" class="headerlink" title="三、数据源概览"></a>三、数据源概览</h3><p>Coco AI 默认植入了官方文档和 Hacker News 数据源，这次新增了三类连接器：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8cab32aeab3b966b11d0370372982415.png" alt="8cab32aeab3b966b11d0370372982415"></p><ul><li>S3 连接器</li><li>本地文件连接器</li><li>RSS 连接器（本篇重点）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/45419dc868e753402576504aedb4be6f.png" alt="数据源连接器"></p><hr><h3 id="四、Hexo-拓展-RSS-功能"><a href="#四、Hexo-拓展-RSS-功能" class="headerlink" title="四、Hexo 拓展 RSS 功能"></a>四、Hexo 拓展 RSS 功能</h3><p>我们先来安装 Hexo：</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">pnpm install -g hexo-cli</span><br><span class="line">hexo init my-blog</span><br><span class="line"><span class="built_in">cd</span> my-blog</span><br><span class="line">pnpm install</span><br></pre></td></tr></table></figure><p>Hexo 默认没有开启 RSS，需要通过插件来支持。你可以这样做：</p><hr><h4 id="1-安装-hexo-generator-feed"><a href="#1-安装-hexo-generator-feed" class="headerlink" title="1. 安装 hexo-generator-feed"></a>1. 安装 <code>hexo-generator-feed</code></h4><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">pnpm add hexo-generator-feed</span><br></pre></td></tr></table></figure><h4 id="2-配置-config-yml"><a href="#2-配置-config-yml" class="headerlink" title="2. 配置 _config.yml"></a>2. 配置 <code>_config.yml</code></h4><p>在 Hexo 根目录的 <code>_config.yml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">feed:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">atom</span> <span class="comment"># 可选: atom / rss2 / json</span></span><br><span class="line">  <span class="attr">path:</span> <span class="string">atom.xml</span> <span class="comment"># 输出文件路径</span></span><br><span class="line">  <span class="attr">limit:</span> <span class="number">20</span> <span class="comment"># 0 表示不限制数量</span></span><br></pre></td></tr></table></figure><h4 id="3-生成"><a href="#3-生成" class="headerlink" title="3. 生成"></a>3. 生成</h4><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">pnpm hexo clean &amp;&amp; pnpm hexo generate</span><br></pre></td></tr></table></figure><p>生成的 RSS 会在 <code>public/atom.xml</code></p><h3 id="五、添加-RSS-连接器"><a href="#五、添加-RSS-连接器" class="headerlink" title="五、添加 RSS 连接器"></a>五、添加 RSS 连接器</h3><ol><li><p>选择 <strong>RSS 连接器</strong>，比如本地调试模式是 localhost:4000：</p></li><li><p>输入 RSS 地址（这里我用的是我的博客）：</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">http://host.docker.internal:4000/atom.xml</span><br></pre></td></tr></table></figure></li><li><p>刷新时间设为 <strong>1 分钟</strong>（默认即可）</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250813065426386.png" alt="image-20250813065426386"></p><p>添加完成后可以看到我同时接入了 S3、本地文件和 RSS：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1d685c561ee0ea233fcf8f92846d5c99.png" alt="连接器列表"></p><h3 id="六、在-Coco-App-中查看数据源"><a href="#六、在-Coco-App-中查看数据源" class="headerlink" title="六、在 Coco App 中查看数据源"></a>六、在 Coco App 中查看数据源</h3><p>登录 Coco App 后，可以看到刚才添加的 S3、本地文件和 RSS 数据源：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/facdce6af59da887be3227b663e2eae9.png" alt="Coco App 数据源"></p><hr><h3 id="七、搜索效果"><a href="#七、搜索效果" class="headerlink" title="七、搜索效果"></a>七、搜索效果</h3><p>使用 Coco-AI 搜索时，能快速检索到 RSS 中的内容，效果比博客自带的好很多：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250813065952619.png" alt="image-20250813065952619"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过 RSS 连接器，Coco-AI 可以实时抓取和索引博客内容，并与本地文件、S3 数据等统一搜索，非常适合做多源聚合知识库。</p><p>如果 RSS 输出有限，可以调整博客端的 RSS 配置，让它输出更多历史内容，发挥 Coco AI 检索的最大价值。</p>]]></content>
    
    
    <summary type="html">手把手教你将 Hexo 博客接入 Coco AI 实现智能检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>学云计算到底是在学什么？</title>
    <link href="https://blog.no-claw.com/posts/4b1e0fb/"/>
    <id>https://blog.no-claw.com/posts/4b1e0fb/</id>
    <published>2025-08-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>个人感觉 AWS 甚至很多云计算平台都被过分神话了。以下是个人经验，不喜勿喷。</p><h2 id="人话版本"><a href="#人话版本" class="headerlink" title="人话版本"></a>人话版本</h2><h3 id="学什么？"><a href="#学什么？" class="headerlink" title="学什么？"></a>学什么？</h3><ol><li>怎么启动虚拟机，以及如何进行远程管理，比如 SSH 和 DRP。</li><li>对象存储 S3。如果你能自建 MinIO 或者 RustFS 也没问题，如果你喜欢 OpenStack Swift 也行。</li><li>Docker&#x2F;K8S。这帮人天天吹云原生，其实主要还是容器充分可以利用云上的弹性。</li><li>网络基础，比如为什么我连不上某台机器，能从网络链路都排查一遍。</li></ol><h3 id="为什么还有各种组件？"><a href="#为什么还有各种组件？" class="headerlink" title="为什么还有各种组件？"></a>为什么还有各种组件？</h3><p>举几个例子：</p><ol><li>中间件或者数据库软件很多都有 PaaS 版本，主打一个“开箱即用”。好处是一键配置多可用区，一键安装软件，让开发人员不用考虑多余的事。<blockquote><p>曾经的槽点：某些云厂商直接把开源软件托管来卖，所以才有后来一些厂家改商用 License 的事。</p></blockquote></li><li>分布式计算软件安装繁琐，对小白友好直接控制台点点点，文科生也能学会。</li><li>都上云了，你可以不用，但云厂商一定要有，否则对不起“宇宙厂”的名头。</li></ol><h3 id="上了云成本就一定小吗？"><a href="#上了云成本就一定小吗？" class="headerlink" title="上了云成本就一定小吗？"></a>上了云成本就一定小吗？</h3><p>不一定，需要在可用性、经济性、性能之间取舍。</p><ol><li>很多 HA 的功能是冗余的，分布式软件自己带了一套，云上又做了一套 Standby。</li><li>除非买年度订阅有很大折扣，否则成本不可控。</li><li>都在云上，网络延迟肯定比线下 DC 要大。</li><li>如果你是纯 C 端用户，玩玩得了，你的这点用量人家根本看不上。</li></ol><h3 id="上云的好处在哪里？"><a href="#上云的好处在哪里？" class="headerlink" title="上云的好处在哪里？"></a>上云的好处在哪里？</h3><ol><li>钱花到哪里更加明确了。</li><li>PPT 能做得更好看。</li><li>产品可以快速出 MVP，能够对全套链有个完整认知。</li><li>有些服务可以 Pay as you go。</li><li>公网 IP 自由，不过也开始陆续收费了。</li><li>减少运维成本，出问题压力直接甩给厂商。</li></ol><h3 id="我是一个小白，买软件送服务吗？"><a href="#我是一个小白，买软件送服务吗？" class="headerlink" title="我是一个小白，买软件送服务吗？"></a>我是一个小白，买软件送服务吗？</h3><ol><li>国内云有些支持免费，但海外云需要付费购买售后支持，可以帮你找文档、讲解服务用法、错误排查等。</li><li>Serverless 普遍做得很烂，你学习平台规则的成本大于你在平台上部署的成本。</li><li>想啥呢？文档都那么烂。</li></ol><h3 id="国内和国外云生态对比"><a href="#国内和国外云生态对比" class="headerlink" title="国内和国外云生态对比"></a>国内和国外云生态对比</h3><ol><li>海外很多 PaaS 或 SaaS 都基于 AWS 来做，所以看起来国外云计算很火。</li><li>国内软件开发和云计算很割裂，整体还是以 SDE 为导向，跟云厂商打交道最多的还是运维。</li><li>一些遵守当地合规的云公司夹在中间更难受，选择需谨慎。</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>如果个人爱好者真的有需要，还是自己搭建 Homelab 吧。控制好噪音和功耗就行。</p>]]></content>
    
    
    <summary type="html">从个人经验聊聊学云计算到底在学什么</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>从零到用：RSS 接入 Coco-AI 实战指南</title>
    <link href="https://blog.no-claw.com/posts/2c28c43/"/>
    <id>https://blog.no-claw.com/posts/2c28c43/</id>
    <published>2025-08-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近 Coco-AI 上线了几个新功能：<strong>S3 连接器、本地文件连接器、RSS 连接器</strong>。我会逐一介绍，本篇先重点讲 RSS 连接器的接入方法。</p><h3 id="一、安装-Coco-Server"><a href="#一、安装-Coco-Server" class="headerlink" title="一、安装 Coco Server"></a>一、安装 Coco Server</h3><p>使用 Docker 部署是最省心的方式。</p><h4 id="方式-1：映射数据目录（推荐）"><a href="#方式-1：映射数据目录（推荐）" class="headerlink" title="方式 1：映射数据目录（推荐）"></a>方式 1：映射数据目录（推荐）</h4><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 cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  -v data:/app/easysearch/data \</span><br><span class="line">  -v config:/app/easysearch/config \</span><br><span class="line">  -v logs:/app/easysearch/logs \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><span id="more"></span><h4 id="方式-2：快速测试（不映射目录）"><a href="#方式-2：快速测试（不映射目录）" class="headerlink" title="方式 2：快速测试（不映射目录）"></a>方式 2：快速测试（不映射目录）</h4><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">docker run -d \</span><br><span class="line">  --name cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><blockquote><p>测试环境可以用方式 2，生产环境建议使用方式 1，避免数据丢失。</p></blockquote><h3 id="二、模型配置"><a href="#二、模型配置" class="headerlink" title="二、模型配置"></a>二、模型配置</h3><p>创建完用户后，我直接设置了 <strong>Ollama</strong> 作为模型提供商：</p><ul><li>地址：<code>http://localhost:11434</code></li><li>模型：<code>deepseek-r1:7b</code></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/35d9bf40d93482edcfd1cac26bd0f557.png" alt="设置模型"></p><p>在「模型提供商」界面可以看到默认开启的 <strong>Coco AI</strong>，它会直接调用我配置的 Ollama，也支持其他兼容 OpenAI API 的 LLM。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b84c85218a471729a646fc47bc899838.png" alt="模型提供商"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b86711e540165b68ad9f77f5c9f7e4c7.png" alt="Coco AI 设置"></p><hr><h3 id="三、数据源概览"><a href="#三、数据源概览" class="headerlink" title="三、数据源概览"></a>三、数据源概览</h3><p>Coco-AI 默认植入了官方文档和 Hacker News 数据源，这次新增了三类连接器：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8cab32aeab3b966b11d0370372982415.png" alt="8cab32aeab3b966b11d0370372982415"></p><ul><li>S3 连接器</li><li>本地文件连接器</li><li>RSS 连接器（本篇重点）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/45419dc868e753402576504aedb4be6f.png" alt="数据源连接器"></p><hr><h3 id="四、添加-RSS-连接器"><a href="#四、添加-RSS-连接器" class="headerlink" title="四、添加 RSS 连接器"></a>四、添加 RSS 连接器</h3><ol><li><p>选择 <strong>RSS 连接器</strong></p></li><li><p>输入 RSS 地址（这里我用的是我的博客）：</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">https://airag.click/atom.xml</span><br></pre></td></tr></table></figure></li><li><p>刷新时间设为 <strong>1 分钟</strong>（默认即可）</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7f43ba7ea73c6285c040f62ddf8e1359.png" alt="7f43ba7ea73c6285c040f62ddf8e1359"><br>添加完成后可以看到我同时接入了 S3、本地文件和 RSS：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1d685c561ee0ea233fcf8f92846d5c99.png" alt="连接器列表"></p><h3 id="五、小插曲：RSS-条目数量限制"><a href="#五、小插曲：RSS-条目数量限制" class="headerlink" title="五、小插曲：RSS 条目数量限制"></a>五、小插曲：RSS 条目数量限制</h3><p>一开始我发现 Coco-AI 只能显示 <strong>最近 20 条</strong>，以为是 Coco Server 的限制，后来群友提醒才发现是 <strong>RSS 服务端设置</strong>的问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810105815147.png"></p><p>在博客服务端调整配置后，RSS 就能显示 <strong>全部文章</strong> 了。虽然很多 RSS 只显示最近内容，但其实可以通过配置让它输出完整数据。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0e9471843e2c4a95330c9561a3a79a50.png" alt="RSS 配置"></p><hr><h3 id="六、在-Coco-App-中查看数据源"><a href="#六、在-Coco-App-中查看数据源" class="headerlink" title="六、在 Coco App 中查看数据源"></a>六、在 Coco App 中查看数据源</h3><p>登录 Coco App 后，可以看到刚才添加的 S3、本地文件和 RSS 数据源：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/facdce6af59da887be3227b663e2eae9.png" alt="Coco App 数据源"></p><hr><h3 id="七、搜索效果"><a href="#七、搜索效果" class="headerlink" title="七、搜索效果"></a>七、搜索效果</h3><p>使用 Coco-AI 搜索时，能快速检索到 RSS 中的内容，效果比博客自带的好很多：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3ce7b0de664342b561aa051a85090b05.png" alt="搜索结果"></p><p>点击搜索结果可直接跳转到博客文章。我用的是 <strong>Hexo 主题</strong>，其他 RSS 源也一样适用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810105446450.png" alt="文章跳转"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过 RSS 连接器，Coco-AI 可以实时抓取和索引博客内容，并与本地文件、S3 数据等统一搜索，非常适合做多源聚合知识库。</p><p>如果 RSS 输出有限，可以调整博客端的 RSS 配置，让它输出更多历史内容，发挥 Coco-AI 检索的最大价值。</p>]]></content>
    
    
    <summary type="html">从零开始将 RSS 订阅源接入 Coco AI 的完整实战教程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco AI × Amazon S3：秒搜你的云端文件</title>
    <link href="https://blog.no-claw.com/posts/f050840/"/>
    <id>https://blog.no-claw.com/posts/f050840/</id>
    <published>2025-08-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着企业和个人数据量的激增，如何高效管理与搜索云端资料，成为提升工作效率的关键。<br><strong>Coco-AI</strong> 新增的 <strong>S3 对象存储连接器</strong>，可以将 <strong>Amazon S3</strong> 存储桶直接接入智能检索系统，实现秒级搜索、即时访问，让云端文件像本地文档一样触手可及。</p><p>本篇将详细介绍如何通过 <strong>Docker 快速部署 Coco Server</strong>，并配置 S3 连接器，完成与亚马逊云科技的无缝集成。</p><h3 id="一、快速部署-Coco-Server"><a href="#一、快速部署-Coco-Server" class="headerlink" title="一、快速部署 Coco Server"></a>一、快速部署 Coco Server</h3><p>Coco Server 是连接器功能的运行核心，部署好它后才能接入 S3。<br>生产环境建议使用持久化存储方式，避免数据丢失。</p><p><strong>推荐部署方式（生产环境）</strong><br>持久化存储，避免数据丢失：</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 cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  -v data:/app/easysearch/data \</span><br><span class="line">  -v config:/app/easysearch/config \</span><br><span class="line">  -v logs:/app/easysearch/logs \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><span id="more"></span><p><strong>测试部署方式（非持久化）</strong></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">docker run -d \</span><br><span class="line">  --name cocoserver \</span><br><span class="line">  -p 9000:9000 \</span><br><span class="line">  infinilabs/coco:0.7.1-2426</span><br></pre></td></tr></table></figure><blockquote><p>建议生产环境使用第一种部署方式，测试环境可选择第二种。</p></blockquote><h3 id="二、配置-AI-模型"><a href="#二、配置-AI-模型" class="headerlink" title="二、配置 AI 模型"></a>二、配置 AI 模型</h3><p>创建用户后，我选择 <strong>Ollama</strong> 作为模型提供商：</p><ul><li>地址：<code>http://localhost:11434</code></li><li>模型：<code>deepseek-r1:7b</code></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/35d9bf40d93482edcfd1cac26bd0f557.png" alt="设置模型"></p><p>在「模型提供商」界面可以看到默认开启的 <strong>Coco AI</strong>，它会直接调用我配置的 Ollama，也支持其他兼容 OpenAI API 的 LLM。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b84c85218a471729a646fc47bc899838.png" alt="模型提供商"><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b86711e540165b68ad9f77f5c9f7e4c7.png" alt="Coco AI 设置"></p><h3 id="三、数据源概览"><a href="#三、数据源概览" class="headerlink" title="三、数据源概览"></a>三、数据源概览</h3><p>Coco AI 默认内置官方文档和 Hacker News 数据源，近期新增三类连接器：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/45419dc868e753402576504aedb4be6f.png" alt="数据源连接器"></p><ul><li><strong>S3 连接器</strong>（本篇重点）</li><li>本地文件连接器</li><li>RSS 连接器</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8cab32aeab3b966b11d0370372982415.png" alt="8cab32aeab3b966b11d0370372982415"></p><h3 id="四、接入-Amazon-S3"><a href="#四、接入-Amazon-S3" class="headerlink" title="四、接入 Amazon S3"></a>四、接入 Amazon S3</h3><ol><li><p><strong>选择 S3 对象存储连接器</strong><br>填写 <strong>Endpoint</strong>（例：东京区 <code>s3.ap-northeast-1.amazonaws.com</code>）、<strong>Bucket 名称</strong>、<strong>亚马逊云科技凭证（Access Key ID &#x2F; Secret Access Key）</strong>，刷新间隔建议保持 <strong>1 分钟</strong> 默认值。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3cb586e73f22d92740ae9ca8589a5548.png" alt="3cb586e73f22d92740ae9ca8589a5548"></p></li><li><p><strong>获取亚马逊云科技访问凭证</strong></p><ul><li>登录 <strong>亚马逊云科技 IAM 控制台</strong></li><li>创建访问密钥（Access Key ID &#x2F; Secret Access Key）</li><li>为用户分配最小化 S3 访问权限（推荐遵循最小权限原则）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810113853491.png" alt="image-20250810113853491"></p><p>这里选择访问密钥 - 创建访问密钥，然后保存 Access Key ID &#x2F; Secret Access Key 就好。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ae3c5becf8fbf554e94a2624e422ecd1.png" alt="ae3c5becf8fbf554e94a2624e422ecd1"></p></li></ol><p>创建过程中会出现最佳实践提示，不影响后续配置，下载密钥即可使用。<br>其他的凭证方式虽然有 IAM Role 和 Role anywhere，但是我们这次不会用到。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/41be05e14aefb28c33a9b6cf6b6c360a.png" alt="41be05e14aefb28c33a9b6cf6b6c360a"></p><p>确保这个用户有访问 S3 的权限，如果是生产的环境的话，确保要采用最小权限原则来防止不必要的麻烦。如果你在存储桶上配置了对应桶策略也可以。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0092335804bdbbc32e1a86523b714ceb.png" alt="0092335804bdbbc32e1a86523b714ceb"></p><ol start="3"><li><strong>对象前缀（Prefix）配置</strong><br>这个是我在 S3 上的对象存储，放了一些 markdown 文件上去。<br>还是这张图，我使用的是东京区的存储桶 dify233，所以 endpoint 是 s3.ap-northeast-1.amazonaws.com。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3cb586e73f22d92740ae9ca8589a5548.png" alt="3cb586e73f22d92740ae9ca8589a5548"></p><p>这里的对象前缀可以理解为目录，在 S3 设置之初会把所有文件夹的名称当作前缀加到文件名前面，所以也有 S3 是扁平化管理一说。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810115049644.png" alt="image-20250810115049644"></p><h3 id="五、集成效果"><a href="#五、集成效果" class="headerlink" title="五、集成效果"></a>五、集成效果</h3><p>完成连接后，S3 中的 Markdown 文件可被 Coco AI <strong>实时索引与检索</strong>，点击搜索结果即可跳转到 S3 公网访问链接，例如：</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">https://&lt;bucket&gt;.s3.&lt;region&gt;.amazonaws.com/&lt;对象名&gt;</span><br></pre></td></tr></table></figure><p>不仅支持标题关键词搜索，还可结合 LLM 实现<strong>语义检索</strong>，极大提升信息获取效率。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1d685c561ee0ea233fcf8f92846d5c99.png" alt="连接器列表"></p><p>添加完成后可以看到我同时接入了 S3、本地文件和 RSS，我们这里主要开介绍关于 S3 的连接器。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/facdce6af59da887be3227b663e2eae9.png" alt="Coco App 数据源"></p><p>使用 Coco-AI 搜索时，能快速检索到 s3 中的 markdown 文件。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c00fba85122f676609f20966c8c2ac37.png" alt="c00fba85122f676609f20966c8c2ac37"></p><p>点击搜索结果可直接跳转到对应链接。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/994789ee413cc311f518d78e8d457e00.png" alt="994789ee413cc311f518d78e8d457e00"></p><p>也支持把地址复制出来：<a href="https://dify233.s3.ap-northeast-1.amazonaws.com/%E5%AF%B9%E8%B1%A1%E5%90%8D%EF%BC%8C%E5%85%B6%E5%AE%9E%E5%B0%B1%E6%98%AFS3%E7%9A%84https">https://dify233.s3.ap-northeast-1.amazonaws.com/对象名，其实就是S3的https</a> 链接了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810115839596.png" alt="image-20250810115839596"></p><h3 id="六、适用场景"><a href="#六、适用场景" class="headerlink" title="六、适用场景"></a>六、适用场景</h3><ul><li><strong>企业内部知识库</strong>：研发文档、政策文件、培训资料统一存放于 S3</li><li><strong>个人云端资料管理</strong>：博客、项目资料随时调用</li><li><strong>跨团队协作</strong>：多地访问，实时共享</li></ul><p>通过 <strong>Coco AI S3 连接器</strong>，只需几步，即可让 ** Amazon S3** 成为高效智能检索系统的云端引擎。<br>无论是个人开发者，还是大型企业团队，都能快速构建<strong>跨云端、本地、第三方数据源的统一知识平台</strong>。</p>]]></content>
    
    
    <summary type="html">将 Amazon S3 接入 Coco AI，实现云端文件的秒级智能搜索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>大上墨水屏测评</title>
    <link href="https://blog.no-claw.com/posts/2fca5ee8/"/>
    <id>https://blog.no-claw.com/posts/2fca5ee8/</id>
    <published>2025-08-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在这个几乎人人都离不开电子屏幕的时代，墨水屏显示器正在悄悄进入更多人的视野。相比传统的 LCD 或 OLED，墨水屏的护眼、低功耗和接近纸张的阅读体验，让不少文字工作者和重度阅读用户心动。</p><p>这次入手的大上 Paperlike 系列墨水屏，使用下来有了不少感受。本文就从外观、使用体验到一些细节槽点，和大家聊聊我的真实体验。</p><p>最早接触墨水屏显示器，是前年在北京的 SKP。很多年前使用过 kindle，那时候去看墨水屏平板。正巧看到了墨水屏显示器，在店里试用了下很喜欢。相对于很古早的 kindle 而言，国产化的产品更加符合国人的使用习惯化。</p><p>和其他产品不同的是，大上的墨水屏有彩色版，所以可以当作外接显示器来用。这么多天使用下来，我比较喜欢的产品的地方竟然是看视频，比如说看《游戏王》，感觉就像是漫画一样。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/67b620a95d9bc60d62ed14e76e74fc29.jpg" alt="67b620a95d9bc60d62ed14e76e74fc29"></p><p>我的机器是 Paperlike253 的 Mac 版本，到手之后先拍了外观一览，总体很好看，奈何桌子空间内有限，实在摆不下了。M2 的 Macbook pro 只能同时连接两个设备，所以基本是一个外接 4K 屏幕和一个外接墨水屏使用的状态。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c0115655d31f7a9f8942a4098db94f14.jpg" alt="c0115655d31f7a9f8942a4098db94f14"></p><p>相对于最早的 Kindle 来说，大上墨水屏已经有质的飞跃，大上的革命者系列竟然能达到 33HZ 的“高刷”，甚至可以播放一部分的视频。毕竟很早以前的 kindle，连翻页都感觉卡。我这些日子挺喜欢用墨水屏来看游戏王的，虽然是日漫，但是开了视频模式，感觉像是漫画一样。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/749ba0f79d91a534e3cbee7479f3ddd1.jpg" alt="749ba0f79d91a534e3cbee7479f3ddd1"></p><p>无论什么电子产品，只要跟 Mac 相关的都需要先做特殊的配置，Mac 版本的墨水屏需要安装客户端来稳定画质，因为苹果的限制。革命者系列自带了一个 HDMI，TYEPC 的线，还有一根 USB-A 转 USB-B 的线。USB 的线是用和驱动软件通讯的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a24d488530586d71912efad70ba9946a.jpg" alt="a24d488530586d71912efad70ba9946a"></p><p>这里有两个槽点:</p><blockquote><ol><li>Typec 只用用来传输数据，并不能给 Macbook 供电。</li><li>Macbook 或者 Macmini 都没用 USB-A 的接口了，所以这个线还要转接一下。</li></ol></blockquote><p>这里有一个小技巧就是如果你是双屏幕的情况下，可以把另外一个显示器当作 HUB 用。这又何尝不是一种菊花链？不过还是觉得能够一线通就更好了～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/61e0ef6a87eb7d73eabb642b0d7a6794.jpg" alt="61e0ef6a87eb7d73eabb642b0d7a6794"></p><p>墨水屏显示器的电源适配器是 12V3A，也就是 36W。这个功率在显示屏的圈子里已经很低了。个人感觉后面也可以出一些直流供电的版本，然后就可以使用移动电源或者笔记本 C2C 进行供电。经验而谈，墨水屏在不刷新的时候基本是不耗电的，个人推测这个 36W 主要还是给背光灯使用的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250816181022845.png" alt="image-20250816181022845"></p><p>言归正传，介绍外观。25.3 寸的屏幕使用起来没有比我的 28 寸显示器体感少太多。机身十分简洁，只有左下角有六个按键，分别对应：</p><blockquote><p>C：短按手动刷新，长按记忆模式。</p><p>M：切换模式，有自动模式，文本模式，图文模式以及视频模式。</p><p>+&#x2F;- ： 对比度调节大小，右边是色温调节，比如</p></blockquote><p>大大的按键，按压起来软软的，给人一种重剑无锋的感觉。使用起来十分方便。没有花里胡哨的功能菜单，更像是大人的玩具。</p><p>显示器背面是标准的 10 * 10 的 VESA 接口，所以这个显示器支架也可以换成市面上通用的支架壁。自带的显示器支架也自带旋转和伸缩的功能，相对于很多显示器自带的支架已经好了很多了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/56161754ace53608554b5e86ab42a06a.jpg" alt="56161754ace53608554b5e86ab42a06a"></p><p>说回屏幕本身，在熄屏状态，以及没有连接数据线或者没有开启客户端的时候，墨水屏会显示需要信号的图案。这个在致敬有线电视时代的无信号，很有意思。</p><p>屏幕有背光灯，所以晚上使用的时候不用担心光源的问题。如果不连接电脑的话，晚上就不会触发这个。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e284353a6c5cd7650789cfb61a0d59e1.jpg" alt="e284353a6c5cd7650789cfb61a0d59e1"></p><p>墨水屏同样也支持 HIDPI，在我的 M2 Pro Macbook Pro 上最高支持 1600 * 900 的 HIDPI。基本上就是把原来的 3K 放大了一倍，这一点比很多 3.5K 带鱼屏的 HIDIPI 好很多。之前公司的带鱼屏外接 Mac 总会把下半部分截断，使用起来体验很差，但是在大上的屏幕上就没有这个问题，毫无疑问，这是一个真正合格的 Macbook 外接屏幕。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250816120740371.png" alt="image-20250816120740371"></p><p>因为这个事，我还特意调查了分辨率以及 PPI 对 MacOS 外接的影响。大上墨水屏外接几乎没啥违和的地方，屏幕分辨虽然不是 4K，但是在开了原生 HIDPI 使用下完全没有问题。问了客服 PPI 是 145，虽然还达不到 MacOS 的 200+，已经可以达到外接使用的程度了。</p><p>计算屏幕 PPI（像素密度）的公式是：</p><figure class="highlight latex"><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">\[</span></span><br><span class="line">PPI = <span class="keyword">\frac</span>&#123;<span class="keyword">\sqrt</span>&#123;(<span class="keyword">\text</span>&#123;宽像素&#125;)<span class="built_in">^</span>2 + (<span class="keyword">\text</span>&#123;高像素&#125;)<span class="built_in">^</span>2&#125;&#125;&#123;<span class="keyword">\text</span>&#123;屏幕对角线尺寸（英寸）&#125;&#125;</span><br><span class="line"><span class="keyword">\]</span></span><br></pre></td></tr></table></figure><blockquote><p>对比参考：32 寸 4K 屏幕 ≈ 138 PPI27 寸 4K：≈ 163 PPI</p></blockquote><p>然后再说说这几种模式，图文模式主要针对混排的场景，这样图片能够显示清晰一些。最清晰的还是文本模式，适合纯文本，看电子书，文字网页，这个效果堪比 LCD 显示器。看视频一定要用视频模式（有点废话）。自动模式我很少用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810203434629.png" alt="image-20250810203434629"></p><p>在几种模式里，我最喜欢的是文本模式，其次是视频模式。文本模式用来看电子书，就是最古早的 Kindle 那种看电子书的感觉，白纸黑字的感觉最好了。这个模式也能用来刷网课，比如一些理论公式什么的，效果竟然比视频模式要好的多。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b52d4ef3110f1afb3f21cef0326db900.jpg" alt="b52d4ef3110f1afb3f21cef0326db900"></p><p>视频模式主要用来刷剧，比如前面提到的看游戏王。感觉就是在看漫画。另外我在设计 PCB 电路板的时候，使用墨水屏来看渲染模型，也有种物理书的即视感。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6350b30de2954e1bc07e1427ab8a9beb.jpg" alt="6350b30de2954e1bc07e1427ab8a9beb"></p><p>美中不足的事看 MV 的效果不算是很好，尤其有些 MV 会出现颜色过暗的问题。还是看日漫吧 hhhh</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/87f350fbe65fddd4761cb3ea476fcee9.jpg" alt="87f350fbe65fddd4761cb3ea476fcee9"></p><p>在不同模式之间切换需要调节合适的亮度和对比度。比如黑色的场景就把对比度调亮一些。文本模式就可以暗一些，这样文字回更加清晰。</p><p>刷新率能够达到宣传的 33HZ，尽管和主流 LCD 显示器相比在窗口拖拽方面还是有较大的差距，但是在文字阅读和日常上网方面已经几乎可以当作主力设备来使用了，比如买一个 Macmini，然后外接这样一个显示器无论老人和小孩都能用。（前提是得解决 Paperlike 客户端开机自启动的问题）</p><p>在拓展屏的模式下 ufotest 有这个报错，在把墨水屏设置为主显示器之后可以正常显示了。</p><figure class="highlight plaintext"><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">ufotest SYNC FAILURE:</span><br><span class="line">Move all apps and browser windows to primary monitor #1.</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1cd33bb392ac586a67a7dbaa73b3b62c.jpg" alt="1cd33bb392ac586a67a7dbaa73b3b62c"></p><p>背景尽量纯色，这样可以让显示效果更好一些。再来说残影，这是一个不可避免的问题，所以可以在客户端中设置刷新时间，也可以左下角使用 C 按键手动刷新。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250810203508034.png" alt="image-20250810203508034"></p><p>墨水屏用着用着就会困，应该是没有蓝光辐射的原因，作为一个科技从业人员，每天面对电子产品和蓝光的辐射，对健康是一个不小的伤害，很多时都在默默承受这一切，虽然慢慢下意识习惯了这一切，但是身体从未忘记。</p><p>墨水屏写作，多了一份静心。让我们回归最早的阅读，把电子化和纸质的感觉合二为一可能是未来的趋势。起码现在对于文字工作者来说，在文本模式下写作已经可以很专注了。</p><p>总的来说，大上的墨水屏在同类产品里已经算是头部代表，无论是作为护眼阅读设备，还是作为 Mac 的外接显示器，都有着不俗的表现。虽然它现在并不能完全取代传统高刷显示器，但在文字工作、漫画视频和长时间阅读方面，确实提供了一种更加健康和沉浸的体验。</p><p>在“屏幕无处不在”的今天，墨水屏或许正在开辟另一条道路。对我来说，它既是工具，也是让人慢下来的媒介。希望未来大上的墨水屏能越做越好，真正成为数字生活与纸质体验之间的桥梁。</p>]]></content>
    
    
    <summary type="html">大上墨水屏显示器深度测评，护眼与阅读体验分析</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
    <category term="显示器" scheme="https://blog.no-claw.com/tags/%E6%98%BE%E7%A4%BA%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Google SEO 技巧</title>
    <link href="https://blog.no-claw.com/posts/3d393e74/"/>
    <id>https://blog.no-claw.com/posts/3d393e74/</id>
    <published>2025-08-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.google.com/ncr">https://www.google.com/ncr</a><br>这个入口就是让它不要应用特定国家重定向（No Country Redirect）</p>]]></content>
    
    
    <summary type="html">Google SEO 实用技巧，包括 NCR 入口、搜索优化等提升网站排名的方法。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="SEO" scheme="https://blog.no-claw.com/tags/SEO/"/>
    
  </entry>
  
  <entry>
    <title>普通人需要哪些电脑知识？</title>
    <link href="https://blog.no-claw.com/posts/87d5ac58/"/>
    <id>https://blog.no-claw.com/posts/87d5ac58/</id>
    <published>2025-08-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为一个 N 多年的电脑社畜，日常使用 window&#x2F;Linux&#x2F;MacOS 三系统，写点最近被问到的问题。</p><blockquote><p>请问您觉得有什么我们普通人可以尝试的一些电脑软件方面的操作呢，这些前沿的知识蛮重要，一般人很难知道 🌹</p></blockquote><ol><li><p>装机，如果追求不多，弄个装系统的 U 盘就行，Windows 安装有手就行。不折腾就用微 PE 工具箱，需要 Linux 共存就用 Ventoy。然后再能换个内存和硬盘就更好了。我习惯于买小配置然后自己升级，当然土豪随意。手不残党还可以上网搜一搜自己换个笔记本屏幕。</p></li><li><p>安装软件。远离 360 全家桶，xx 大师，xx 精灵，这些不止流氓，还会带一堆的捆绑软件。杀毒软件就用火绒，或者干脆不装大多数情况也没啥事。还有就是再垃圾软件篡改主页之后再改回来，以及更换浏览器的搜索引擎。</p></li><li><p>分清楚快捷方式和安装软件的区别。很多人把快捷方式一删除就不管了，很多年前遇到过一个妹子倒腾半天最后发过来一个快捷方式给我。请掌握正确的卸载方法（比如 windows 的控制面板）。还要分清楚安装版和绿色版区别。大家都是这么过来的，windows 虽然不好用，但是会强迫你看到一堆的技术字眼，比如 DLL 链接库， .net 框架等等。</p></li><li><p>分清无线和有线，以及怎么查看动态 IP 地址和配置静态地址。这能够解决大部分电脑不能上网的情况。如果能自己接网线就更好了。</p></li><li><p>分清楚电脑接口，比如视频线的 VGA，HDMI，DP， Typec。数据线的 USB2.0 和 3.0，不管 3.X 后面多少后缀，他也是当年的 USB3.0。手边常备一个数据线转接头，转 HDMI 或者 USB。</p></li><li><p>买电脑买新不买旧，不要为了省钱而受罪。配置太低的可能刷网页，写文档都卡。不需要频繁更新系统。</p></li><li><p>U 盘不要买金士顿。</p></li><li><p>不要找程序员修电脑。很多人不会，会的也懒得修。</p></li></ol>]]></content>
    
    
    <summary type="html">面向普通用户的实用电脑知识科普与常见问题解答</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>使用 MacOS 给泰山派烧录镜像</title>
    <link href="https://blog.no-claw.com/posts/89eeed1/"/>
    <id>https://blog.no-claw.com/posts/89eeed1/</id>
    <published>2025-08-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>MacOS 单系统单系统一直用了这么多年，最近捡起来硬件，也顺便记录下 MacOS 烧录泰山派 RK3568 的过程。官方教程写得比较简略，踩了几次坑后，整理一下记录，也算是给同样习惯用 Mac 的朋友一个参考。</p><p>官方文档参考链接：<br><a href="https://wiki.lckfb.com/zh-hans/tspi-rk3566/system-usage/img-download.html">泰山派烧录镜像说明（官方 Wiki）</a></p> <span id="more"></span><h3 id="准备工具"><a href="#准备工具" class="headerlink" title="准备工具"></a>准备工具</h3><p>嘉立创网盘提供了烧录工具 <code>upgrade_tool</code>，Mac 上直接下载即可。<br>下载后，记得先给执行权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x upgrade_tool</span><br></pre></td></tr></table></figure><p>主要参考了这个文章，提供了有效的 Mac 烧录方法：<br><a href="https://zhuanlan.zhihu.com/p/684922505">知乎参考文章</a></p><h3 id="硬件连接"><a href="#硬件连接" class="headerlink" title="硬件连接"></a>硬件连接</h3><ol><li><p>泰山派上电开机，用 Type-C 接口接到 Mac。<br>⚠️ 一定要用<strong>高速数据线</strong>，很多 Type-C 线只支持充电，没数据功能，用这种线是可能识别不了设备，就算能够识别只有几兆的速度。烧录镜像通常几个 GB 大小，速度差的线也容易出错。</p></li><li><p>按住开发板上的 <strong>REC 按键</strong>（不松手）。</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921200130465.png" alt="image-20250921200130465"></p><ol start="3"><li>轻按一下 <strong>RST 按键</strong>（复位），立即松开，但仍然保持 REC 键按下。</li></ol><p>此时设备进入 Loader 模式，可以在 Mac 端确认：</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">./upgrade_tool ld</span><br></pre></td></tr></table></figure><p>正常输出会显示类似：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Program Log will save in the /Users/xu/upgrade_tool/log/</span><br><span class="line">List of rockusb connected(1)</span><br><span class="line">DevNo=1 Vid=0x2207,Pid=0x350a,LocationID=1 Mode=Loader SerialNo=86b2acaf11e3305</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921195537592.png" alt="识别设备"></p><p>如果能看到 <code>Mode=Loader</code>，就说明设备被正确识别了。</p><h3 id="烧录镜像"><a href="#烧录镜像" class="headerlink" title="烧录镜像"></a>烧录镜像</h3><p>官方镜像是 <code>update.img</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">./upgrade_tool uf update.img</span><br></pre></td></tr></table></figure><p>正常会输出烧录进度，比如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">Program Log will save in the /Users/xu/upgrade_tool/log/</span><br><span class="line">ftruncate: Invalid argument</span><br><span class="line">Loading firmware...</span><br><span class="line">Support Type:RK3568 FW Ver:1.0.00 FW Time:2024-09-19 08:50:15</span><br><span class="line">Loader ver:1.01 Loader Time:2024-09-18 17:38:28</span><br><span class="line">Download Image Total(4957941K),Current(2925601K)</span><br><span class="line">Download Image Total(4957941K),Current(4066337K)</span><br><span class="line">Upgrade firmware ok.</span><br></pre></td></tr></table></figure><p>最后出现 <code>Upgrade firmware ok.</code> 就表示烧录完成，可以松开 REC 键了。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>整体流程下来，其实在 MacOS 上烧录泰山派并不复杂：</p><ul><li>核心是 <code>upgrade_tool</code> 工具 + 正确的键位操作；</li><li>数据线质量很关键；</li><li>日志里的部分报错不用慌，只要最后有 <code>Upgrade firmware ok.</code> 就算成功。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250921195305486.png" alt="成功识别"></p>]]></content>
    
    
    <summary type="html">在 macOS 上为泰山派 RK3568 开发板烧录系统镜像的详细教程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="纯硬件" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%BA%AF%E7%A1%AC%E4%BB%B6/"/>
    
    
    <category term="硬件" scheme="https://blog.no-claw.com/tags/%E7%A1%AC%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>Mac 上 Orbstack的Docker容器访问微服 Wordpress IPv6 解析问题记录</title>
    <link href="https://blog.no-claw.com/posts/2ad973ba/"/>
    <id>https://blog.no-claw.com/posts/2ad973ba/</id>
    <published>2025-08-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天在 Mac 上的 Docker 容器访问微服里的 Wordpress 时，遇到了 IPv6 无法正常访问的问题。<br>现象是：<code>dig</code> 能解析出 IPv6 地址，但容器内网络不可达。</p><p><a href="https://appstore.lazycat.cloud/#/shop/detail/dev.beiyu.wordpress">https://appstore.lazycat.cloud/#/shop/detail/dev.beiyu.wordpress</a></p><h3 id="问题现象"><a href="#问题现象" class="headerlink" title="问题现象"></a>问题现象</h3><ul><li><code>dig</code> 查询正常，能返回 IPv6 结果。</li><li>但容器内访问（<code>curl</code>、<code>ping6</code>）失败，提示网络不可达。</li></ul><span id="more"></span><h3 id="原因排查"><a href="#原因排查" class="headerlink" title="原因排查"></a>原因排查</h3><p>查询后发现：</p><ul><li>默认情况下，Docker 引擎并未为容器分配 IPv6 地址。</li><li>这导致虽然 DNS 能解析，但容器无 IPv6 出口。</li></ul><h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><p>在 Orbstack 设置中开启 IPv6 支持即可：</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/4aa9a333-dd99-4667-914e-18c71fc4504b.png" alt="开启 IPv6"></p><ul><li>开启后，Docker 引擎会自动重启。</li><li>无需手动添加 <code>--ipv6</code> 启动参数。</li></ul><h3 id="验证结果"><a href="#验证结果" class="headerlink" title="验证结果"></a>验证结果</h3><p>开启 IPv6 后，在容器内执行 <code>curl</code> 获取 Wordpress RSS 链接，正常返回内容：</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 https://micro.heiyu.space/feed</span><br></pre></td></tr></table></figure><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/070f7321-c061-4a1b-9fb4-7805a0dc0b35.png" alt="curl 成功结果"></p><p><code>ping6</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">[root@5c79a5875d68 easysearch]# ping6 micro.heiyu.space</span><br><span class="line">PING micro.heiyu.space(fc03:1136:384f:313:a637:437:d22b:0) 56 data bytes</span><br><span class="line">64 bytes from fc03:1136:384f:313:a637:437:d22b:0: icmp_seq=1 ttl=62 <span class="keyword">time</span>=4.27 ms</span><br><span class="line">64 bytes from fc03:1136:384f:313:a637:437:d22b:0: icmp_seq=2 ttl=62 <span class="keyword">time</span>=5.85 ms</span><br><span class="line">64 bytes from fc03:1136:384f:313:a637:437:d22b:0: icmp_seq=3 ttl=62 <span class="keyword">time</span>=3.36 ms</span><br><span class="line">64 bytes from fc03:1136:384f:313:a637:437:d22b:0: icmp_seq=4 ttl=62 <span class="keyword">time</span>=3.97 ms</span><br></pre></td></tr></table></figure><p><strong>总结</strong><br>在 Mac 上运行的 Docker 容器默认不分配 IPv6 地址，需要在 Orbstack 设置中手动开启 IPv6 支持。开启后无需额外配置，容器即可正常解析并访问 IPv6 目标。</p>]]></content>
    
    
    <summary type="html">记录 Mac 上 Orbstack 容器访问懒猫微服 WordPress 时 IPv6 解析异常的排查</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="排查" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%8E%92%E6%9F%A5/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>修改 Hexo 的 RSS 阅读数量（Icarus 主题）</title>
    <link href="https://blog.no-claw.com/posts/2d0d3922/"/>
    <id>https://blog.no-claw.com/posts/2d0d3922/</id>
    <published>2025-08-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Icarus 主题本身<strong>不直接生成 RSS</strong>，只是<strong>提供 RSS 链接的展示位置</strong>（例如导航栏、侧边栏的 RSS 按钮）。<br>真正生成 RSS&#x2F;Atom 的功能，需要依赖 Hexo 的插件（通常是 <a href="https://github.com/hexojs/hexo-generator-feed"><code>hexo-generator-feed</code></a>）。</p><p>因此，要修改 RSS 条数，需要分两步：</p><h2 id=""><a href="#" class="headerlink" title=""></a><span id="more"></span></h2><h2 id="1-安装-RSS-生成插件"><a href="#1-安装-RSS-生成插件" class="headerlink" title="1. 安装 RSS 生成插件"></a>1. 安装 RSS 生成插件</h2><p>在博客根目录（<code>_config.yml</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">npm install hexo-generator-feed --save</span><br></pre></td></tr></table></figure><hr><h2 id="2-在站点-config-yml（根目录）配置-RSS"><a href="#2-在站点-config-yml（根目录）配置-RSS" class="headerlink" title="2. 在站点 _config.yml（根目录）配置 RSS"></a>2. 在站点 <code>_config.yml</code>（根目录）配置 RSS</h2><p>RSS 条数由站点配置控制，而不是主题配置。示例：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">feed:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">atom</span> <span class="comment"># 类型，可选 atom、rss2、json</span></span><br><span class="line">  <span class="attr">path:</span> <span class="string">atom.xml</span> <span class="comment"># 输出文件名</span></span><br><span class="line">  <span class="attr">limit:</span> <span class="number">50</span> <span class="comment"># 输出文章数，0 表示全部文章</span></span><br></pre></td></tr></table></figure><blockquote><p>如果不写 <code>limit</code>，默认值是 20（插件源码中 <code>var limit = config.limit || 20</code>）。</p></blockquote><hr><h2 id="3-在-Icarus-主题-config-yml-添加-RSS-链接"><a href="#3-在-Icarus-主题-config-yml-添加-RSS-链接" class="headerlink" title="3. 在 Icarus 主题 _config.yml 添加 RSS 链接"></a>3. 在 Icarus 主题 <code>_config.yml</code> 添加 RSS 链接</h2><p>Icarus 主题只是显示你生成好的 RSS 地址，常见的配置方式有两种：</p><p><strong>导航栏：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="attr">navbar:</span></span><br><span class="line">  <span class="attr">menu:</span></span><br><span class="line">    <span class="attr">RSS:</span> <span class="string">/atom.xml</span> <span class="comment"># 对应 feed.path 的路径</span></span><br></pre></td></tr></table></figure><p><strong>侧边栏（Profile 小部件）：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="attr">widgets:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">position:</span> <span class="string">left</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">profile</span></span><br><span class="line">    <span class="attr">social_links:</span></span><br><span class="line">      <span class="attr">RSS:</span></span><br><span class="line">        <span class="attr">icon:</span> <span class="string">fas</span> <span class="string">fa-rss</span></span><br><span class="line">        <span class="attr">url:</span> <span class="string">/atom.xml</span></span><br></pre></td></tr></table></figure><hr><h2 id="4-重新生成站点"><a href="#4-重新生成站点" class="headerlink" title="4. 重新生成站点"></a>4. 重新生成站点</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo g</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">http://你的域名/atom.xml</span><br></pre></td></tr></table></figure><p>即可查看 RSS 内容。</p><hr><h2 id="优先级说明"><a href="#优先级说明" class="headerlink" title="优先级说明"></a>优先级说明</h2><ul><li><strong>RSS 条数生成逻辑</strong> → 由 <code>hexo-generator-feed</code> 插件控制，只读取 <strong>站点 <code>_config.yml</code></strong> 的 <code>feed</code> 配置。</li><li><strong>RSS 链接展示</strong> → 由主题（如 Icarus）控制，在主题 <code>_config.yml</code> 中设置按钮或链接。</li><li>如果两个地方都有 <code>feed:</code> 配置，<strong>站点 <code>_config.yml</code> 优先级更高</strong>，主题配置不会覆盖它。</li></ul>]]></content>
    
    
    <summary type="html">修改 Hexo Icarus 主题的 RSS 输出文章数量配置。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="Blog" scheme="https://blog.no-claw.com/tags/Blog/"/>
    
  </entry>
  
  <entry>
    <title>避雷！ 百大值友熊猫不是猫公开抄袭文章理直气壮，态度恶劣，投诉、辟谣、关评全流程实录</title>
    <link href="https://blog.no-claw.com/posts/951989c3/"/>
    <id>https://blog.no-claw.com/posts/951989c3/</id>
    <published>2025-08-05T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>前面的文章提到了 熊猫不是熊猫&#x2F;panda 不是猫，这个人抄袭我的文章，我出于礼貌跟他沟通，希望他道歉和整改结果没有任何后续，于是我发布了一开始的文章</p><p>最早在这个平台上刷到我抄袭我的文章，然后果断投诉下架，值得买和知乎也处理了他的文章。</p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805180331325.png" alt="image-20250805180331325"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805181155989.png" alt="image-20250805181155989"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805181217180.png" alt="image-20250805181217180"></p><p>同时他的公众号也抄袭了这个文章，阅读量有小 1W，转发就有 100 多次，也不清楚到底是赚了多少钱。</p><p>更有意思的是，他不仅不道歉而且在公众号上发辟谣。来说不是抄袭之类的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805180402497.png" alt="image-20250805180402497"></p><p>然后评论区就是一边倒的状态，甚至有说“他就是挂出来挨打的”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805182706199.png" alt="image-20250805182706199"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250805182642259.png" alt="image-20250805182642259"></p><p>这个是我一开始评论的样子，后来他直接屏蔽我的留言。为了让大家看明白这个事情，所以我贴了我的原文链接。，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/01a34927898554d32c581fc5484749cb.jpg" alt="01a34927898554d32c581fc5484749cb"></p><p>过了一天之后发现评论变成了仅作者可见，显然是心虚了，不敢让其他读者看原文。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c5e13708609ac7bebab7e56aa50dfec3.jpg" alt="c5e13708609ac7bebab7e56aa50dfec3"></p><p>从工信部的备案中能够查看这个人的信息，还会继续扒其他信息的，也欢迎互联网网友提供。（信息为公开）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e144a360ae0373a240222b8fbc6f9b49.png" alt="e144a360ae0373a240222b8fbc6f9b49"></p><p>抄袭文章，然后连一个公开的道歉都没有么？然后发过来发辟谣说我侮辱他？</p><p>简直是素质低劣，屡教不改。我仁至义尽了。</p><p>很难不让人认为他其他文章是不是也是抄的，多次沟通让他公开道歉，但是这个态度实在在就是惯犯。</p><p>如果有商家想找他合作推广产品，慎重吧。。。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/708b3286525e9faa71d4f144c4e9a255.png" alt="708b3286525e9faa71d4f144c4e9a255"></p><p>典型的死猪不怕开水烫。</p><blockquote><p>很多的情况是很多原作者流量不高，抄袭的人天南海北的洗，所以流量数字也好看。等被发现或者告了。再来个下架关帐号建新号来逃避法律的追责。如此往复，抄袭者总是能无本获利。我呼吁能够维护原作的知识产权，不要让这种蛀虫危害互联网。</p></blockquote>]]></content>
    
    
    <summary type="html">记录百大值友抄袭文章后投诉维权的完整过程</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="零碎" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E9%9B%B6%E7%A2%8E/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>上网逛逛开源项目，发现自己的文章被偷了，心慈手软给对方整改机会，结果后悔到拍大腿!</title>
    <link href="https://blog.no-claw.com/posts/5f056020/"/>
    <id>https://blog.no-claw.com/posts/5f056020/</id>
    <published>2025-07-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>本来想找点 NAS 的开源项目玩一玩，然后很巧搜到了别人”介绍”我的作品，一开始还挺高兴的，起码自己写的工具有人用了。除了我的朋友们，懒猫微服的圈子之外还有互联网上的人用。然后看到底，这个文字风格咋怎么眼熟，就好像是我的风格，打开我原来的文章，确认是洗稿无疑了。（后面有证据）</p><h3 id="事件起因"><a href="#事件起因" class="headerlink" title="事件起因"></a>事件起因</h3><p>我的文章最早是发在懒猫微服的，后续也发在了亚马逊云科技的公众号上，这个稿件是商业性质了，所以足够够成侵权了。大家记住这个人，虽然他删了帖子，但是再前前后后与他沟通之后，我没有得到他良好的认错态度，所以还是决定把这个文章发出来。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729124620875.png" alt="image-20250729124620875"></p><p>记住这个 ID，这个人有很多平台，有的叫做 panda 不是猫。然后他所有的平台都洗稿了我这个文章，并且流量还不低。而且我的文章是给懒猫微服的商业供稿，就被这个人无耻的剽窃了。所以也很难不怀疑他的其他文章也是不是洗稿来的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9ef262128ea86b9000a634c6b9af32ef.png" alt="9ef262128ea86b9000a634c6b9af32ef"></p><h3 id="抄袭证据一览"><a href="#抄袭证据一览" class="headerlink" title="抄袭证据一览"></a>抄袭证据一览</h3><p>我列出了整个文章的四个抄袭的点，几乎涵盖了所有的项目介绍，而且介绍顺序，功能，隐藏彩蛋，话术丝毫不差。</p><h4 id="抄袭-1-核心功能描述被整段搬运"><a href="#抄袭-1-核心功能描述被整段搬运" class="headerlink" title="抄袭 1: 核心功能描述被整段搬运"></a>抄袭 1: 核心功能描述被整段搬运</h4><p><strong>我的原文</strong>：Containly 的核心功能是通过目录央射的 Docker 引擎读取所有容器信息，包括容器的启动、退出、停止及其他:如，当容器处于“Create“状态时它会被标记为“Other”状态，便于管理,</p><p><strong>他的洗稿抄袭版本</strong>：界面非常清爽，是我喜欢的类型，整个容器就这么一个界面，能看到容器的启动、退出、停止及其他状态。例如，当容器处于“Create”状态时，它会被标记为“Other”状态，便于管理。</p><blockquote><p>这个 other 状态其实埋下的一个彩蛋，绝对不是可以第一次使用的是能够测出来的。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8a06932af8f2d422f30feb8d0354c1da.png" alt="8a06932af8f2d422f30feb8d0354c1da"></p><h4 id="抄袭-2-容器卡片设计原封不动"><a href="#抄袭-2-容器卡片设计原封不动" class="headerlink" title="抄袭 2: 容器卡片设计原封不动"></a>抄袭 2: 容器卡片设计原封不动</h4><p><strong>我的原文</strong>：默认情况下，每个容器卡片会显容器的网桥信息、端口映射和 URL。默认使用 HTTP 协议，鼠标悬停时，会在右侧显示操作按钮。通过点击这些按钮操作会被保留，再次点击会隐藏，这样子就整个比较美观。</p><p>按钮功能包括</p><ul><li>停止&#x2F;启动</li><li>重启</li><li>查看日志</li><li>SSH 进入容器</li><li>切换 HTTP&#x2F;HTTPS</li><li>黑名单管理</li></ul><p><strong>他的洗稿抄袭版本</strong>：容器卡片会显示容器的网桥信息、端口信息以及 URL 链接地址，默认使用 HTTP 协议，可切换到 HTTPS 协议。</p><p>当鼠标悬停时，卡片右侧会显示操作按钮，这里提供了容器的启停重启、日志查看、SSH 功能以及黑名单管理功能，再次点击可以隐藏按钮，显得卡片更为美观。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/610586e1c594a8634e2681934d6151b7.png" alt="610586e1c594a8634e2681934d6151b7"></p><h4 id="抄袭-3-域名拼接也照抄"><a href="#抄袭-3-域名拼接也照抄" class="headerlink" title="抄袭 3: 域名拼接也照抄"></a>抄袭 3: 域名拼接也照抄</h4><p><strong>我的原文</strong>：此外，Containly 还提供了一个输入框，用户可以输入需要监控的 NAS 域名，面板会自动根据域名和端口拼接成 URI，并存储在 localStorage 中。更进一步，Containly 还支持暗黑模式，提升了用户体验。</p><p><strong>他的洗稿抄袭版本</strong>：右上角，Containly 提供了一个地址输入框，在这里填入我们 NAS 的域名或者 IP，面板会自动根据域名和端口拼接成 URI，并存储在 localStorage 中。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a8a8735c989f5f25fcc9b41ddbf4887d.png" alt="a8a8735c989f5f25fcc9b41ddbf4887d"></p><h4 id="抄袭-4-一句不改的-SSH-彩蛋"><a href="#抄袭-4-一句不改的-SSH-彩蛋" class="headerlink" title="抄袭 4: 一句不改的 SSH 彩蛋"></a>抄袭 4: 一句不改的 SSH 彩蛋</h4><p><strong>我的原文</strong>：利用面板的 SSH 功能能够直接从面板进去访问容器的 SHELL，不用执行再 docker exec 的命令。</p><p><strong>他的洗稿抄袭版本</strong>：利用面板的 SSH 功能能够直接从面板进去访问容器的 SHELL，不用执行再 docker exec 的命令。</p><p>（原文一个字没改）<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b0973ae3ea3fd502fe5d6b398c698d0e.png" alt="b0973ae3ea3fd502fe5d6b398c698d0e"></p><p>把两篇文章扔到 GPT 里，也判断为洗稿。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729124409398.png" alt="image-20250729124409398"><br>根据您提供的两篇文章内容，第二篇文章显然是<strong>Containly</strong> 项目的原创介绍，详细描述了项目的背景、功能、部署方式以及开发过程。第一篇文章则是对第二篇内容的转载，几乎没有进行实质性的修改或添加新信息。因此，第一篇文章属于<strong>抄袭</strong>行为。</p><p>如果您是第一篇文章的作者，建议您对内容进行充分的改写，或者注明原文出处，以避免版权问题。如果您是第二篇文章的作者，您有权要求删除或修改未经授权转载的内容。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729124517725.png" alt="image-20250729124517725"></p><h3 id="沟通-维权过程"><a href="#沟通-维权过程" class="headerlink" title="沟通 &amp; 维权过程"></a>沟通 &amp; 维权过程</h3><p>先是公众号上告诉他涉嫌抄袭洗稿，但是没有任何效果。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802222852033.png" alt="image-20250802222852033"></p><p>然后给我回复了这个，完全没有意识到自己错在哪里。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802222904535.png" alt="image-20250802222904535"></p><p>我于是贴证据给他，然后就被无视了。他用我这个文章流量也不少，看 100 多的转发应该也赚了不少的广告费。<br>抄袭他人稿件，然后用来盈利。除了微信的平台之外，其他平台也不会有太少的广告费。</p><h3 id="适用法律条款-赔偿依据"><a href="#适用法律条款-赔偿依据" class="headerlink" title="适用法律条款 &amp; 赔偿依据"></a>适用法律条款 &amp; 赔偿依据</h3><blockquote><p>根据著作权法第 10 条、第 52 条第 5 项及第 53 条第 1 项，对方未经许可复制并在信息网络向公众传播我的文章，已构成剽窃及侵权，应承担停止侵害、赔礼道歉并赔偿损失的法律责任。</p></blockquote><blockquote><p>第五十四条 侵犯著作权或者与著作权有关的权利的，侵权人应当按照权利人的实际损失或者侵权人的违法所得给予赔偿；权利人的实际损失或者侵权人的违法所得难以计算的，由人民法院根据侵权行为的情节，判决给予五百元以上五百万元以下的赔偿。<br>为制止侵权行为所支付的合理开支，也可以酌情计入赔偿数额</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3abc5179f57f832208d0cb342fceb865.jpg" alt="3abc5179f57f832208d0cb342fceb865"></p><h3 id="写在最后：给创作者的提醒"><a href="#写在最后：给创作者的提醒" class="headerlink" title="写在最后：给创作者的提醒"></a>写在最后：给创作者的提醒</h3><p>那再给一个机会吧，然后加微信聊聊，说不定有悔意呢（实时证明我单纯了）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/45386ea4832e60e4c1602145873d05c3.png" alt="45386ea4832e60e4c1602145873d05c3"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729124923744.png"></p><p>然后对方一开始没理不饶人，简直认为原创。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802221718775.png" alt="image-20250802221718775"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802221759610.png" alt="image-20250802221759610"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802221815635.png" alt="image-20250802221815635"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802222035715.png" alt="image-20250802222035715"></p><p>给了他半个多周的时间来整改，然后就没有然后了，再也没回过我消息。这种无耻的人，还是把他发出来，让大家避雷吧，这种靠抄袭别人还振振有词骗流量的人，才是当代互联网的蛀虫。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250802220649125.png" alt="image-20250802220649125"></p><h3 id="免责声明"><a href="#免责声明" class="headerlink" title="免责声明"></a>免责声明</h3><p>本文仅陈述可核实事实，所引用截图均为证据保存；如有异议可联系作者。<br>这个人微信号在他平台上有，不能够算恶意曝光泄漏隐私。</p>]]></content>
    
    
    <summary type="html">发现文章被开源项目洗稿后维权的完整经历与教训</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="零碎" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E9%9B%B6%E7%A2%8E/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>找到了一个wedav的项目</title>
    <link href="https://blog.no-claw.com/posts/3ebd9b42/"/>
    <id>https://blog.no-claw.com/posts/3ebd9b42/</id>
    <published>2025-07-29T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>win10 连接 Mac 共享的 SMB 有问题，所以想挂载 webdav，于是发现了这个项目：</p><p>可以不话心思解决 mac 个 windows 关于 Sambda 的兼容问题。</p><p><a href="https://github.com/mar10/wsgidav">https://github.com/mar10/wsgidav</a></p><p><code>WsgiDAV</code> 是一个支持 SSL 的独立 WebDAV 服务器，可以在 Linux、OSX 和 Windows 上作为 Python 命令行脚本运行。它的主要功能包括：</p> <span id="more"></span><h3 id="主要功能："><a href="#主要功能：" class="headerlink" title="主要功能："></a>主要功能：</h3><ol><li><strong>WebDAV 支持</strong>：它提供完整的 WebDAV 协议实现，用于通过 HTTP 协议远程访问、管理和编辑文件。</li><li><strong>SSL 支持</strong>：你可以为 <code>WsgiDAV</code> 启用 SSL 加密，确保文件传输的安全性。</li><li><strong>文件系统提供程序</strong>：通过文件系统提供程序，允许你将文件夹暴露为 WebDAV 共享。</li><li><strong>基本认证和 PAM 登录认证</strong>：支持基本认证，并且支持在 Linux 或 OSX 上使用 PAM 认证。</li><li><strong>Docker 支持</strong>：<code>WsgiDAV</code> 提供了一个实验性的 Docker 镜像，可以在 Docker 容器中运行 WebDAV 服务。</li><li><strong>多线程支持</strong>：支持高性能的多线程 Web 服务器功能。</li></ol><h3 id="安装："><a href="#安装：" class="headerlink" title="安装："></a>安装：</h3><ol><li><p>安装 <code>wsgidav</code> 和 <code>cheroot</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">pip install wsgidav cheroot</span><br></pre></td></tr></table></figure></li><li><p>启动 <code>WsgiDAV</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">wsgidav --host=0.0.0.0 --port=80 --root=/tmp --auth=anonymous</span><br></pre></td></tr></table></figure></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250730090658313.png" alt="image-20250730090658313"></p><h3 id="配置选项："><a href="#配置选项：" class="headerlink" title="配置选项："></a>配置选项：</h3><ul><li><p><code>--auth=anonymous</code> 启用匿名认证，允许没有身份验证的访问。</p></li><li><p><code>--auth=pam-login</code> 启用基于 PAM 的认证（在 Linux 或 OSX 上使用）。</p></li><li><p>启用 SSL（推荐使用）：</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">wsgidav --host=0.0.0.0 --port=8080 --root=/tmp --auth=anonymous --ssl-adapter pyopenssl</span><br></pre></td></tr></table></figure></li></ul><h3 id="用-Docker-启动-WebDAV-服务器："><a href="#用-Docker-启动-WebDAV-服务器：" class="headerlink" title="用 Docker 启动 WebDAV 服务器："></a>用 Docker 启动 WebDAV 服务器：</h3><ol><li><p>拉取 Docker 镜像：</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">docker pull mar10/wsgidav</span><br></pre></td></tr></table></figure></li><li><p>运行 Docker 容器：</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">docker run --<span class="built_in">rm</span> -it -p 8080:8080 -v /tmp:/var/wsgidav-root mar10/wsgidav</span><br></pre></td></tr></table></figure></li></ol><h3 id="扩展功能："><a href="#扩展功能：" class="headerlink" title="扩展功能："></a>扩展功能：</h3><ul><li><strong>虚拟文件系统</strong>：通过 WebDAV 使数据结构呈现为可编辑的文件系统。</li><li><strong>文档编辑</strong>：支持在线编辑 MS Office 文档。</li><li><strong>集成 WSGI</strong>：<code>WsgiDAV</code> 可以作为 WSGI 应用程序在其他 WSGI 兼容的 Web 服务器上运行。</li></ul><h3 id="结论："><a href="#结论：" class="headerlink" title="结论："></a>结论：</h3><p><code>WsgiDAV</code> 是一个灵活、强大的 WebDAV 解决方案，适合用于文件共享、在线文档编辑等应用场景。如果你需要在 Python 环境中快速部署 WebDAV 服务，它提供了简单的命令行启动选项以及 Docker 支持。如果需要更多自定义功能，还可以通过配置文件和 WSGI 中间件进行扩展。</p>]]></content>
    
    
    <summary type="html">发现一个 WebDAV 开源项目，可用于 NAS 文件远程访问。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>使用 OpenList 将 S3 转换为 WebDAV</title>
    <link href="https://blog.no-claw.com/posts/21efda6/"/>
    <id>https://blog.no-claw.com/posts/21efda6/</id>
    <published>2025-07-28T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Amazon S3 是一种高可扩展、低延迟的对象存储服务，广泛用于存储和管理数据。尽管目前有多个工具来将 S3 与其他存储解决方案（如 Storage Gateway，EMRFS 或者 S3FS 等）集成，但今天我们介绍一个新的方法，通过使用 OpenList，将 S3 存储转换为 WebDAV，简化文件管理和访问。</p><p>本文将引导你通过 Docker Compose 启动 OpenList，并将其与 Amazon S3 配置，以便通过 WebDAV 协议进行访问。</p><span id="more"></span><h3 id="步骤-1：使用-Docker-Compose-启动-OpenList"><a href="#步骤-1：使用-Docker-Compose-启动-OpenList" class="headerlink" title="步骤 1：使用 Docker Compose 启动 OpenList"></a>步骤 1：使用 Docker Compose 启动 OpenList</h3><p>首先，我们需要通过 Docker Compose 来启动 OpenList 服务。以下是一个示例 <code>docker-compose.yml</code> 配置文件：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">openlist:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">&quot;openlistteam/openlist:latest&quot;</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">openlist</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;./data:/opt/openlist/data&quot;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5244:5244&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">PUID=0</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">PGID=0</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">UMASK=022</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><p>保存该配置后，使用以下命令启动 OpenList 容器：</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">docker-compose up -d</span><br></pre></td></tr></table></figure><p>启动成功后，OpenList 的 Web 界面将在端口 5244 上可用。你可以通过浏览器访问 <code>http://localhost:5244</code> 进入管理界面。默认用户名为 <code>admin</code>，初始密码可以通过环境变量设置，或者在容器日志中查看。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f06557eb12f1646385c081bfa863fec0.png" alt="f06557eb12f1646385c081bfa863fec0"></p><p>我用的是 Orbstack，可以很方便的查看容器日志。如果你使用的是 Docker cli，也可以使用 docker logs 进行查看。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ff036c9af184b24239b0729beccad6c8.png" alt="ff036c9af184b24239b0729beccad6c8"></p><h3 id="步骤-2：配置-S3-存储"><a href="#步骤-2：配置-S3-存储" class="headerlink" title="步骤 2：配置 S3 存储"></a>步骤 2：配置 S3 存储</h3><p>一开始，OpenList 容器没有绑定任何存储，所以页面将显示为空白。此时需要点击右下角的“管理”按钮，进入存储配置界面。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/671adf2b04ec081b09073583b0a67307.png" alt="671adf2b04ec081b09073583b0a67307"></p><ol><li>在“存储”选项卡下，选择“对象存储”作为存储类型。</li><li>配置挂载路径（例如 <code>/s3</code>），这相当于 Linux 系统中的挂载目录。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ad1ec0280dbbb949229bb1098f5452fc.png" alt="ad1ec0280dbbb949229bb1098f5452fc"></li><li>输入你的 S3 存储桶的名称、区域和访问密钥。确保使用正确的 S3 endpoint。<br>我的存储桶位于东京，因此我在配置中使用了 <code>s3.ap-northeast-1.amazonaws.com</code> 作为 endpoint。如果你的存储桶位于其他区域，记得修改为相应区域的 endpoint。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729184150981.png" alt="image-20250729184150981"><br>为了方便获取你的 AWS 凭证，可以使用以下命令获取当前机器绑定的凭证：</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">pip install awsx</span><br></pre></td></tr></table></figure><p>如果你使用 <code>uv</code> 管理 Python 环境，可以运行以下命令打印当前用户名和使用的 Access Key 以及 Secret Key：</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">uvx awsx</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729185603905.png" alt="image-20250729185603905"></p><h3 id="步骤-3：配置重定向"><a href="#步骤-3：配置重定向" class="headerlink" title="步骤 3：配置重定向"></a>步骤 3：配置重定向</h3><p>与许多 SDK 的重定向机制不同，如果你在配置中错误地设置了美东区的 endpoint，OpenList 客户端将不会自动在收到 <code>301</code> 重定向响应后转发请求到正确的区域，而是会报错。</p><p>例如，如果你将 endpoint 设置为 <code>s3.us-east-1.amazonaws.com</code>，但存储桶位于 <code>ap-northeast-1</code> 区域，你将遇到以下错误：</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">BucketRegionError: incorrect region, the bucket is not in &#x27;ap-northeast-1&#x27; region at endpoint &#x27;s3.us-east-1.amazonaws.com&#x27;</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/19fe3ef40d5397199b06e22d0c1a9131.png" alt="19fe3ef40d5397199b06e22d0c1a9131"></p><p>解决方法是确保在配置中使用正确的区域，避免跨区域错误。</p><h3 id="步骤-4：启用-MFA（可选）"><a href="#步骤-4：启用-MFA（可选）" class="headerlink" title="步骤 4：启用 MFA（可选）"></a>步骤 4：启用 MFA（可选）</h3><p>为了提高安全性，尤其是在将 OpenList 部署到公网环境时，建议启用多重身份验证（MFA）。启用 MFA 可以增加 AWS 账户的安全性，避免潜在的安全风险。</p><p>在 AWS 控制台中启用 MFA 后，记得更新 OpenList 中的凭证配置，确保启用了双重认证。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d55c835493702d0c535ee2c03b275899.png" alt="d55c835493702d0c535ee2c03b275899"></p><h3 id="步骤-5：配置用户权限"><a href="#步骤-5：配置用户权限" class="headerlink" title="步骤 5：配置用户权限"></a>步骤 5：配置用户权限</h3><p>OpenList 默认情况下将用户权限设置为只读。要赋予 <code>admin</code> 用户 WebDAV 的管理权限，请进入“用户 - 编辑”界面，修改相应的权限设置。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1163f857b7f54d79f578d0efccc3ea10.png" alt="1163f857b7f54d79f578d0efccc3ea10"></p><h3 id="步骤-6：访问-S3-文件"><a href="#步骤-6：访问-S3-文件" class="headerlink" title="步骤 6：访问 S3 文件"></a>步骤 6：访问 S3 文件</h3><p>完成配置后，OpenList 将自动同步 S3 存储桶的数据。你可以在 Web 界面上方便地进行文件下载、解压、上传文件等操作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729190608521-20250729191041135.png" alt="image-20250729190608521"></p><p>这个是 S3 上页面，可以看到 s3 的数据都被同步到 Openlist 上了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729191033044.png" alt="image-20250729191033044"></p><p>同时也能够在 Openlist 上在线观看 S3 上的存的视频教程。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729190938564.png" alt="image-20250729190938564"></p><p>同时所有操作都可以通过 WebDAV 协议进行，访问路径为：</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">http(s)://&lt;ip&gt;/dav</span><br></pre></td></tr></table></figure><p>例如，在 MacOS 上，可以通过 Finder 进行 WebDAV 访问：</p><ol><li>在 Finder 中选择“前往”&gt;“连接服务器”。</li><li>输入 WebDAV 路径，例如：<code>http://localhost:5244/dav</code>。</li><li>输入 OpenList 的用户名和密码进行身份验证。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/330ca92c-9cec-4366-ae66-2f6b25db8fe2.png" alt="image.png"></p><p>在 Finder 中使用 WebDAV 进行访问：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bfe32cbb82f531b34fbf803b96c9c71a.png" alt="bfe32cbb82f531b34fbf803b96c9c71a"></p><p>你还可以使用 Linux 命令行来操作 WebDAV，减少了学习 S3 命令行的成本。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250729184439882.png" alt="image-20250729184439882"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过使用 OpenList，我们可以轻松地将 Amazon S3 转换为 WebDAV，简化了文件访问和管理。通过本文的步骤，你可以快速启动 OpenList、配置 S3 存储桶，并通过 WebDAV 协议访问存储在 S3 上的文件。希望这篇文章能帮助你更高效地管理 S3 数据，并为你提供更加便捷的文件访问方式。</p>]]></content>
    
    
    <summary type="html">通过 OpenList 将 Amazon S3 存储转换为 WebDAV 协议访问</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>再也不用拼手速！一条命令直接进 BIOS（Windows/Linux/macOS 全攻略)</title>
    <link href="https://blog.no-claw.com/posts/a0b67926/"/>
    <id>https://blog.no-claw.com/posts/a0b67926/</id>
    <published>2025-07-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>看了大狸子的教程, 尝试了一下，顺便补充了其他平台的命令。</p></blockquote><p>很多朋友在装系统、改启动项、开虚拟化（VT-x）、关闭安全启动的时候，都需要进入 BIOS&#x2F;UEFI。但平时开机要拼命按 <strong>DEL&#x2F;F2&#x2F;F10&#x2F;ESC</strong> 等快捷键，手速还要够快，稍微慢一点就错过了。</p><span id="more"></span><p>其实在 Windows 和 Linux 系统里，都有“命令行直达 BIOS”的办法，简单又优雅。今天给大家写个全攻略。</p><hr><h4 id="一、Windows-系统"><a href="#一、Windows-系统" class="headerlink" title="一、Windows 系统"></a>一、Windows 系统</h4><h5 id="1-命令行进入-BIOS"><a href="#1-命令行进入-BIOS" class="headerlink" title="1. 命令行进入 BIOS"></a>1. 命令行进入 BIOS</h5><p>在 Windows 10&#x2F;11 打开 <strong>命令提示符（管理员）</strong> 或 PowerShell（管理员），输入：</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">shutdown /r /fw /t 0</span><br></pre></td></tr></table></figure><p>参数说明：</p><ul><li><code>/r</code> → 重启</li><li><code>/fw</code> → 重启后进入 BIOS&#x2F;UEFI 固件设置</li><li><code>/t 0</code> → 立即执行（默认是 30 秒倒计时）</li></ul><p>执行后，电脑会立刻重启并直接进入 BIOS。</p><p>⚠️ 注意：</p><ul><li>必须是 <strong>UEFI 启动模式</strong>才支持 <code>/fw</code>，如果是 Legacy BIOS 会报错 “找不到环境选项(203)”。</li><li>可以在命令行里输入 <code>msinfo32</code>，检查 “BIOS 模式” 是否为 UEFI。</li></ul><h5 id="2-系统设置进入"><a href="#2-系统设置进入" class="headerlink" title="2. 系统设置进入"></a>2. 系统设置进入</h5><p>如果命令报错，可以这样操作：</p><ol><li>打开 <strong>设置 → 更新和安全 → 恢复</strong></li><li>在 “高级启动” 点击 <strong>立即重新启动</strong></li><li>依次选择 <strong>疑难解答 → 高级选项 → UEFI 固件设置 → 重启</strong></li></ol><hr><h4 id="二、Linux-系统"><a href="#二、Linux-系统" class="headerlink" title="二、Linux 系统"></a>二、Linux 系统</h4><p>Linux 用户也能一键进入固件设置：</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">systemctl reboot --firmware-setup</span><br></pre></td></tr></table></figure><p>这个命令支持大多数基于 <code>systemd</code> 的发行版（Ubuntu、Debian、Fedora、Arch 等）。</p><p>如果提示不支持，那就只能用开机热键进入。</p><hr><h4 id="三、macOS-系统"><a href="#三、macOS-系统" class="headerlink" title="三、macOS 系统"></a>三、macOS 系统</h4><p>Mac 没有传统 BIOS，只有 EFI 设置。</p><ul><li><p><strong>Intel Mac</strong>：</p><ul><li>开机时长按 <strong>Option (⌥)</strong> 进入启动磁盘选择界面。</li><li>长按 <strong>Command + Option + P + R</strong> → 重置 NVRAM&#x2F;PRAM，相当于恢复固件设置。</li></ul></li><li><p>**Apple Silicon (M1&#x2F;M2&#x2F;M3)**：</p><ol><li>完全关机</li><li>长按 <strong>电源键</strong>，直到出现“启动选项”</li><li>点击 “选项” 进入恢复模式，在里面可以修改启动安全策略等。</li></ol></li></ul><hr><h4 id="四、常见品牌-BIOS-快捷键速查表"><a href="#四、常见品牌-BIOS-快捷键速查表" class="headerlink" title="四、常见品牌 BIOS 快捷键速查表"></a>四、常见品牌 BIOS 快捷键速查表</h4><table><thead><tr><th>品牌</th><th>快捷键</th></tr></thead><tbody><tr><td>联想 ThinkPad</td><td>F1</td></tr><tr><td>联想 IdeaPad</td><td>F2</td></tr><tr><td>华硕 ASUS</td><td>F2 或 DEL</td></tr><tr><td>惠普 HP</td><td>ESC 或 F10</td></tr><tr><td>戴尔 Dell</td><td>F2</td></tr><tr><td>宏碁 Acer</td><td>F2 或 DEL</td></tr><tr><td>微星 MSI</td><td>DEL</td></tr><tr><td>技嘉 GIGABYTE</td><td>DEL</td></tr><tr><td>微软 Surface</td><td>音量加 + 电源键</td></tr></tbody></table><hr><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><ul><li><strong>Windows 用户</strong>：最推荐 <code>shutdown /r /fw /t 0</code>，前提是 UEFI 模式。</li><li><strong>Linux 用户</strong>：推荐 <code>systemctl reboot --firmware-setup</code>。</li><li><strong>Mac 用户</strong>：通过开机组合键进入固件设置。</li></ul><p>以后再也不用拼手速狂按 F2&#x2F;DEL 了，直接用命令行一键进 BIOS，优雅又高效。</p>]]></content>
    
    
    <summary type="html">一条命令直接进入 BIOS，Windows/Linux/macOS 全平台方法汇总</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>TrackWeight, 把 Macbook 的触控板当作电子秤</title>
    <link href="https://blog.no-claw.com/posts/10473fce/"/>
    <id>https://blog.no-claw.com/posts/10473fce/</id>
    <published>2025-07-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>大数据推送的，地址在这里 <a href="https://github.com/KrishKrosh/TrackWeight/">https://github.com/KrishKrosh/TrackWeight/</a></p><p>最新的 release 打开一开始是这样，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250727151730910.png" alt="image-20250727151730910"></p><p>然后给作者留言，修复了 MacOS13 上不能运行的问题。（不到 10 分钟作者就改好了 release）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c5f42ac2-9d88-4106-88b9-1f423a87de83.png" alt="c5f42ac2-9d88-4106-88b9-1f423a87de83"></p><p>这个程序需要手指始终触摸板，然后一开始需要校准。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250727152021019.png" alt="image-20250727152021019"></p><p>校准之后就可以放置物品了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250727152049079.png" alt="image-20250727152049079"></p><p>除了手有点抖之外，总体还不错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250727152145456.png" alt="image-20250727152145456"></p><p>这个页面也很好看，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250727152010149.png" alt="image-20250727152010149"></p>]]></content>
    
    
    <summary type="html">用 TrackWeight 将 MacBook 触控板变成电子秤的趣味体验</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="MacOS" scheme="https://blog.no-claw.com/tags/MacOS/"/>
    
  </entry>
  
  <entry>
    <title>记一次iphone的死机</title>
    <link href="https://blog.no-claw.com/posts/b81a6bca/"/>
    <id>https://blog.no-claw.com/posts/b81a6bca/</id>
    <published>2025-07-27T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>早上起来发个微信，拍照片，然后 IOS 就死机了。<br>能正常推送 app 通知，电话也能正常打进来。表面现象是屏幕失灵，主板功能正常。</p><p>路人给出的排查：</p><span id="more"></span><ol><li>手机内屏坏了</li><li>内屏触点松了</li><li>屏幕积液</li></ol><p>到了 apple 线下直接强制重启，按一下+， 再按一下- ，然后长按电源键。然后重启解决了。目测是没有硬件问题，只是 IOS 的崩溃。iphone13 也不能再更新系统了。</p><p>我也用 GPT 搜到了这个步骤，只是需要按压 20 秒，我按的时间不够。</p><p>你遇到了这个问题，可以长按超过 20 秒来重启。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2025/09/02/1756827659677-a8080742-c89a-4a43-b09b-25c45e7f9f5c.png"></p>]]></content>
    
    
    <summary type="html">记录一次 iPhone 屏幕失灵死机的排查与解决过程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
    <category term="手机" scheme="https://blog.no-claw.com/tags/%E6%89%8B%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>AWS Credit 充值到账户全流程图文指南</title>
    <link href="https://blog.no-claw.com/posts/11273243/"/>
    <id>https://blog.no-claw.com/posts/11273243/</id>
    <published>2025-07-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在云计算的世界里，<strong>AWS Credit（代金券）</strong> 就像是给大家的一张“云上优惠券”。很多朋友可能是在参加 <strong>黑客松、社区活动、竞赛</strong> 时拿到的，也有的是通过 <strong>AWS Educate、Activate 初创企业计划</strong> 领取的。无论来源如何，这些 Credit 都能在你使用 AWS 服务时抵扣费用，帮大家节省真金白银。</p><p>但是，Credit 拿到手之后，如何把它“充值”到自己的 AWS 账户里？下面我结合实际操作截图，为大家做一个完整的图文教程。</p><h3 id="一、进入-AWS-Billing-控制台"><a href="#一、进入-AWS-Billing-控制台" class="headerlink" title="一、进入 AWS Billing 控制台"></a>一、进入 AWS Billing 控制台</h3><p>首先，登录 <a href="https://console.aws.amazon.com/">AWS 管理控制台</a>，在搜索栏里输入 <strong>Billing</strong>。</p><p>进入 <strong>Billing Dashboard</strong>，就能看到当前账户的费用情况，包括账单、支付方式和 Credit 使用情况。</p><p>👉 示例截图如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250830104755735.png" alt="image-20250830104755735"></p><p>在这个总览页面，大家可以随时掌握自己账户的消费情况。</p><h3 id="二、查看账户已绑定的-Credits"><a href="#二、查看账户已绑定的-Credits" class="headerlink" title="二、查看账户已绑定的 Credits"></a>二、查看账户已绑定的 Credits</h3><p>接着，在左侧菜单栏找到 <strong>Credits</strong>，进入代金券管理页面。</p><p>这里会列出所有已经绑定到你账户的 Credit，包括：</p><ul><li>金额（Amount）</li><li>有效期（Expiration Date）</li><li>剩余可用额度（Remaining Balance）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250830095200413.png" alt="Credit 页面"></p><h3 id="三、兑换新的-Credit"><a href="#三、兑换新的-Credit" class="headerlink" title="三、兑换新的 Credit"></a>三、兑换新的 Credit</h3><p>如果你手上有 AWS 发放的 <strong>Promotion Code（兑换码）</strong>，就可以在这里点击 <strong>兑换积分</strong>，输入兑换码完成绑定。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250830095238750-20250830104934018.png" alt="验证 Credit"></p><p>👉 输入兑换码示例：（我这个是刚刚兑换完）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250830094900872.png" alt="Redeem Credit"></p><p>点击 <strong>Redeem</strong> 后，系统会提示你“成功绑定”，这就说明 Credit 已经充值到账户啦！</p><h3 id="四、验证充值是否成功"><a href="#四、验证充值是否成功" class="headerlink" title="四、验证充值是否成功"></a>四、验证充值是否成功</h3><p>绑定成功后，再回到 <strong>Credits 页面</strong>，就能看到新的 Credit 已经显示出来。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250830105006823.png" alt="image-20250830105006823"></p><p>接下来，AWS 在结算账单时会 <strong>优先从 Credit 中抵扣</strong>，只有当 Credit 用尽或过期，才会从银行卡&#x2F;信用卡中实际扣款。</p><p>这意味着，只要你有 Credit，账户就能“免费”使用 AWS 服务一段时间。</p><h3 id="五、常见问题解答"><a href="#五、常见问题解答" class="headerlink" title="五、常见问题解答"></a>五、常见问题解答</h3><ol><li><strong>Credit 可以转到其他账户吗？</strong><br>不行，AWS Credit 只能充值到指定的账户，无法转让。</li><li><strong>哪些服务可以用 Credit 抵扣？</strong><br>大部分 AWS 原生服务（如 EC2、S3、RDS、Lambda）都支持。部分 <strong>Marketplace 第三方产品</strong> 可能无法使用。</li><li><strong>Credit 会不会过期？</strong><br>会的！每个 Credit 都有有效期，过期之后就无法使用。所以大家要注意在有效期内消耗掉。</li><li><strong>扣费顺序是怎样的？</strong><br>系统会优先消耗 Credit，Credit 用尽后才会从绑定的支付方式扣款。</li></ol><h3 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h3><p>整体流程其实非常简单：</p><p>👉 登录 <strong>Billing Console</strong> → <strong>Credits 页面</strong> → <strong>Redeem credit</strong> → 输入兑换码 → 验证到账。</p><p>这样，你就能安心使用 AWS 服务，账单会自动优先从 Credit 扣除，大大节省云上的支出。</p><h3 id="七、使用场景举例"><a href="#七、使用场景举例" class="headerlink" title="七、使用场景举例"></a>七、使用场景举例</h3><ul><li>学生开发者用 Educate Credit 免费部署个人网站；</li><li>初创团队通过 Activate Credit 在 AWS 上搭建 MVP，降低前期成本；</li><li>参加黑客松拿到的 Credit，用来训练 AI 模型或做大数据实验。</li></ul><p>对个人和企业来说，合理使用 AWS Credit，能帮助大家 <strong>快速试错、降低成本、加速创新</strong>。</p><p>🔥 小结一句：<strong>AWS Credit 不仅是一张优惠券，更是你云上实验和创新的“启动资金”。</strong></p>]]></content>
    
    
    <summary type="html">图文详解 AWS Credit 代金券充值到账户的完整操作流程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十四）：在懒猫微服中使用群晖</title>
    <link href="https://blog.no-claw.com/posts/815f771a/"/>
    <id>https://blog.no-claw.com/posts/815f771a/</id>
    <published>2025-07-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>各种 OS in docker 的操作很多，包括 debian，arch，windows，Macos，安卓，这次甚至带来了黑群晖 in docker，基本上都是基于 QUME 的虚拟化来做的。也希望哪天有也有 docker 版本的 QNAP 吧。NAS 一家人就要整整齐齐。（unraid 估计没戏 哈哈哈哈哈）</p><p><a href="https://appstore.lazycat.cloud/#/shop/detail/chestnut.app.vdsm">https://appstore.lazycat.cloud/#/shop/detail/chestnut.app.vdsm</a></p><p>从懒猫微服商店就可以直接下载了，群友上传了 release。</p><span id="more"></span><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a742a8a6-80b0-480f-b37e-2556f461b908.png" alt="image.png" title="image.png"></p><p>开源地址在这里：<a href="https://github.com/vdsm/virtual-dsm">https://github.com/vdsm/virtual-dsm</a></p><p>也不需要再搞群晖引导啥的，直接一件安装很方便，最后设置下用户名和密码就可以了。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/09a3feda-3933-4346-ace0-0fd9138e8715.png" alt="image.png" title="image.png"></p><p>版本是目前最新的 DSM7.22，相信群晖应该是很多爱好者的第一个 NAS 系统。这些年我一路从 DSM6 走过来，群晖的系统 UI 是越来越好看了。第一件是必须是安装套件，激活文件管理器。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/b38ef86a-9632-4b8c-9780-bd775641c0ea.png" alt="image.png" title="image.png"></p><p>美中不足的是，没有找到群晖的虚拟机管理器。也罢，反正是虚拟机，VM in VM 性能损耗很大，倒是 Docker in Docker 还能接受。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/907e5cc7-65cc-430d-8b55-0eddcf4b4ce9.png" alt="image.png" title="image.png"></p><p>虽然这个存储空间只有 16G，不过嘛，感觉这个大小其实适合尝鲜或者做已有群晖的 backup。不过机械硬盘倒是没有明显的卡顿。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/265c0179-e950-4ae9-bf10-65e27de5c367.png" alt="image.png" title="image.png"></p><p>去应用查看器翻了一下，磁盘大小竟然是在环境变量写死的，那么在 V1.38+的 OS 也能通过修改环境变量来换成更大的空间，毕竟数据盘都是 HDD 不差这点空间。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9ad0f301-0e64-4612-8248-a0a69e9c9d55.png" alt="image.png" title="image.png"></p><p>因为我已经有了一个物理机的群晖，上面也跑着虚拟机和打印机驱动的一些软件，所以这个群晖更多是尝鲜，或者说组成一个集群双活。磁盘太小的话，那么我就添加外部的 SMB，其实就是懒猫网盘。</p><p>在群晖的文件管理器中 - 工具 - 装载远程文件夹 - CIFS 共享文件夹。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/7aa44029-ad4a-4f14-ab20-93ad0f524b04.png" alt="image.png" title="image.png"></p><p>然后输入 SMB 的信息。需要事先选中一个空文件夹。</p><p>这个时候发挥 Linux 的 mount 哲学了 hhhhh</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d6d52f52-675c-451b-b308-814a30508c85.png" alt="image.png" title="image.png"></p><p>然后把群晖自己 SMB 映射出去，还能做一个 SMB 的存储网关。</p><p>懒猫网盘和 share 都是 SMB 挂载的其他设备。也能通过这个虚拟群晖的 SMB 一起访问。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/46fc5f27-7378-4d5d-84bf-12ec307166c8.png" alt="image.png" title="image.png"></p><p>怎么洗白？都有懒猫微服穿透和相册了？不用洗了吧！</p>]]></content>
    
    
    <summary type="html">在懒猫微服中运行群晖系统，一台设备同时享受两套 NAS 生态。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>家居常用测网速软件</title>
    <link href="https://blog.no-claw.com/posts/73f93987/"/>
    <id>https://blog.no-claw.com/posts/73f93987/</id>
    <published>2025-07-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>中兴路由器测速的软件</p><p>speedtest 和中科大测速有时候抽风，中兴自带的还不错</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250726224900260.png" alt="image-20250726224900260"></p><p>手机也可以使用全球网测，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250726231345139.png" alt="image-20250726231345139"></p><p>安卓使用花瓣测速</p>]]></content>
    
    
    <summary type="html">家居场景下常用的网络测速与管理软件推荐</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十三）：使用 GitHub 单点登录到 Memos</title>
    <link href="https://blog.no-claw.com/posts/b10cd1f4/"/>
    <id>https://blog.no-claw.com/posts/b10cd1f4/</id>
    <published>2025-07-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前我们介绍了如何使用 Memos 替代 Github，那这次我们来进阶一下，给 Memos 添加 Github 单点登录，如果你开发的应用后续想接入 Github 也可以采取这种办法。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250724093520542.png" alt="image-20250724093520542"></p><span id="more"></span><h2 id="一、创建-GitHub-OAuth-应用"><a href="#一、创建-GitHub-OAuth-应用" class="headerlink" title="一、创建 GitHub OAuth 应用"></a>一、创建 GitHub OAuth 应用</h2><h3 id="1-打开-GitHub-OAuth-应用配置入口"><a href="#1-打开-GitHub-OAuth-应用配置入口" class="headerlink" title="1. 打开 GitHub OAuth 应用配置入口"></a>1. 打开 GitHub OAuth 应用配置入口</h3><ol><li>登录 GitHub，点击右上角头像，选择 <strong>Settings（设置）</strong></li><li>在左侧栏选择 <strong>Developer settings</strong></li><li>点击 <strong>OAuth Apps</strong>，然后点击右侧的 <strong>New OAuth App</strong> 创建新的应用</li></ol><p>📷 页面示意：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20250713224449968-20250713225034801.png" alt="创建 OAuth 应用"></p><hr><h3 id="2-填写应用基本信息"><a href="#2-填写应用基本信息" class="headerlink" title="2. 填写应用基本信息"></a>2. 填写应用基本信息</h3><p>在创建页面中填写如下字段：</p><ul><li><p><strong>Application Name</strong>：如 <code>Console SSO</code></p></li><li><p><strong>Homepage URL</strong>：建议填写系统主页，例如 <code>https://memos.name.heiyu.space</code></p></li><li><p><strong>Authorization callback URL</strong>：授权成功后的回调地址，格式如下：</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">https://memos.name.heiyu.space/auth/callback</span><br></pre></td></tr></table></figure></li></ul><p>📷 示例填写界面：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250713224139574.png" alt="填写信息"></p><hr><h3 id="3-获取-Client-ID-和-Client-Secret"><a href="#3-获取-Client-ID-和-Client-Secret" class="headerlink" title="3. 获取 Client ID 和 Client Secret"></a>3. 获取 Client ID 和 Client Secret</h3><p>提交后，GitHub 将生成：</p><ul><li><strong>Client ID</strong>：用于标识该 OAuth 应用</li><li><strong>Client Secret</strong>：用于身份验证，请妥善保存，<strong>不要泄露！</strong></li></ul><p>📷 凭证界面如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640" alt="获取客户端凭证"></p><hr><h2 id="二、查看和管理-OAuth-应用"><a href="#二、查看和管理-OAuth-应用" class="headerlink" title="二、查看和管理 OAuth 应用"></a>二、查看和管理 OAuth 应用</h2><p>创建完成后，返回 OAuth 应用列表，即可看到刚创建的应用。</p><p>点击应用名可查看授权信息和应用详情：</p><p>📷 应用列表和详情视图：</p><ul><li><p>应用列表页面<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20250713224027323-20250713224450558-20250713225035672" alt="OAuth 应用列表"></p><p>然后邮件会收到 Github 绑定 Oauth 的通知。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bb21e51f43bf5eedd8b6c44ef27e3fd9-20250713225035879.png" alt="授权详情"></p></li></ul><hr><h3 id="4-在-Memos-中配置-GitHub-登录"><a href="#4-在-Memos-中配置-GitHub-登录" class="headerlink" title="4. 在 Memos 中配置 GitHub 登录"></a>4. 在 Memos 中配置 GitHub 登录</h3><p>进入系统设置页面：</p><ol><li>点击左下角齿轮图标（设置）</li><li>选择 <strong>单点登录（SSO）</strong></li><li>选择 GitHub 作为登录方式，填写刚获取的 Client ID 和 Client Secret</li></ol><p>📷 Memos 配置页面示例：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250713224629619.png" alt="设置 GitHub 单点登录"></p><p>配置保存后，注销当前账号，登录页面会显示 GitHub 登录按钮。</p><p>📷 登录页面展示效果：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/26dac7e6f9531a83010a5cc44e95c71f-20250713225036385.png" alt="GitHub 登录入口"></p><p>通过以上步骤，我们完成了 GitHub 登录的接入流程：</p><p>✅ 创建并配置 GitHub OAuth 应用<br>✅ 获取并填入凭证<br>✅ 在 Memos 中启用 OAuth 登录</p><p>最后成员列表一览：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250724093626741.png" alt="image-20250724093626741"></p>]]></content>
    
    
    <summary type="html">配置 GitHub OAuth 实现单点登录到懒猫微服上的 Memos 笔记应用。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服炫技篇（三）：sunshine+moonlight 双人串流打游戏</title>
    <link href="https://blog.no-claw.com/posts/2d63e359/"/>
    <id>https://blog.no-claw.com/posts/2d63e359/</id>
    <published>2025-07-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前在懒猫商店上架了坦克大战，一直想着怎么双人一起玩的问题。现在的年轻人除了逢年过节之外很难凑在一起，所以就想着能不能通过远程共享+键盘映射的办法远程双人游戏呢？碰巧刷到了司波图的 NAS 串流打游戏视频，加上之前一直被朋友安利 sunshine+moonlight 的组合，远程 linux 比 VNC 和 XRDP 都要好很多。所以我也来试试。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250725224446427.png" alt="image-20250725224446427"></p><p>下载地址：<a href="https://app.lizardbyte.dev/Sunshine/?lng=zh-CN">https://app.lizardbyte.dev/Sunshine/?lng=zh-CN</a></p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250723093125009.png" alt="image-20250723093125009"></p><p>懒猫微服默认没有图形化，所以直接安装串流服务端也没有意义。于是我用懒猫开启了一个 Windows 虚拟机来做服务器，关于后面怎么安装 windows 虚拟机，后面来讲，或者你也可以找一台物理机 windows 来安装懒猫微服的客户端。</p><p>然后把 windows 作为串流的服务端（安装 sunshine）。直接 EXE 安装，然后启动之后在浏览器中设置一下串流的密码就好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/479b2d3fdbb764c010e8c35236862f40.png" alt="479b2d3fdbb764c010e8c35236862f40"></p><p>为什么不用 RDP？</p><p>RDP 的原生限制，想两个人连接同一个屏幕操作，需要改注册表，很麻烦，所以放弃了这个方案。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e04ff54b9ba80dc8f8f7923455b51008.png" alt="e04ff54b9ba80dc8f8f7923455b51008"></p><p>客户端下载 Monnlight，然后会自动发现局域网设备。（如果是广域网可以考虑组网，朋友之前有试过也可以串流玩 stream）</p><p>客户端加入的时候可以使用 PIN 认证，在服务端输入客户端认证弹出的 PIN，然后再输入设备名称。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cc64e015fad0619e5441c1ede72645b2.png" alt="cc64e015fad0619e5441c1ede72645b2"></p><p>然后在客户端点击 Desktop 就可以了，第二个设备也可以重复这个操作，实测不会把第一个串流的 Session 挤掉。</p><p>我的目的就是用服务器打开懒猫微服的网页，然后两个客户端同时串流玩双人的游戏。</p><p>如果是大型游戏的话，可以直接直接从商店下载部署版本，但是这种小游戏还想娱乐一下的，两个人用串流一起玩还是听方便的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3958f12f11abf32561f7eceece4ecacc.png" alt="3958f12f11abf32561f7eceece4ecacc"></p><p>能够看到我用 Ipad 和 Macbook pro 同时串流懒猫微服里的坦克大战，玩双人游戏还可以基本没啥问题的。（只是稍微有点点卡）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d861b19c0942841ebfb136b103063945.jpg" alt="d861b19c0942841ebfb136b103063945"></p><p>也算是圆了一个梦吧。</p>]]></content>
    
    
    <summary type="html">在懒猫微服上用 Sunshine+Moonlight 实现远程双人串流游戏</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="炫技" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%82%AB%E6%8A%80/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服买硬件送服务，刚学的计算机知识顺便接了个单</title>
    <link href="https://blog.no-claw.com/posts/cb1ae078/"/>
    <id>https://blog.no-claw.com/posts/cb1ae078/</id>
    <published>2025-07-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>事先声明，懒猫微服不提供接单服务，但是可以通过贡献攻略和移植应用赚取激励。</p></blockquote><p>挺有意思的一个事，头几天刚刚找过懒猫微服的技术帮我配置 cloudflare 相关操作，顺便学习了一下基本使用，把自己在 AWS 的 Route53 上购买的域名迁移过去了，然后代理到了博客，AWS EC2 服务器，甚至家里的机器。</p><p>偶然间在微信群看到这样一个需求，这不就是前两天懒猫微服的技术人员手把手教我做的。cloudflare 有很多操作，之前周围的人还有使用 cloudflare 反向代理到家里的 NAS，然后 obsidian 实时同步笔记连回家的。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720131125114.png" alt="image-20250720131125114"></p><p>于是我给了他三个方案：</p><ol><li>最省心的：cloudflare 只做域名解析，应用无缝迁移到 Linux 服务器上。</li><li>全托管的：cloudflare 做域名解析，然后利用 cloudflare 的 Worker 部署后端</li><li>最日常的：使用 cloudflare tunnel 反向代理到家里电脑。</li></ol><p>客户选择了方案 3，然后接下来就是配置 cloudflare tunnel，甚至不用再配置 A 记录。</p><h2 id="🌐-Cloudflare-Tunnel-能干什么？"><a href="#🌐-Cloudflare-Tunnel-能干什么？" class="headerlink" title="🌐 Cloudflare Tunnel 能干什么？"></a>🌐 Cloudflare Tunnel 能干什么？</h2><ul><li>把本地 Web 服务（如网站、应用、API、NAS）通过 Cloudflare 安全暴露到公网</li><li><strong>无需公网 IP</strong>，不管你是在家庭宽带、NAT、内网还是 IPv6-only 网络都能跑</li><li>通过 Cloudflare 的全球 CDN 加速和防护（DDoS 保护、TLS、WAF）</li><li>支持访问控制（如 Zero Trust）</li><li>支持反向代理多服务（如 <code>/app1</code>, <code>/app2</code>）或多个子域名绑定</li></ul><p>首先登录到<a href="https://dash.cloudflare.com/%E9%A6%96%E9%A1%B5%E3%80%82%E7%82%B9%E5%87%BBZero-Trust%E3%80%82">https://dash.cloudflare.com/首页。点击Zero-Trust。</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720132333168.png" alt="image-20250720132333168"></p><p>然后选择 网络 - Tunnels ，然后新建隧道来内网穿透。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720144304637.png" alt="image-20250720144304637"></p><p>选择创建隧道，这个哥们是 Windows 的环境，所以隧道类型使用 Cloudflared。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720144329817.png" alt="image-20250720144329817"></p><p>然后选择新建隧道，然后输入隧道名称。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720144350889.png" alt="image-20250720144350889"></p><p>这个时候选择安装 cloudflared 引擎，需要安装一个 agent，基本是全平台都有，甚至还有 Docker 版本的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720144633620.png" alt="image-20250720144633620"></p><p>然后把 test 子域代理本地的 localhost:8000。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720145038577.png" alt="image-20250720145038577"></p><p>然后通过域名访问就可以了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250720145308297.png" alt="image-20250720145308297"></p><p>这哥们还有一个额外的要求，要开启启动天  Cloudflared ，然后 GPT 了一下</p><p>直接启动已安装的  Cloudflared  服务</p><p>运行 Start-Service cloudflared 启动服务</p><p>使用 Get-Service cloudflared 查看服务状态</p><p>设为开机自启</p><p>运行 Set-Service cloudflared -StartupType Automatic 将</p><p>Cloudflared 设置为自动启动</p><blockquote><p>结语</p><p>买 NAS 学的是网络技术，虽然可能是别人眼中的野路子。但是多一分趣味嘛。切身感受到技术的意义。</p></blockquote>]]></content>
    
    
    <summary type="html">入手懒猫微服后用所学计算机知识帮人部署服务的真实经历</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="Cloudflared" scheme="https://blog.no-claw.com/tags/Cloudflared/"/>
    
  </entry>
  
  <entry>
    <title>GIT小书</title>
    <link href="https://blog.no-claw.com/posts/fa9f211a/"/>
    <id>https://blog.no-claw.com/posts/fa9f211a/</id>
    <published>2025-07-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h4 id="第一章：不明觉厉的-Git"><a href="#第一章：不明觉厉的-Git" class="headerlink" title="第一章：不明觉厉的 Git"></a><strong>第一章：不明觉厉的 Git</strong></h4><p>小李刚从大学毕业，加入了一家快速发展的初创公司，成为了公司的前端开发工程师。这是他人生中的第一份正式工作，他兴奋又忐忑。虽然他在学校里学过一些编程技术，但真正的项目经验还很薄弱。第一天，老板就把他分配到了一个正在开发的 Web 项目中，需要用 Git 进行版本管理。</p><p>“小李，这是你需要参与的项目，我们已经把代码推到 GitHub 上了，记得拉取下来工作。”老板简短的几句话让小李有点懵。</p><p>“GitHub？拉取？我听说过 Git，但从来没用过。”小李在心里嘀咕着。他记得在学校的课堂上，老师提到过 Git 作为一种版本控制工具，可以帮助开发团队协作，但具体怎么使用，还是个谜。</p><span id="more"></span><p>“小李，别担心，我们的团队里有很多 Git 使用经验丰富的人，你可以请教他们。”老板似乎察觉到他的一丝不安，轻轻拍了拍他的肩膀。“首先，你得把代码克隆到本地。”</p><p>“克隆？那是什么？”小李心中更是一阵迷茫。</p><p>他的同事小王看出了他的困惑，走过来笑着解释：“Git 是一种分布式的版本控制系统，‘克隆’是从远程仓库复制一份代码到你本地电脑上的操作。你只需要使用 <code>git clone</code> 命令，把我们的仓库拉下来就行了。”</p><p>小李点点头，拿起电脑，打开命令行，准备开始他的 Git 之旅。</p><h4 id="第二章：第一次克隆"><a href="#第二章：第一次克隆" class="headerlink" title="第二章：第一次克隆"></a><strong>第二章：第一次克隆</strong></h4><p>小李根据小王的提示，打开了 GitHub，找到了公司项目的仓库链接。接着，他按照小王的指示，输入了以下命令：</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">git clone https://github.com/company/project-repo.git</span><br></pre></td></tr></table></figure><p>他按下回车键，屏幕上出现了下载的进度条，Git 开始从远程仓库将项目文件拉取到本地。当下载完成后，他看到自己的本地目录下多了一个与仓库同名的文件夹，这时，他才恍若大悟，原来 Git 仓库就像是一个储藏库，而 <code>git clone</code> 命令就是让这个储藏库的内容变成了他自己本地的副本。</p><p>“小王，这样我就能开始写代码了吗？”小李兴奋地问。</p><p>“是的！不过在你修改代码之前，你需要查看一下当前的状态。” 小王笑了笑，接着说：“输入 <code>git status</code> 命令，它会告诉你当前文件夹里的文件是否已被 Git 跟踪，是否有变更。”</p><p>小李按照提示输入了 <code>git status</code>，屏幕上显示出一长串信息，告诉他哪些文件被修改了，哪些文件没有被 Git 跟踪。</p><p>“原来 Git 会这么细致地记录每个文件的变化啊！太厉害了。” 小李感慨道。</p><h4 id="第三章：第一次提交"><a href="#第三章：第一次提交" class="headerlink" title="第三章：第一次提交"></a><strong>第三章：第一次提交</strong></h4><p>经过短暂的适应，小李开始修改代码。他添加了一些新的功能，并修复了一个小 bug。这时，他想把自己做的更改提交到 Git。</p><p>“小王，接下来该怎么办？”小李再次求助于小王。</p><p>“你需要先使用 <code>git add</code> 把修改的文件放到暂存区，然后再用 <code>git commit</code> 提交到本地仓库。” 小王耐心地解释道。</p><p>小李按照步骤执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git add index.html</span><br><span class="line">git commit -m &quot;修复首页 bug，添加登录功能&quot;</span><br></pre></td></tr></table></figure><p>“提交完成了！” 小李兴奋地说道。</p><p>小王接着补充道：“记住，提交信息要简洁明了，别人可以通过这些信息快速了解你修改的内容。”</p><p>小李点点头，觉得这比学校的作业提交要简单多了。每一次修改都能被记录下来，每一次提交都可以清晰地说明自己做了什么。</p><h4 id="第四章：第一次推送"><a href="#第四章：第一次推送" class="headerlink" title="第四章：第一次推送"></a><strong>第四章：第一次推送</strong></h4><p>小李第一次提交到本地仓库后，心里有了些许成就感。接着，他又向小王询问：“如果我想把本地的修改推送到远程仓库，应该怎么做？”</p><p>“你需要用 <code>git push</code> 命令来推送你的提交到远程仓库。” 小王笑着回答，“不过，在推送之前，你需要先从远程仓库拉取最新的代码，避免和其他人的修改发生冲突。”</p><p>“哦，明白了。”小李迅速输入了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git pull</span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>当他成功将本地修改推送到远程仓库时，屏幕上出现了“push successful”的提示。他心中不禁涌现出一股自豪感：“原来，Git 还真是方便，和团队合作时，每个人都能在同一个项目上协同工作！”</p><h4 id="第五章：第一次遇到冲突"><a href="#第五章：第一次遇到冲突" class="headerlink" title="第五章：第一次遇到冲突"></a><strong>第五章：第一次遇到冲突</strong></h4><p>几天后，团队里另外一位开发者小张修改了同一个文件，并且推送到了远程仓库。小李接着更新了代码库，准备继续开发时，突然遇到了一个问题。</p><p>“Git 说我无法推送，提示我当前分支落后于远程分支。”小李看着终端输出的错误信息，感到有些困惑。</p><p>小王走过来看了一眼，解释道：“这是因为你在推送之前没有拉取最新的远程代码，Git 检测到远程仓库有你没有更新的提交，因此推送失败。”</p><p>“那该怎么办？”小李焦急地问。</p><p>“我们需要先拉取远程的更新，解决可能的冲突，再进行推送。”小王平静地说道。</p><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">git pull origin main</span><br></pre></td></tr></table></figure><p>Git 检测到有冲突文件后，小李被要求手动解决冲突。经过一番调试，他成功解决了冲突，并将自己的更改再次推送到远程仓库。</p><p>“解决冲突是 Git 使用中的一项重要技能，虽然有点麻烦，但熟悉了之后就能游刃有余。” 小王笑着提醒他。</p><h4 id="第六章：Git，成了朋友"><a href="#第六章：Git，成了朋友" class="headerlink" title="第六章：Git，成了朋友"></a><strong>第六章：Git，成了朋友</strong></h4><p>这一天，小李结束了一天的工作。他站在公司楼下，深吸一口气，觉得整个世界变得更加清晰。Git，这个曾经陌生又让他感到害怕的工具，现在已经变成了他开发工作中不可或缺的伙伴。</p><p>“小王，谢谢你教我这么多。” 小李感激地说。</p><p>“不用谢，我们都是一个团队。” 小王微笑着回答。</p><p>随着日子的推移，小李对 Git 的理解越来越深入。每当遇到问题时，他不再感到焦虑，而是学会了从容应对。在这条开发道路上，Git 已经成为了他忠实的伙伴，帮助他高效管理版本，协同开发。</p><h4 id="第七章：第一次的-Git-Rebase"><a href="#第七章：第一次的-Git-Rebase" class="headerlink" title="第七章：第一次的 Git Rebase"></a><strong>第七章：第一次的 Git Rebase</strong></h4><p>随着项目的不断发展，小李逐渐熟悉了 Git 的基本操作，代码管理变得越来越得心应手。然而，随着开发人员数量的增加，项目中的提交历史开始显得越来越凌乱，尤其是一些多次合并的提交记录，看上去非常混乱。</p><p>有一天，小李在查看 Git 提交历史时，发现每次合并分支的提交记录都让历史显得很复杂：“这样下去，历史会越来越乱，别人查找问题的时候会很麻烦。”</p><p>小李向小王请教，是否有办法将这些杂乱的提交历史整理得更加简洁。</p><p>“你可以使用 <code>git rebase</code> 来整理提交历史，” 小王解释道，“<code>git rebase</code> 可以把你的提交历史进行重新排列，将一些不必要的合并提交压缩成更简洁的历史记录。”</p><p>小李兴奋地想试一试。于是，他在 Git 上执行了 <code>git rebase -i</code> 命令，进入了交互式 rebase 模式。这个命令让他可以查看最近的几个提交记录，并选择哪些提交需要保留，哪些提交需要合并。</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">git rebase -i HEAD~5</span><br></pre></td></tr></table></figure><p>Git 打开了一个编辑器，列出了最近的 5 次提交记录。小李看到，之前的一些功能开发提交被频繁地合并，而这些合并的记录并没有特别的意义。他决定将这些不重要的提交进行合并。</p><p>通过将不必要的提交标记为 <code>squash</code>（合并），小李简洁地将历史整理得更加简洁，并减少了重复的合并提交。整理完成后，他使用 <code>git push</code> 推送了这些更改。</p><p>当他看到远程仓库的提交历史变得清晰简洁时，心中充满了成就感。原来，Git 不仅是一个强大的版本控制工具，它也能帮助开发者将代码历史整理得井井有条。</p><h4 id="第八章：团队合作中的-Git-Stash"><a href="#第八章：团队合作中的-Git-Stash" class="headerlink" title="第八章：团队合作中的 Git Stash"></a><strong>第八章：团队合作中的 Git Stash</strong></h4><p>团队开发中，小李和其他同事经常需要在不同的任务之间切换。一天，小李在开发一个新功能时，突然接到紧急任务，要求他修复一个线上 bug。他立即决定中止当前工作，切换到 bug 修复任务。</p><p>“小王，怎么才能保证我现在的工作不会丢失呢？”小李问道。</p><p>“你可以使用 <code>git stash</code> 命令，把当前未完成的工作保存起来，等你修复完 bug 后再恢复。” 小王笑着答道。</p><p>“这可以让我暂时保存当前的工作进展？”小李眼前一亮。</p><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">git stash</span><br></pre></td></tr></table></figure><p>Git 将他当前工作区的更改暂时保存了起来，并恢复了工作目录的干净状态。小李随后切换到了修复 bug 的任务，并快速解决了问题。</p><p>几小时后，他回到原先的任务时，使用 <code>git stash apply</code> 恢复了之前未完成的工作。</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">git stash apply</span><br></pre></td></tr></table></figure><p>小李惊讶地发现，所有的更改和修改都完好无损地恢复了过来。通过 <code>git stash</code>，他不仅没有丢失任何代码，而且能够在不提交的情况下，顺利切换任务。</p><p>这让他深刻体会到，Git 是多么强大的工具，在团队协作和多任务处理中，它能够帮助开发者高效管理代码和进度。</p><h4 id="第九章：深入-Git-Bisect-查找-Bug"><a href="#第九章：深入-Git-Bisect-查找-Bug" class="headerlink" title="第九章：深入 Git Bisect 查找 Bug"></a><strong>第九章：深入 Git Bisect 查找 Bug</strong></h4><p>项目中的一个新功能上线后，客户突然反馈了一个 bug，导致页面无法正确显示数据。小李接到任务后，立即开始调查这个问题。但问题是，线上代码已经迭代了好几个版本，谁也不清楚到底是哪一次提交引入了问题。</p><p>“小王，如何才能定位到具体是哪个提交引入了 bug 呢？”小李问道。</p><p>“你可以使用 <code>git bisect</code> 来进行二分查找。”小王回答道，“<code>git bisect</code> 可以帮助你快速找到 bug 引发的提交。它会将历史提交分成两部分，每次告诉你一个范围，你只需要标记 bug 是否存在，Git 会逐步缩小范围，直到找到问题的根源。”</p><p>小李恍然大悟。于是，他开始使用 <code>git bisect</code> 查找 bug 的根源：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git bisect start</span><br><span class="line">git bisect bad</span><br><span class="line">git bisect good v1.0</span><br></pre></td></tr></table></figure><p><code>git bisect</code> 会从最近的提交开始，将所有的提交历史分成两部分，并让小李检查每一部分是否存在 bug。每次，他都根据测试结果输入“good”或“bad”，Git 会根据他的反馈继续缩小范围。</p><p>最终，Git 在经过几轮查找后，成功定位到某个特定的提交，这个提交引入了 bug。小李修复了 bug 后，通过 <code>git bisect reset</code> 重置了 bisect 状态，返回到正常的开发流程。</p><p>“原来 Git 不仅可以帮助我们管理版本，还能高效地找出问题的根源。” 小李感叹道。</p><h4 id="第十章：Git-冲突中的成长"><a href="#第十章：Git-冲突中的成长" class="headerlink" title="第十章：Git 冲突中的成长"></a><strong>第十章：Git 冲突中的成长</strong></h4><p>随着团队中成员越来越多，开发中出现的合并冲突也越来越频繁。虽然小李已经掌握了一些 Git 的基本操作，但每当遇到合并冲突时，他还是感到有些紧张和不知所措。</p><p>一天，他在合并一个分支时，遇到了一个棘手的冲突，Git 无法自动合并他和其他同事的更改。</p><p>“小王，怎么解决合并冲突呢？”小李有些焦虑。</p><p>小王笑了笑，走过来耐心地解释道：“Git 会标记出冲突的地方，你需要打开冲突文件，手动选择保留哪部分代码。解决完冲突后，再执行 <code>git add</code> 和 <code>git commit</code>。”</p><p>小李按照指示，打开了冲突文件，看到了被 Git 标记出来的冲突部分。经过仔细的分析，他决定保留自己的修改，并删除了无关的部分。</p><p>解决冲突后，小李使用了以下命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br><span class="line">git commit -m &quot;解决合并冲突&quot;</span><br></pre></td></tr></table></figure><p>“解决了！这次我终于顺利地解决了冲突。”小李长舒一口气。</p><p>“别担心，冲突在团队开发中很常见，解决起来也是一种成长。”小王鼓励他说。</p><p>从这次经历后，小李对 Git 的理解更加深刻，他开始不再畏惧合并冲突，反而在解决冲突的过程中积累了更多的经验。</p><h4 id="第十一章：Git-的复合技能——远程协作中的“Push”与“Pull”"><a href="#第十一章：Git-的复合技能——远程协作中的“Push”与“Pull”" class="headerlink" title="第十一章：Git 的复合技能——远程协作中的“Push”与“Pull”"></a><strong>第十一章：Git 的复合技能——远程协作中的“Push”与“Pull”</strong></h4><p>时间过得飞快，随着项目的逐步推进，小李越来越熟悉了 Git 的使用。一次，团队的新需求要求他和小张一起共同开发一个新功能。项目中由于要对数据接口进行修改，涉及到的代码文件较多，这也意味着双方必须频繁地同步代码，避免重复工作。</p><p>“小李，你负责前端页面的改动，我负责后端接口的修改。我们需要保持代码同步，你把你的修改推送到 Git 仓库，我拉取下来进行合并。” 小张提醒道。</p><p>“好的，小张，我会尽量减少冲突的。” 小李点点头，心里想着如何避免两个开发者在代码中的修改冲突。</p><p>小李根据约定，开始在本地开发工作并修改了部分前端代码。当修改完毕后，他通过 <code>git add</code> 和 <code>git commit</code> 提交了自己的修改。接着，他输入了 <code>git push</code> 命令，将本地的更改推送到了远程仓库：</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">git push origin feature-frontend</span><br></pre></td></tr></table></figure><p>随着“push successful”的提示，修改成功上传到了远程仓库。小李心里松了一口气，开始等着小张拉取他的代码进行合并。</p><p>然而，小张这边也在进行着自己的开发工作，修改了后端接口，并且同样进行了提交。当小张准备将修改推送到仓库时，却遇到了困难。“小李，我拉取了你的代码，但是推送时遇到了错误，说我本地分支落后于远程分支。”</p><p>“哦，那是因为我在你推送之前就已经提交了我的修改。你需要先拉取我的更改，再进行推送。” 小李知道问题所在，告诉了小张解决方案。</p><p>小张理解地点点头，输入了 <code>git pull</code> 来拉取最新的修改：</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">git pull origin feature-frontend</span><br></pre></td></tr></table></figure><p>Git 自动将小张的修改和小李的修改进行了合并，并解决了没有冲突的部分。小张再一次执行 <code>git push</code>，这次成功了。</p><p>“解决了！感谢你，小李。” 小张松了口气，“这就是协作开发的魅力，不同的代码能在 Git 中无缝连接。”</p><p>小李微笑着点头，虽然有时遇到一些小麻烦，但通过 Git，这一切变得简单而高效。在远程协作中，<code>git push</code> 和 <code>git pull</code> 成为他日常开发的两大法宝，每次与团队成员一起开发时，他都能高效同步、解决冲突，确保项目的顺利推进。</p><h4 id="第十二章：Git-中的-“Tag”-之旅"><a href="#第十二章：Git-中的-“Tag”-之旅" class="headerlink" title="第十二章：Git 中的 “Tag” 之旅"></a><strong>第十二章：Git 中的 “Tag” 之旅</strong></h4><p>随着项目的不断进展，小李和团队的开发进度也越来越顺利。为了标记项目中的一些关键版本，项目经理提出了一个新的要求：“我们需要为每个阶段的发布版本创建一个 <code>tag</code>，便于后续的版本管理。”</p><p>“<code>tag</code>，我之前只听说过，但是不太明白具体怎么用。” 小李略显困惑。</p><p>“没问题！” 项目经理一边解释一边操作，“<code>tag</code> 就是给某个特定的提交添加一个标签。它通常用来标记版本号，例如 v1.0、v1.1 等。你可以通过 <code>git tag</code> 命令为某个提交打标签。”</p><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">git tag v1.0</span><br></pre></td></tr></table></figure><p>他通过 <code>git tag</code> 命令查看了所有的标签：</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">git tag</span><br></pre></td></tr></table></figure><p>“<code>tag</code> 是不可变的，它就像是一个时间戳，标记了某个关键时刻的版本。” 项目经理接着说，“如果以后想要回到某个版本，可以通过 <code>git checkout</code> 切换到这个标签。”</p><p>小李按照提示，通过 <code>git checkout</code> 轻松地切换到标记为 <code>v1.0</code> 的版本，查看了之前提交的代码，并发现 <code>tag</code> 真的非常方便，帮助他回溯项目的重要节点。</p><p>每当开发到一个新阶段或发布一个新版本时，小李都习惯性地为当前版本添加一个 <code>tag</code>，以便将来能够快速回顾项目的重要里程碑。</p><h4 id="第十三章：Git-在-“临时”-工作中的灵活运用"><a href="#第十三章：Git-在-“临时”-工作中的灵活运用" class="headerlink" title="第十三章：Git 在 “临时” 工作中的灵活运用"></a><strong>第十三章：Git 在 “临时” 工作中的灵活运用</strong></h4><p>有一天，小李接到一个紧急任务，需要修复生产环境中的一个 bug。由于 bug 可能影响整个系统的稳定性，他必须马上开始处理，但又不想打乱当前正在开发的其他功能。</p><p>“小王，我在本地修改的功能还没有完成，怎么办才能保证现在的修改不会丢失？” 小李有些焦急。</p><p>小王走过来，笑着告诉小李：“你可以用 <code>git stash</code> 把当前修改暂时存起来，等你解决完 bug 再恢复。这样就能保证你现在的工作不会丢失。”</p><p>小李恍然大悟：“哦，原来可以这么灵活处理！”</p><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">git stash</span><br></pre></td></tr></table></figure><p>Git 会暂时保存小李当前的修改，并将工作目录恢复到干净状态。他迅速切换到修复 bug 的任务中，解决了线上问题。当任务完成后，他输入 <code>git stash apply</code> 恢复了之前未完成的功能开发。</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">git stash apply</span><br></pre></td></tr></table></figure><p>“小王，你看，修改恢复了！不管处理什么任务，Git 总能让我保持高效！”小李激动地说。</p><p>小王笑着点点头：“是的，Git 不仅帮助我们管理版本，还能在繁忙的开发过程中高效地切换任务。Git 的 <code>stash</code> 让你随时可以保存当前进度，恢复工作，不会有任何遗漏。”</p><h4 id="第十四章：Git-Revert——从失败中学习"><a href="#第十四章：Git-Revert——从失败中学习" class="headerlink" title="第十四章：Git Revert——从失败中学习"></a><strong>第十四章：Git Revert——从失败中学习</strong></h4><p>有一次，小李在开发过程中进行了一个重大的功能更新，然而在提交后，他很快发现这个功能并没有按预期工作，甚至还引发了其他问题。为了修复这个问题，他需要撤回这次提交。</p><p>“小王，我不小心提交了一个有问题的功能，这个提交需要撤回，怎么操作？” 小李有些焦急地问道。</p><p>小王没有惊讶，而是轻松地告诉小李：“你可以使用 <code>git revert</code> 来撤回指定的提交，它会创建一个新的提交来撤销之前的更改，而不会影响历史。”</p><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">git revert &lt;commit-hash&gt;</span><br></pre></td></tr></table></figure><p>Git 创建了一个新的提交，撤销了原先的修改。当他查看提交历史时，发现撤销操作被正确记录了，而原来的错误提交没有影响到后续的工作。</p><p>“原来，撤销错误提交也能这么优雅地操作。” 小李松了口气。</p><p>“Git 的 <code>revert</code> 命令让你可以安全地撤回更改，并在代码历史中留下清晰的记录。” 小王总结道。</p><h4 id="第十五章：Git-中的-“Fork”-和-“Pull-Request”"><a href="#第十五章：Git-中的-“Fork”-和-“Pull-Request”" class="headerlink" title="第十五章：Git 中的 “Fork” 和 “Pull Request”"></a><strong>第十五章：Git 中的 “Fork” 和 “Pull Request”</strong></h4><p>随着项目越来越庞大，团队成员的增多，开发方式也发生了变化。为了更好地管理开发流程，项目经理决定采用 GitHub 的 <code>Fork</code> 和 <code>Pull Request</code> 工作流，以便团队成员能在自己的分支上开发功能，并通过 <code>Pull Request</code> 将修改提交到主仓库。</p><p>“小李，今天我给你分配了一个任务，你需要在 GitHub 上对项目进行一些优化。你可以直接 Fork 这个仓库，然后在自己的仓库中开发。”项目经理说道。</p><p>“好的，<code>Fork</code> 是什么意思？”小李有些疑惑。</p><p>“<code>Fork</code> 就是将原始仓库的完整副本复制到你的 GitHub 账户下，之后你就可以在自己的副本上进行开发了。开发完成后，提交一个 <code>Pull Request</code>，把你的修改合并到主仓库里。” 项目经理耐心解释道。</p><p>“明白了！那我就去 Fork 一下。” 小李立刻在 GitHub 上点击了仓库页面的 <code>Fork</code> 按钮，将仓库复制到了自己的 GitHub 账户中。</p><p>接着，小李通过 <code>git clone</code> 克隆了自己的仓库到本地：</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">git clone https://github.com/your-username/project-repo.git</span><br></pre></td></tr></table></figure><p>然后，他在自己的仓库中开始进行代码的修改。修改完成后，他通过 <code>git push</code> 将自己的更改推送到了自己的 GitHub 仓库，并创建了一个 <code>Pull Request</code> 请求将更改合并到主仓库。</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">git push origin feature-optimization</span><br></pre></td></tr></table></figure><p>小李完成了 <code>Pull Request</code> 后，项目经理和其他团队成员开始查看并审查他的代码。经过几轮讨论和修改，最终小李的优化功能被成功合并到主仓库。</p><p>通过这种工作流，小李意识到，<code>Fork</code> 和 <code>Pull Request</code> 提供了一种高效的协作模式，开发者可以在独立的仓库中进行开发，不会影响主仓库的稳定性，同时也能保持代码的清晰和有序。</p><h4 id="第十六章：Git-的持久化——“GitHub-Actions”-帮我自动化部署"><a href="#第十六章：Git-的持久化——“GitHub-Actions”-帮我自动化部署" class="headerlink" title="第十六章：Git 的持久化——“GitHub Actions” 帮我自动化部署"></a><strong>第十六章：Git 的持久化——“GitHub Actions” 帮我自动化部署</strong></h4><p>随着项目的需求变得越来越复杂，小李和团队开始考虑如何将代码部署过程自动化。为了减少人工操作，提高效率，小李提议使用 GitHub 的 CI&#x2F;CD 功能——GitHub Actions。</p><p>“小王，我们可以用 GitHub Actions 来实现自动化部署吗？” 小李询问道。</p><p>“当然可以！GitHub Actions 是 GitHub 提供的 CI&#x2F;CD 服务，能够在你每次推送代码时自动触发一系列动作，比如编译、测试、部署等。” 小王一边讲解，一边打开了 GitHub 仓库的设置页面。</p><p>小李兴奋地学习着如何配置 GitHub Actions。他创建了一个新的 <code>.yml</code> 配置文件，定义了自动化的流程。每次有新的代码推送到主分支时，GitHub Actions 会自动执行一系列操作，首先进行单元测试，然后编译代码，最后自动将更新部署到生产环境。</p><p>小李的配置文件如下：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">CI/CD</span> <span class="string">Pipeline</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Node.js</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v2</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="string">&quot;14&quot;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">tests</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">test</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">to</span> <span class="string">production</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">deploy</span></span><br></pre></td></tr></table></figure><p>这个配置文件定义了当代码推送到 <code>main</code> 分支时，GitHub Actions 会自动进行代码的检查、依赖安装、测试和部署过程。小李将这个文件推送到 GitHub 仓库中后，团队中的每个成员都能在代码推送后享受到自动化部署的便利。</p><p>不久之后，当小李提交代码并推送到 GitHub 上时，他看到 GitHub Actions 自动触发了部署流程，并成功将代码部署到生产环境。每次提交后，他不再需要手动执行部署操作，GitHub Actions 自动为他完成了这一切。</p><p>“真是太棒了！这下我们可以更专注于开发，不用再浪费时间在部署上了。” 小李高兴地对小王说。</p><p>小王点头道：“是的，自动化部署让我们可以更快速、更高效地推送代码，也减少了人为错误。”</p><h4 id="第十七章：Git-Submodule——为项目添加依赖库"><a href="#第十七章：Git-Submodule——为项目添加依赖库" class="headerlink" title="第十七章：Git Submodule——为项目添加依赖库"></a><strong>第十七章：Git Submodule——为项目添加依赖库</strong></h4><p>随着项目的扩展，团队决定将一些通用的库或模块独立出来，作为子模块来进行管理。这些子模块将在多个项目中复用，减少了重复代码的编写。</p><p>“小李，我需要你帮助把我们当前的公共库添加为 Git 子模块，这样其他项目也可以引用这个库。” 项目经理安排了一个新任务。</p><p>“好的，<code>git submodule</code> 是怎么操作的？” 小李询问。</p><p>“<code>git submodule</code> 是 Git 提供的一个工具，它允许你将一个 Git 仓库嵌套在另一个 Git 仓库中。你可以在主项目中添加其他的 Git 仓库作为子模块，通过子模块来管理依赖。” 项目经理解释道。</p><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">git submodule add https://github.com/example/public-library.git libs/public-library</span><br></pre></td></tr></table></figure><p>通过这个命令，Git 将公共库克隆到主项目中的 <code>libs/public-library</code> 目录，并将其添加为子模块。小李继续执行了：</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">git submodule update --init --recursive</span><br></pre></td></tr></table></figure><p>这样，子模块的所有内容都成功同步到本地。当其他团队成员需要使用这个公共库时，他们只需要在主仓库中执行相同的 <code>git submodule</code> 命令来同步子模块。</p><p>“小李，感谢你的帮助！以后其他项目也能通过这个子模块共享公共库了，极大减少了重复开发的时间。” 项目经理赞扬道。</p><p>小李点点头，意识到 Git 的 <code>submodule</code> 功能极大地提升了团队的开发效率，让依赖库的管理变得更简单、更灵活。</p><h4 id="第十八章：Git-的高效工具——“Git-Cherry-pick”"><a href="#第十八章：Git-的高效工具——“Git-Cherry-pick”" class="headerlink" title="第十八章：Git 的高效工具——“Git Cherry-pick”"></a><strong>第十八章：Git 的高效工具——“Git Cherry-pick”</strong></h4><p>小李在团队开发的过程中，逐渐接触到了更多的 Git 高级操作。随着项目逐渐向前推进，他发现有时需要从一个分支中挑选特定的提交，而不是直接合并整个分支。比如，有时他只想把某个特定的功能或者修复应用到当前工作中，而不希望将整个分支的其他修改都合并过来。</p><p>“小王，有没有什么方法能让我只选取某个提交而不是合并整个分支？”小李问道。</p><p>“当然有，”小王笑着说道，“Git 有一个非常有用的命令，叫做 <code>git cherry-pick</code>，它可以让你从另一个分支上挑选特定的提交，并将该提交应用到当前分支。”</p><p>小李顿时豁然开朗：“那如果我想把某个功能从 <code>feature</code> 分支中拿到当前的 <code>main</code> 分支上，该怎么操作？”</p><p>“你只需要找到那个提交的哈希值，然后用 <code>git cherry-pick</code> 命令把它应用到你的当前分支。”小王答道。</p><p>于是，小李开始操作：</p><ol><li><p><strong>首先，他通过 <code>git log</code> 查找 <code>feature</code> 分支中的那个提交的哈希值</strong>。</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">git log feature</span><br></pre></td></tr></table></figure></li><li><p><strong>然后，他切换到 <code>main</code> 分支</strong>，准备将提交引入当前分支：</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">git checkout main</span><br></pre></td></tr></table></figure></li><li><p><strong>接着，使用 <code>git cherry-pick</code> 来选择并应用那个提交</strong>：</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">git cherry-pick &lt;commit-hash&gt;</span><br></pre></td></tr></table></figure></li></ol><p>当命令执行后，Git 自动将那个提交的更改应用到了 <code>main</code> 分支上，而不需要合并整个 <code>feature</code> 分支。小李检查了代码，确认应用成功后，顺利地将新功能整合到当前分支。</p><p>“小王，真是太棒了！这个命令太实用了，以后再也不用为合并多余的代码而烦恼了！”小李高兴地说道。</p><p>小王点头笑道：“对，<code>git cherry-pick</code> 就是为了解决这个问题，它让你可以选择性地引入更改，避免不必要的合并。”</p><p>小李开始在日常开发中频繁使用 <code>git cherry-pick</code>，每当他需要从其他分支挑选特定提交时，这个命令都成为了他的得力助手。</p><h4 id="第十九章：Git-与团队协作中的高效沟通"><a href="#第十九章：Git-与团队协作中的高效沟通" class="headerlink" title="第十九章：Git 与团队协作中的高效沟通"></a><strong>第十九章：Git 与团队协作中的高效沟通</strong></h4><p>在一次代码审查的过程中，小李和团队成员遇到了一些小小的矛盾。由于对某个功能实现的理解不同，大家对如何修改代码意见不合。虽然问题本身并不大，但由于缺乏清晰的沟通，导致开发的进展有些停滞。</p><p>“小李，你能给我们解释一下你这次修改的思路吗？”项目经理耐心地询问。</p><p>小李站在桌前，深吸一口气，决定用更清晰的方式来表达自己的想法。他打开了 Git 提交记录，逐一展示了自己的修改和思路。</p><p>“我在这里用 <code>git commit --amend</code> 修改了之前的提交，因为在初始提交时，我没有充分考虑到性能问题。通过这次修改，我优化了这一部分。”小李一边说，一边展示了代码改动的细节。</p><p>项目经理点点头：“明白了，这样修改的确能够提升性能。但是，我们最好在团队中进行一些小范围的讨论，再决定如何优化。”</p><p>小李决定更加注重团队的沟通，他理解到 Git 提交的详细记录和清晰的提交信息可以极大地帮助团队成员理解每个开发者的修改意图。在随后的开发过程中，他在每次提交时，都更加注重编写简洁且易懂的提交信息。</p><p>“小李，你这次提交信息写得很好，大家都能清楚知道你的修改意图。”项目经理称赞道。</p><p>这次经验让小李意识到，Git 不仅是一个版本控制工具，它还成为了团队成员之间沟通的桥梁。通过清晰的提交记录和及时的 <code>Pull Request</code>，每个开发者都能了解其他人的工作，从而更好地进行协作。</p><h4 id="第二十章：Git-Tag-的应用——版本管理的好帮手"><a href="#第二十章：Git-Tag-的应用——版本管理的好帮手" class="headerlink" title="第二十章：Git Tag 的应用——版本管理的好帮手"></a><strong>第二十章：Git Tag 的应用——版本管理的好帮手</strong></h4><p>随着项目开发的深入，版本发布的节奏也逐渐加快。小李开始频繁接触到版本管理的任务。每当团队开发出一个新功能并完成测试时，他们就会创建一个版本发布，并用 Git 的 <code>tag</code> 来标记发布的版本。</p><p>“小李，接下来我们需要为即将发布的版本打上标签。” 项目经理走过来，指着屏幕说道，“你可以使用 <code>git tag</code> 来为我们当前的版本创建一个标签，并标记一个明确的版本号。”</p><p>“我明白了，标签就像是一个历史的快照，可以帮助我们标记每一个发布版本。” 小李回答道。</p><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">git tag v1.0</span><br></pre></td></tr></table></figure><p>然后，他通过 <code>git push</code> 推送了标签到远程仓库：</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">git push origin v1.0</span><br></pre></td></tr></table></figure><p>项目经理看到远程仓库成功更新了标签后，表示满意：“很好，<code>tag</code> 可以让我们轻松标记每次发布的版本，方便后续的维护和版本回溯。”</p><p>小李意识到，Git 的 <code>tag</code> 功能不仅能够帮助团队在版本发布时更加清晰地管理项目，还能在遇到回滚或版本回退时快速恢复到特定的版本。随着团队开发的推进，<code>tag</code> 成为了团队工作中不可缺少的工具之一。</p><h4 id="第二十一章：Git-工作流中的“Branch”与“Merge”"><a href="#第二十一章：Git-工作流中的“Branch”与“Merge”" class="headerlink" title="第二十一章：Git 工作流中的“Branch”与“Merge”"></a><strong>第二十一章：Git 工作流中的“Branch”与“Merge”</strong></h4><p>随着小李逐渐掌握了 Git 的基础和高级操作，团队中的工作流程也开始更加成熟。在一次团队会议上，项目经理提议引入一种新的 Git 工作流，以便更高效地管理开发和部署过程。</p><p>“小李，团队正在采用 Git 的 <code>feature branch</code> 工作流，我们建议每个开发者在开发新功能时，都创建一个新的分支，开发完成后再合并回主分支。”项目经理说道。</p><p>“这样可以避免多人同时修改同一代码文件时产生冲突吗？”小李问道。</p><p>“没错，”项目经理解释道，“通过使用 <code>feature branch</code>，每个开发者都可以在独立的分支上进行开发，确保主分支保持稳定。只有在开发完成后，才能通过 <code>git merge</code> 或者 <code>git pull request</code> 合并回主分支。”</p><p>小李听后对这种工作流产生了浓厚兴趣。他立刻实践了这个流程。在开发新功能时，他从 <code>main</code> 分支创建了一个新的分支，并在分支上进行修改。当功能开发完成后，他执行了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git pull origin main</span><br><span class="line">git merge feature-new-feature</span><br></pre></td></tr></table></figure><p>通过这种方式，小李能够确保每个功能都能在独立的环境中开发，不会影响其他团队成员的工作。而且，<code>git merge</code> 能够帮助他将所有更改平滑地合并到主分支，确保代码的稳定性。</p><h4 id="Git，开发者的最佳伙伴"><a href="#Git，开发者的最佳伙伴" class="headerlink" title="Git，开发者的最佳伙伴**"></a>Git，开发者的最佳伙伴**</h4><p>随着时间的推移，小李不断地在实践中熟练掌握 Git 的各种技巧和工作流，他不仅能够高效地管理代码和版本，还能帮助团队提高开发效率。Git 成为了他工作中不可或缺的伙伴，见证了他从初学者到专业开发者的成长。</p><p>每当遇到问题或新的挑战时，小李总能依赖 Git 解决问题。他逐渐明白，Git 不仅仅是一个工具，它更像是一位教练，帮助开发者从错误中学习，从实践中进步。</p><p>未来的路还很长，而 Git 将继续陪伴小李，助力他在开发的世界中越走越远。在这个信息化飞速发展的时代，Git 让团队协作变得更加高效，代码管理变得更加清晰，开发者的每一步成长，都将在 Git 的世界中留下深深的印记。</p><h4 id="第二十二章：Git-进阶——掌握-git-reset-和-git-reflog"><a href="#第二十二章：Git-进阶——掌握-git-reset-和-git-reflog" class="headerlink" title="第二十二章：Git 进阶——掌握 git reset 和 git reflog"></a><strong>第二十二章：Git 进阶——掌握 <code>git reset</code> 和 <code>git reflog</code></strong></h4><p>随着开发项目的不断发展，小李逐渐遇到了一些复杂的情况。有时候，他在提交后发现代码存在问题，或者做了不必要的操作，想要撤回。对于这种情况，<code>git reset</code> 命令成为了他解决问题的利器。</p><p>一天，小李正在开发一个新功能，突然意识到自己在提交时选择了错误的文件进行修改，造成了不必要的代码变动。他想要撤回这次提交，怎么做呢？</p><p>“小王，我不小心提交了错误的代码，能不能撤回？” 小李焦急地问道。</p><p>小王走过来，微笑着解释道：“你可以使用 <code>git reset</code> 来撤回最近的提交。<code>git reset</code> 允许你恢复到某个特定的提交状态，你可以选择是保留本地修改，还是完全丢弃。”</p><p>“那如何使用呢？”小李问道。</p><p>“你可以通过 <code>git reset --soft HEAD~1</code> 撤销最近一次提交，但保留你的工作区更改；或者使用 <code>git reset --hard HEAD~1</code> 完全撤销提交，并且丢弃所有更改。” 小王继续说道。</p><p>小李决定使用 <code>git reset --soft</code> 来撤回错误的提交并保留工作目录中的修改：</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">git reset --soft HEAD~1</span><br></pre></td></tr></table></figure><p>执行完命令后，Git 将最近一次提交撤销，并把修改恢复到暂存区。这时，小李可以继续修改代码并重新提交。</p><p>“小王，真是太方便了！<code>git reset</code> 让我能够灵活控制我的代码，避免了不必要的错误提交。”</p><p>“是的，<code>git reset</code> 是一个非常强大的命令，但要小心使用，尤其是 <code>--hard</code> 参数，使用不当可能会丢失数据。” 小王提醒道。</p><p>不仅如此，小李还学会了使用 <code>git reflog</code> 查看历史操作的记录。当他不小心做错操作时，<code>git reflog</code> 成为了解决问题的救命稻草。</p><p>“<code>git reflog</code> 是一个很有用的工具，它记录了你所有的 HEAD 操作历史，即便你执行了 <code>git reset</code> 或者其他改变了历史的操作，它也能帮你找回丢失的记录。” 小王向小李展示了 <code>git reflog</code> 的使用。</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">git reflog</span><br></pre></td></tr></table></figure><p>小李通过 <code>git reflog</code> 快速找到了丢失的提交，并通过 <code>git reset</code> 恢复了之前的状态。这让他更加有信心在复杂的开发任务中使用 Git 来管理版本和操作历史。</p><h4 id="第二十三章：Git-在代码审查中的优势"><a href="#第二十三章：Git-在代码审查中的优势" class="headerlink" title="第二十三章：Git 在代码审查中的优势"></a><strong>第二十三章：Git 在代码审查中的优势</strong></h4><p>随着项目的逐渐深入，团队的代码审查变得尤为重要。小李不再是唯一负责提交和修改代码的人，团队中的每个成员都在贡献自己的力量。而 Git 在代码审查中的作用，也变得愈发突出。</p><p>“小李，今天我们来做代码审查，你的这段代码有一些地方需要调整。” 项目经理提醒道。</p><p>“好的，项目经理，能不能给我一些反馈？”小李走到项目经理的办公桌旁。</p><p>项目经理通过 Git 提交记录查看了小李的代码，指出了其中的一些问题。通过 <code>git diff</code> 命令，他能清楚地看到小李在提交时修改了哪些部分。</p><p>“小李，你在提交时改变了这个函数的实现逻辑。我们能不能保留原有逻辑并优化一下性能？”项目经理问道。</p><p>小李立刻打开终端，执行 <code>git diff</code> 查看具体的代码差异，确认了项目经理的建议：</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">git diff HEAD~1</span><br></pre></td></tr></table></figure><p>“确实是这样。谢谢你指出这个问题，我会按照建议进行修改。”小李认真地说道。</p><p>通过 <code>git diff</code>，小李能够迅速查看每次提交所带来的代码差异，确保每次修改都能符合团队的要求。而且，Git 也让代码审查变得更加高效，团队成员可以轻松查看每个提交的改动内容，避免了沟通上的误解。</p><p>项目经理总结道：“Git 在团队协作中的优势非常明显，代码审查时，我们能直接通过提交记录看到每个开发者的修改，而且能随时回溯历史，查看每个修改的细节。”</p><p>小李深刻认识到，Git 提供的强大工具不仅能帮助他高效管理代码，还能在团队合作中提升代码审查的效率，确保每个成员的代码质量。</p><h4 id="第二十四章：Git-和持续集成（CI）结合"><a href="#第二十四章：Git-和持续集成（CI）结合" class="headerlink" title="第二十四章：Git 和持续集成（CI）结合"></a><strong>第二十四章：Git 和持续集成（CI）结合</strong></h4><p>随着项目的规模不断扩大，团队决定引入持续集成（CI）工具，以便更好地自动化测试和部署流程。小李负责配置 Git 和 CI 工具的集成，让团队的代码自动化构建、测试和部署。</p><p>“小李，我们要使用 Jenkins 来进行自动化构建和测试，能帮我把 Git 和 Jenkins 集成起来吗？”项目经理问道。</p><p>“没问题，项目经理，我来配置。” 小李点头答应。</p><p>首先，他在 GitHub 仓库中配置了 Webhook，当代码推送到 GitHub 仓库时，Webhook 会自动触发 Jenkins 构建任务。接着，小李在 Jenkins 中配置了一个构建任务，并在构建脚本中加入了自动拉取 Git 仓库代码的命令：</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">git clone https://github.com/your-username/project-repo.git</span><br></pre></td></tr></table></figure><p>此外，Jenkins 还配置了自动化测试步骤，每当代码推送到 Git 仓库时，Jenkins 会自动拉取代码，执行单元测试，确保没有新的 bug 被引入。</p><p>通过这种方式，小李实现了 Git 与 Jenkins 的无缝集成，让每次代码推送后都能自动触发构建和测试流程，极大提高了开发效率。</p><p>“小李，你的配置太棒了！现在我们每次推送代码后，Jenkins 会自动进行构建和测试，这样就能第一时间发现问题，避免了手动操作的麻烦。”项目经理感慨道。</p><p>小李也深刻感受到，Git 不仅是团队协作的工具，还能与 CI 工具结合，提升整个开发流程的自动化和高效性。</p><h4 id="第二十五章：Git-的深层次运用——多仓库管理"><a href="#第二十五章：Git-的深层次运用——多仓库管理" class="headerlink" title="第二十五章：Git 的深层次运用——多仓库管理"></a><strong>第二十五章：Git 的深层次运用——多仓库管理</strong></h4><p>随着小李在项目中不断积累经验，他开始接触到更加复杂的 Git 使用场景。例如，团队中的多个子项目之间需要进行协调开发，这就需要管理多个仓库。为了有效管理这些子项目，小李学习了如何使用 Git 管理多个仓库的代码。</p><p>“小李，我们有多个子项目需要同步更新，能不能管理多个 Git 仓库？”项目经理问道。</p><p>小李思索了一下，答道：“我们可以使用 Git 的 <code>submodule</code> 功能来管理这些子项目，或者通过 <code>git remote</code> 将多个远程仓库关联到一个本地仓库。”</p><p>他通过 <code>git remote</code> 添加了其他项目的远程仓库，使得多个仓库能够通过一个 Git 仓库进行管理。这种方法让小李能够方便地同步多个子项目的代码，并确保所有仓库的代码始终保持一致。</p><p>“小李，这种管理方式非常有效，能让我们轻松地同步多个仓库的代码。”项目经理满意地说道。</p><h4 id="第二十六章：Git-在应对大规模项目中的应用"><a href="#第二十六章：Git-在应对大规模项目中的应用" class="headerlink" title="第二十六章：Git 在应对大规模项目中的应用"></a><strong>第二十六章：Git 在应对大规模项目中的应用</strong></h4><p>随着团队逐渐发展，项目的规模也在不断扩大，甚至开始涉及多个跨团队的合作。小李渐渐意识到，在这样的大规模项目中，Git 不再是一个单纯的版本控制工具，而是整个开发流程的核心之一。为了解决不同团队成员之间的协作问题，团队决定采用 Git 作为核心工具来协调各项工作。</p><p>“小李，我们的项目越来越复杂，多个团队的协作需求也越来越高，如何更好地协调和管理多个模块之间的关系呢？” 项目经理问道。</p><p>“我建议我们将每个模块或子系统作为独立的 Git 仓库，采用 Git 的 Submodule 或者 Subtree 功能来管理不同模块之间的依赖。” 小李回答道，“这样可以让每个团队独立工作，同时通过 Git 的功能保持各个模块之间的同步。”</p><p>“Submodule 和 Subtree？能不能再详细说一下？” 项目经理有些疑惑。</p><p>“好的，” 小李微笑着解释，“<code>git submodule</code> 用于将一个 Git 仓库嵌套到另一个仓库中，适合在一个大的项目中引用一些公共的库或者子项目。它能够让我们独立管理每个模块，并通过主仓库来同步这些模块。<code>git subtree</code> 则是一个更强大的工具，允许我们将一个项目的子目录作为另一个仓库的历史部分来管理，方便合并和共享代码。”</p><p>项目经理点点头，表示理解：“那我们可以先尝试使用 <code>git submodule</code>，看看能不能有效管理各个子模块。”</p><p>于是，小李开始在主项目中引入了 Git 子模块，将各个子模块独立管理，每个团队都可以独立开发自己的部分，通过 Git Submodule 来同步。每当有更新时，团队成员只需使用 <code>git submodule update</code> 命令来同步各自的子模块。</p><p>“小李，这样的做法确实很有效。每个团队都可以专注于自己负责的模块，且能通过 Git 保证模块间的同步。” 项目经理满意地说道。</p><p>随着项目的不断发展，团队的协作也变得更加高效。Git Submodule 成为了管理大规模项目中多个模块之间关系的关键工具，使得每个团队能够更加高效地独立开发，避免了冲突和重复工作。</p><h4 id="第二十七章：Git-和-DevOps-的融合"><a href="#第二十七章：Git-和-DevOps-的融合" class="headerlink" title="第二十七章：Git 和 DevOps 的融合"></a><strong>第二十七章：Git 和 DevOps 的融合</strong></h4><p>随着团队在多个项目中的逐渐积累，团队的开发模式也逐渐从传统的开发方式向 DevOps 转型。持续集成（CI）、持续交付（CD）和自动化测试成为了团队开发的核心需求，而 Git 作为版本控制工具在 DevOps 中的作用变得尤为重要。</p><p>“小李，接下来我们要将开发流程进行 DevOps 化，我们需要一个自动化的 CI&#x2F;CD 流程来提高开发效率。” 项目经理指示道，“你能帮忙把 Git 与我们的 CI 工具结合起来吗？”</p><p>“没问题，我来配置。” 小李迅速答应道。</p><p>小李首先配置了 Git 与 Jenkins 的集成。每当团队成员向 Git 仓库推送代码时，Jenkins 会自动检测到提交，触发自动化构建流程，执行单元测试，确保每次提交的代码都能够通过测试。接着，他配置了持续交付流程，每当通过测试后，Jenkins 会将代码自动部署到开发环境进行进一步的验证。</p><p>为了进一步优化流程，小李还在 GitHub 上配置了 Webhooks，将每次 Git 提交推送事件通知给 Jenkins，确保流程的自动化和即时性。</p><p>“通过这种方式，我们的代码每次提交后都会自动构建和测试，减少了手动操作的时间和错误。” 小李解释道，“这种 DevOps 思维方式不仅提升了开发效率，也保证了代码的质量。”</p><p>项目经理看着自动化部署流程的顺利运行，表示满意：“非常好，Git 与 DevOps 结合，自动化构建和测试提高了我们的交付效率，减少了人工操作的错误。”</p><h4 id="第二十八章：Git-在开源项目中的协作模式"><a href="#第二十八章：Git-在开源项目中的协作模式" class="headerlink" title="第二十八章：Git 在开源项目中的协作模式"></a><strong>第二十八章：Git 在开源项目中的协作模式</strong></h4><p>随着小李在公司中积累的经验越来越丰富，他开始参加一些开源项目的开发。开源项目通常有很多开发者参与，其中涉及到不同的工作流和版本管理方式。在参与开源项目时，Git 的使用成为了开发中的一个重要部分，尤其是如何高效地与其他开发者协作。</p><p>“小李，最近我在 GitHub 上看到一个非常有意思的开源项目，我们也可以参与其中贡献代码。” 小王激动地说。</p><p>“开源项目？那我们要如何参与其中呢？”小李问道。</p><p>“我们可以通过 Fork 该项目，并在我们的个人 GitHub 仓库中进行开发。当我们完成自己的功能后，通过 <code>Pull Request</code> 向原项目提交我们的更改。” 小王解释道。</p><p>小李立刻开始了解开源项目的工作流程。首先，他在 GitHub 上 Fork 了项目，然后将其克隆到本地。接着，他在本地进行了修改，并在完成之后推送到个人仓库。</p><p>然后，他创建了一个 <code>Pull Request</code>，请求将自己的更改合并到原项目中。通过这种方式，原项目的维护者可以查看他的修改并决定是否接受他的更改。</p><p>“小李，<code>Fork</code> 和 <code>Pull Request</code> 真是开源项目的核心工作流。它能让我们独立开发，并通过 PR 与原项目进行贡献。”小王感慨道。</p><p>通过参与开源项目，小李不仅进一步提升了自己的开发技能，还学会了如何在全球范围内与其他开发者协作。Git 的强大功能使得开源项目的开发变得有序而高效，团队成员之间能够通过清晰的提交记录和 PR 进行有效沟通，确保了代码质量和开发进度。</p><h4 id="第二十九章：Git-与-Agile-开发的结合"><a href="#第二十九章：Git-与-Agile-开发的结合" class="headerlink" title="第二十九章：Git 与 Agile 开发的结合"></a><strong>第二十九章：Git 与 Agile 开发的结合</strong></h4><p>随着团队的进一步发展，团队决定采用敏捷开发（Agile）方法来提高开发效率和灵活性。敏捷开发要求团队快速响应需求变化，并在较短的时间内交付高质量的代码。而 Git 成为了实现这一目标的关键工具之一。</p><p>“小李，我们决定引入敏捷开发，采用迭代的方式进行功能开发。每个 Sprint 结束后，我们需要提交一个可交付的版本。” 项目经理说道，“你认为我们如何利用 Git 来支持敏捷开发？”</p><p>“我认为，Git 能够非常好地支持敏捷开发。” 小李答道，“通过创建 <code>feature</code> 分支，我们能够快速开发出独立的功能。在每个 Sprint 结束时，我们可以通过 <code>git merge</code> 或者 <code>git pull request</code> 将功能合并到主分支，从而保证代码的稳定性。”</p><p>“那我们如何处理版本发布呢？” 项目经理问道。</p><p>“Git 的 <code>tag</code> 功能非常适合版本管理。在每次功能开发完成后，我们可以通过 <code>git tag</code> 打上版本标签，标记每个发布的里程碑。” 小李解释道。</p><p>通过使用 Git 支持敏捷开发，团队能够更快速地进行功能开发和迭代交付，同时保持代码的清晰和可维护性。每次迭代结束后，Git 的分支管理和版本标签都确保了每个功能都能够按时交付，且不会影响其他功能的开发。</p><p>项目经理表示赞赏：“Git 的分支管理和 <code>tag</code> 功能完美支持了我们敏捷开发的需求，让我们能够更加高效地进行迭代和发布。”</p><h4 id="Git-——-开发者的长久伙伴"><a href="#Git-——-开发者的长久伙伴" class="headerlink" title="Git —— 开发者的长久伙伴**"></a>Git —— 开发者的长久伙伴**</h4><p>随着时间的推移，小李不仅在公司中积累了丰富的 Git 使用经验，也在开源项目和敏捷开发中不断提升自己。Git 成为了他日常开发工作中不可或缺的工具，帮助他在团队中与其他成员高效协作，推动项目顺利进行。</p><p>Git 不再只是一个简单的版本控制工具，它已经深入到整个开发流程中，成为小李的长久伙伴。无论是日常的功能开发，还是复杂的多团队协作，Git 都能够帮助开发者高效管理代码，提升开发效率。</p><p>未来的道路依然充满挑战，但小李相信，只要有 Git 作为工具，他将能够不断突破自己，迎接更大的成功。在这个快速发展的技术时代，Git 将继续陪伴小李，在他编程的旅程中不断迈向新的高度。</p><h4 id="第三十章：Git-在大规模团队中的管理实践"><a href="#第三十章：Git-在大规模团队中的管理实践" class="headerlink" title="第三十章：Git 在大规模团队中的管理实践"></a><strong>第三十章：Git 在大规模团队中的管理实践</strong></h4><p>随着公司项目的规模日益壮大，团队成员也越来越多。小李和其他团队成员发现，Git 在大规模团队中的协作变得更加复杂。在一个多团队、跨部门的项目中，如何有效地管理和合并不同的代码变得尤为重要。</p><p>“小李，团队的规模扩大后，我们发现管理多个开发分支变得越来越困难，很多人都在不同的分支上并行开发，合并冲突也变得更频繁了。你觉得我们怎么做才能更高效地管理代码？” 项目经理问道。</p><p>小李开始思考，发现随着团队规模的扩大，代码管理面临的挑战确实在增加。他想到了一些改进方法：“我们可以采用 Git Flow 工作流来更好地管理分支。Git Flow 是一个标准化的分支管理模式，它通过规定各个分支的功能来减少冲突，确保每个分支的职责明确。”</p><p>“Git Flow？听起来很有用。你能详细解释一下吗？” 项目经理有些好奇。</p><p>小李耐心地讲解道：“Git Flow 是由 Vincent Driessen 提出的分支模型，它定义了几个主要的分支角色：</p><ol><li><strong>master</strong>：主分支，保存着所有已经发布过的代码，始终保持稳定。</li><li><strong>develop</strong>：开发分支，所有新的功能都在这个分支上开发，只有当功能开发完成并经过测试后，才会合并到 <code>master</code> 分支。</li><li><strong>feature</strong>：特性分支，用来开发独立的功能模块。每个功能都会在一个单独的 <code>feature</code> 分支上开发，开发完成后合并回 <code>develop</code> 分支。</li><li><strong>release</strong>：发布分支，主要用于准备新版本的发布。当 <code>develop</code> 分支上的功能基本完成时，我们会创建一个 <code>release</code> 分支来进行最终测试和 bug 修复。</li><li><strong>hotfix</strong>：修复分支，快速修复主分支上的紧急 bug，修复完成后合并到 <code>master</code> 和 <code>develop</code>。</li></ol><p>这种工作流的优势在于，每个团队成员可以在自己的特性分支上独立开发，减少冲突。而且，<code>release</code> 和 <code>hotfix</code> 分支帮助我们在发布和修复过程中保持主分支的稳定。”</p><p>项目经理听后点点头：“这个工作流确实很有意义，它能够帮助我们更有条理地管理代码，避免过多的冲突和合并问题。”</p><p>小李随后帮助团队将 Git Flow 工作流应用到团队项目中，确保每个开发人员按照规范创建分支、提交代码，并使用 <code>git merge</code> 或 <code>git pull request</code> 合并分支。通过这种方法，团队成员之间的协作变得更加高效，而 Git Flow 也为团队提供了清晰的分支管理结构。</p><h4 id="第三十一章：Git-在多平台开发中的角色"><a href="#第三十一章：Git-在多平台开发中的角色" class="headerlink" title="第三十一章：Git 在多平台开发中的角色"></a><strong>第三十一章：Git 在多平台开发中的角色</strong></h4><p>随着团队的技术栈不断扩展，项目开始涉及多个平台的开发需求：前端使用 React，后端使用 Node.js 和 Express，移动端使用 React Native，甚至还有一些用于数据处理的 Python 脚本。每个平台的代码和环境不同，但如何确保这些平台的代码都能同步更新、管理起来却不产生冲突，成为了小李的一个新挑战。</p><p>“小李，考虑到我们开发的多个平台，如何更好地管理这些不同代码的依赖呢？” 项目经理询问道。</p><p>小李回忆起自己在过去的项目中遇到过类似的需求，顿时有了主意：“我们可以利用 Git 子模块（<code>git submodule</code>）来管理跨平台的代码库。通过将每个平台的代码作为独立的 Git 仓库，使用主仓库来统一管理这些代码，就能有效地将不同的代码库集中在一个地方，方便我们管理。”</p><p>“你是说，不同的代码库可以独立管理，然后通过主仓库来同步吗？” 项目经理问。</p><p>“是的，<code>git submodule</code> 允许我们将其他 Git 仓库嵌套在主仓库中，作为子模块来管理。每个子模块有自己的版本号，主仓库负责对子模块进行版本控制和更新。当我们需要更新子模块的代码时，只需在主仓库中同步子模块即可。” 小李解释道。</p><p>他接着为项目配置了 Git 子模块，每个平台的代码库都作为一个子模块被添加进主仓库中，使用 <code>git submodule</code> 命令来同步更新各平台的代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git submodule add https://github.com/example/frontend-repo.git frontend</span><br><span class="line">git submodule add https://github.com/example/backend-repo.git backend</span><br><span class="line">git submodule add https://github.com/example/mobile-repo.git mobile</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">git submodule update --recursive --remote</span><br></pre></td></tr></table></figure><p>通过这种方式，团队能够有效管理不同平台的代码，并确保每个平台的版本都能保持同步，减少了版本冲突的发生。</p><p>“通过子模块管理多个平台的代码，团队成员可以独立开发，不同平台之间不会相互干扰。” 项目经理表示赞赏。</p><h4 id="第三十二章：Git-与-Code-Review-流程"><a href="#第三十二章：Git-与-Code-Review-流程" class="headerlink" title="第三十二章：Git 与 Code Review 流程"></a><strong>第三十二章：Git 与 Code Review 流程</strong></h4><p>随着项目的不断发展，代码质量和团队合作变得尤为重要。小李意识到，除了版本控制和代码同步，如何高效地进行代码审查（Code Review）也是团队合作中的一个关键环节。</p><p>“小李，最近我们在团队中遇到了一些代码质量问题。为了提高代码质量，我们决定加强代码审查流程。” 项目经理说。</p><p>“我明白，代码审查能帮助团队发现问题并保持一致的编码风格。我觉得 Git 可以在这方面发挥很大作用。” 小李答道。</p><p>小李向项目经理提议，通过 Git 提交记录和 GitHub 的 Pull Request 功能，团队成员可以高效地进行代码审查。每次提交新的功能或修改时，开发者都通过 <code>git push</code> 将代码推送到远程仓库，并创建一个 Pull Request，请求团队成员进行审查。</p><p>在代码审查过程中，团队成员可以在 GitHub 上查看具体的改动（<code>git diff</code>），留下评论，提出修改意见。每次修改完毕，开发者会更新 Pull Request，直到所有问题得到解决。最终，项目经理或负责人会合并代码并批准最终的提交。</p><p>“小李，这个流程非常有帮助，Git 的 Pull Request 功能能让我们更高效地进行代码审查，确保每个功能都经过了充分的讨论和审核。” 项目经理说道。</p><p>通过 Git 提供的强大功能，团队能够在代码审查中更好地协作，不仅提升了代码质量，还加强了团队之间的沟通。</p><h4 id="第三十三章：Git-在自动化和脚本化中的应用"><a href="#第三十三章：Git-在自动化和脚本化中的应用" class="headerlink" title="第三十三章：Git 在自动化和脚本化中的应用"></a><strong>第三十三章：Git 在自动化和脚本化中的应用</strong></h4><p>随着团队项目越来越复杂，小李开始接触到一个新的需求——自动化。项目中有很多重复性、繁琐的操作，比如同步子模块、打标签、发布新版本等。小李发现，手动执行这些操作既耗时又容易出错，因此他决定通过编写脚本来自动化这些任务。</p><p>“小李，我们希望能简化每次发布的过程，能不能帮忙写个自动化脚本，避免每次都要手动执行 Git 命令？” 项目经理提出了一个需求。</p><p>小李非常兴奋，因为他一直对自动化非常感兴趣。他思考了一下，决定用 Bash 脚本来实现这一功能。脚本的目标是每次新版本发布时，自动更新子模块、切换到主分支、打标签，并将代码推送到远程仓库。</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 更新所有子模块</span></span><br><span class="line">git submodule update --init --recursive</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 切换到主分支</span></span><br><span class="line">git checkout main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 拉取最新的代码</span></span><br><span class="line">git pull origin main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 打上新的版本标签</span></span><br><span class="line">VERSION=$(<span class="built_in">date</span> +<span class="string">&quot;%Y%m%d%H%M%S&quot;</span>)</span><br><span class="line">git tag -a <span class="string">&quot;v<span class="variable">$VERSION</span>&quot;</span> -m <span class="string">&quot;Release version <span class="variable">$VERSION</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 推送主分支和标签到远程</span></span><br><span class="line">git push origin main</span><br><span class="line">git push origin <span class="string">&quot;v<span class="variable">$VERSION</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Release <span class="variable">$VERSION</span> successfully created and pushed!&quot;</span></span><br></pre></td></tr></table></figure><p>这个脚本首先更新了所有子模块，确保每个模块都是最新的。然后，它会切换到 <code>main</code> 分支，拉取远程仓库的最新代码，接着生成一个基于当前时间戳的版本号，并使用 <code>git tag</code> 打上标签。最后，脚本会将 <code>main</code> 分支和新标签都推送到远程仓库。</p><p>“小李，这个自动化脚本简直太棒了！以后发布新版本时，直接执行这个脚本就行了，避免了手动操作的错误。” 项目经理大加赞赏。</p><p>小李也对这个自动化过程非常满意。通过自动化脚本，他不仅减少了繁琐的操作，还降低了人为错误的风险。这次经历让小李更加深刻地认识到，Git 不仅是版本控制工具，它也能够与自动化流程紧密结合，提升整个开发流程的效率。</p><h4 id="第三十四章：Git-与团队沟通的桥梁"><a href="#第三十四章：Git-与团队沟通的桥梁" class="headerlink" title="第三十四章：Git 与团队沟通的桥梁"></a><strong>第三十四章：Git 与团队沟通的桥梁</strong></h4><p>随着团队规模和项目复杂度的增加，沟通变得尤为重要。团队成员之间不仅要讨论功能需求，还需要清楚地了解其他成员的工作进展。小李发现，Git 提供的提交记录、分支管理、合并过程和 Pull Request，实际上都为团队沟通提供了强有力的支持。</p><p>一天，小李在查看项目的 Git 提交记录时，注意到某些提交信息比较模糊，无法准确反映开发的进度和内容。他认为，这是团队在使用 Git 时未能充分利用其沟通能力。</p><p>“小王，你觉得 Git 提交信息的重要性有多大？”小李向同事请教。</p><p>“其实提交信息是团队沟通的一个重要桥梁，它可以帮助我们了解每个功能的开发进度和思路。如果提交信息不清晰，团队成员很难理解开发者的意图。” 小王认真回答道。</p><p>“那我们能不能有一个规范，确保每个人在提交时都写清楚相关信息？”小李提议。</p><p>小王点点头：“是的，我们可以制定一些提交信息规范，比如每次提交时都写清楚这次提交的目的、所修复的 bug、改进的功能等。这样可以帮助团队成员快速理解代码的变动，提升沟通效率。”</p><p>于是，小李和团队一起讨论并制定了一份 Git 提交信息规范，要求每次提交时都必须包括以下内容：</p><ol><li><strong>简短的标题</strong>：描述这次提交的主要目的（如“修复登录功能”、“优化数据处理逻辑”）。</li><li><strong>详细的描述</strong>：具体说明这次提交修改了什么，为什么要修改，以及如何修改的。</li><li><strong>关联的 issue 或 bug</strong>：如果是修复 bug 或实现某个需求，提交信息中需要提及关联的 issue 或 bug 编号。</li></ol><p>从那以后，团队的 Git 提交记录变得更加清晰和规范，每个人在查看提交记录时都能迅速理解变更内容。Git 不再只是一个简单的工具，它成为了团队沟通的桥梁，帮助开发者更好地协作。</p><h4 id="第三十五章：Git-与远程协作的无缝结合"><a href="#第三十五章：Git-与远程协作的无缝结合" class="headerlink" title="第三十五章：Git 与远程协作的无缝结合"></a><strong>第三十五章：Git 与远程协作的无缝结合</strong></h4><p>在小李所在的公司，随着项目的扩展，团队成员开始分布在不同的地方，甚至有了远程工作模式。如何保持远程团队的协作效率，成了一个关键问题。小李深知，Git 的强大远程协作能力将在这里发挥巨大的作用。</p><p>“小李，考虑到我们团队已经是远程工作，我们需要有一个标准化的流程来管理远程仓库和分支，确保每个人都能高效协作。”项目经理提出了新的挑战。</p><p>“我们可以利用 GitHub、GitLab 或 Bitbucket 等平台的功能来实现这一点，配合 <code>git fetch</code>、<code>git pull</code>、<code>git push</code>，保证每个人的工作始终与远程仓库同步。” 小李回答道，“我们可以设置清晰的分支管理规则，例如每个功能使用独立的 <code>feature</code> 分支，合并时使用 Pull Request，并且严格要求审查代码。”</p><p>“那这样的话，团队成员都可以在自己的分支上独立工作，最后通过 Pull Request 来进行合并和代码审查，对吗？” 项目经理进一步确认。</p><p>“是的，完全正确。每个人都可以在自己的分支上工作，提交并推送到远程仓库后，其他成员可以随时拉取更新。通过 Pull Request 和代码审查，我们可以确保代码质量，避免直接推送到 <code>main</code> 分支。” 小李解释道。</p><p>为了更好地管理远程协作，小李还建议团队使用 GitHub 的团队管理功能，将每个项目的成员分成不同的权限组，确保项目代码的安全性。</p><p>通过这些措施，团队的远程协作变得越来越顺畅，开发进度也大大加快。Git 成为他们跨时区、跨地域协作的桥梁，保证了远程工作模式下的高效沟通与协作。</p><h4 id="第三十六章：Git-与知识管理的结合"><a href="#第三十六章：Git-与知识管理的结合" class="headerlink" title="第三十六章：Git 与知识管理的结合"></a><strong>第三十六章：Git 与知识管理的结合</strong></h4><p>在一次团队会议上，项目经理提到：“我们团队已经积累了大量的知识和技术文档，如何能够更好地管理这些知识，让每个新加入的成员都能快速了解项目和技术栈？”</p><p>小李听后立即想到了 Git 的优势：“我们可以将这些文档和知识管理工作与 Git 结合起来，创建一个专门的 Git 仓库，来管理项目的技术文档、架构设计以及解决方案等。”</p><p>项目经理表示认可：“这样一来，新成员可以通过 Git 仓库查看我们的文档，也能通过 Git 提交的历史记录了解每个问题的解决过程。”</p><p>小李和团队决定创建一个专门的 Git 仓库，专门用来存储项目文档、设计文档、代码规范和技术文章等。在这个仓库中，所有文档都将与代码一样进行版本控制，每个文档的更新和修改都会记录在 Git 提交历史中，方便后续查看和追溯。</p><p>通过这种方式，团队的知识管理变得更加高效，每个成员都可以随时查看、更新和修改文档，而 Git 则确保了文档的版本控制和更新记录。</p><h4 id="第三十七章：Git-在复杂环境中的应用——跨平台开发与持续集成"><a href="#第三十七章：Git-在复杂环境中的应用——跨平台开发与持续集成" class="headerlink" title="第三十七章：Git 在复杂环境中的应用——跨平台开发与持续集成"></a><strong>第三十七章：Git 在复杂环境中的应用——跨平台开发与持续集成</strong></h4><p>随着项目变得越来越复杂，小李发现开发环境的多样化带来了新的挑战。在一个典型的跨平台开发环境中，团队同时在开发前端、后端、移动应用、甚至机器学习模型等多个部分，而每个部分都有不同的技术栈和依赖。</p><p>“小李，我们的项目现在涉及多个平台，前端是 React，后端是 Node.js，移动端是 React Native，机器学习模型是用 Python 实现的。如何在这些平台之间保持一致，并且高效协作呢？” 项目经理问。</p><p>“这个问题确实不小，”小李思考了一下，“不过我想到了一种方式。我们可以利用 Git 和 Docker 来帮助管理这些平台的环境，确保每个开发者的环境一致。同时，我们可以通过 CI 工具进行自动化测试和构建，确保每个平台的代码都能顺利运行。”</p><p>他继续讲解：“首先，我们可以为每个平台创建独立的 Git 子模块，分别管理每个平台的代码库。然后，我们可以通过 Docker 为每个平台创建独立的开发环境，确保每个开发者都能在本地轻松搭建相同的开发环境。”</p><p>项目经理表示理解并表示认可：“这个思路不错，Git 的子模块能帮助我们管理各个项目，而 Docker 则能保证每个开发者的环境一致。接下来，你可以为我们实现这一方案吗？”</p><p>小李开始实施方案。他为每个平台创建了独立的 Git 子模块，将不同平台的代码库分开管理，确保每个平台的代码能独立开发且相互不干扰。接着，他为每个平台编写了 Dockerfile 文件，确保每个开发者都能通过 Docker 快速搭建一致的开发环境。</p><p>例如，前端的开发环境通过 Docker 配置了 Node.js 和相关的构建工具，而后端则使用了 Express 和数据库连接，机器学习的环境则配置了 Python 和 TensorFlow。通过 Docker 容器，开发者只需要拉取相应的容器镜像，就可以快速搭建起完全一致的开发环境。</p><p>“小李，这个方案非常棒！通过 Docker，我们能够在不同平台之间确保开发环境的一致性，Git 子模块则让我们能更好地管理不同平台的代码。” 项目经理感慨道。</p><p>小李也对这一方案非常满意。通过 Git 和 Docker 的结合，团队能够有效地管理和协作多个平台的开发，避免了环境不一致的问题。同时，自动化测试和持续集成的引入也让开发进度更加高效。</p><h4 id="第三十八章：Git-在数据库版本控制中的重要性"><a href="#第三十八章：Git-在数据库版本控制中的重要性" class="headerlink" title="第三十八章：Git 在数据库版本控制中的重要性"></a><strong>第三十八章：Git 在数据库版本控制中的重要性</strong></h4><p>随着项目的功能越来越丰富，数据库的管理也变得尤为重要。数据库结构、存储过程和数据迁移等方面的变动需要进行详细的记录和管理。小李发现，传统的数据库管理方式往往不够透明，难以追溯数据库的变动历史。于是，他提出了一个新方案：使用 Git 来管理数据库的版本控制。</p><p>“小李，我看到你最近在研究数据库版本控制的方案。你能给我们讲讲怎么将 Git 应用到数据库管理中吗？” 项目经理问。</p><p>“我想到了一个方法，就是通过将数据库的结构和迁移脚本也放入 Git 仓库中，和应用代码一起管理。” 小李回答道。</p><p>他接着解释：“我们可以把每次数据库的变动（例如表结构变更、索引创建、存储过程更新等）写成迁移脚本，并将这些脚本放入 Git 仓库中进行版本控制。每当需要修改数据库结构时，开发者只需编写迁移脚本，并将其提交到 Git 仓库。然后，通过自动化工具或者手动方式运行这些迁移脚本，数据库就能与应用代码同步更新。”</p><p>项目经理觉得这个方案非常有价值，毕竟数据库的变更往往难以管理，Git 的引入能够帮助团队更好地追踪和管理这些变动。</p><p>“小李，你可以为我们建立一个数据库迁移管理流程吗？” 项目经理问道。</p><p>小李开始构建这个方案。他创建了一个新的 Git 仓库，用于存储所有数据库的迁移脚本。每次数据库结构有变化时，开发者会编写相应的迁移脚本，提交到 Git 仓库中。迁移脚本的命名规则也被统一，以便于后续的管理和回滚。</p><p>当需要更新数据库时，开发者会使用一个数据库迁移工具（例如 Flyway 或 Liquibase）来自动应用这些脚本，确保数据库与应用代码保持一致。如果数据库出现问题，可以通过 Git 历史记录查看之前的变更，并回滚到某个历史版本。</p><p>“小李，你的这个方案太好了！数据库变更再也不需要手动记录和更新了，所有的变动都可以通过 Git 来追溯。” 项目经理激动地说道。</p><p>通过将数据库版本控制与 Git 集成，团队不仅提高了管理效率，还增加了开发过程中的透明度和可追溯性。</p><h4 id="第三十九章：Git-与团队文化的融合"><a href="#第三十九章：Git-与团队文化的融合" class="headerlink" title="第三十九章：Git 与团队文化的融合"></a><strong>第三十九章：Git 与团队文化的融合</strong></h4><p>随着项目逐渐走向成熟，小李逐渐意识到，Git 不仅仅是一个技术工具，它也在潜移默化地影响着团队的文化。在团队的协作中，Git 强调了透明度、代码的可追溯性和团队成员之间的高效沟通。</p><p>“小李，最近我注意到，团队成员在协作时越来越注重代码的清晰度和规范性，大家的合作也越来越顺畅。” 项目经理感慨道，“你觉得这与 Git 的使用有关系吗？”</p><p>小李点点头：“有很大关系。Git 强调了每个人对代码的责任和透明度。每次提交都可以清晰地查看修改内容，团队成员通过 <code>git diff</code> 和 <code>git log</code> 能够清楚地看到彼此的工作。这不仅提升了代码质量，也促进了团队成员之间的有效沟通。”</p><p>项目经理继续说：“我还发现，团队成员越来越注重提交信息的规范，大家在写提交信息时，会写得很清晰，标明修复的 bug、优化的功能以及其他细节。这种良好的习惯促进了团队的协作和项目的高效推进。”</p><p>小李微笑着回答：“是的，Git 的提交信息规范性确实对团队合作有很大影响。当每个开发者都知道自己提交的每一行代码都需要经过审查并且能被追溯时，大家会更自觉地保证代码的质量和清晰度。Git 让我们在开发过程中养成了良好的编码习惯，也提升了团队协作的效率。”</p><p>项目经理点头赞同：“Git 作为我们的核心工具，不仅帮助我们管理版本、协作开发，还帮助我们树立了良好的团队文化。我们现在的开发过程更加高效，团队也更加和谐。”</p><h4 id="第四十章：Git-与-DevSecOps-的结合"><a href="#第四十章：Git-与-DevSecOps-的结合" class="headerlink" title="第四十章：Git 与 DevSecOps 的结合"></a><strong>第四十章：Git 与 DevSecOps 的结合</strong></h4><p>随着公司项目和团队规模的扩大，小李逐渐意识到，除了版本控制和协作，Git 在保障代码安全性方面也能发挥重要作用。在项目中，团队开始引入 DevSecOps（开发、运维和安全一体化）的概念，确保代码在整个开发周期中都能遵循最佳的安全实践。</p><p>“小李，我们最近决定加强项目的安全性，将安全控制纳入到开发流程中，你觉得 Git 在这个过程中能扮演什么角色？” 项目经理问道。</p><p>小李思索了一下，回答道：“Git 在 DevSecOps 中的作用主要体现在两个方面。一方面，它能够帮助我们确保代码的安全性，通过代码审查和历史提交记录，让我们清晰地了解代码变动，确保没有恶意或不安全的代码被引入；另一方面，Git 还能够与安全工具集成，通过 CI&#x2F;CD 管道自动化地检测漏洞，保证每次代码提交都经过安全审查。”</p><p>“能举个例子吗？” 项目经理感兴趣地问道。</p><p>小李接着解释道：“举个例子，我们可以在 Git 提交时，集成一些静态代码分析工具，例如 SonarQube 或 Checkmarx，这些工具会扫描每次提交的代码，检测潜在的漏洞或安全隐患。如果发现问题，Git 会阻止代码的合并，直到问题被修复。通过这种方式，我们能够确保每个提交都符合安全标准。”</p><p>项目经理点头称赞：“这个思路很好，Git 不仅能管理版本，还能与安全工具紧密结合，自动化检查代码中的安全问题。这将大大提高我们的安全性，避免漏洞进入生产环境。”</p><p>小李开始为团队配置了与 Git 集成的安全工具。他设置了 SonarQube，确保每次代码提交时，都会自动进行静态代码分析，并通过 Git 的 hook 阻止潜在的漏洞进入主分支。通过这种方式，团队确保每次提交的代码都能符合安全标准，而不需要人工介入。</p><h4 id="第四十一章：Git-在微服务架构中的管理"><a href="#第四十一章：Git-在微服务架构中的管理" class="headerlink" title="第四十一章：Git 在微服务架构中的管理"></a><strong>第四十一章：Git 在微服务架构中的管理</strong></h4><p>随着项目架构的复杂性增加，团队决定将项目的开发模式转向微服务架构。微服务架构将应用拆分成多个独立的服务，每个服务都负责一个特定的功能模块，能够独立部署、独立开发，并且通过 API 进行互联。</p><p>“小李，随着我们逐渐采用微服务架构，如何高效地管理每个微服务的代码和依赖呢？” 项目经理问道。</p><p>“我们可以使用 Git 来管理每个微服务的代码库，确保每个微服务都能独立开发和部署。” 小李回答道，“每个微服务都可以作为一个独立的 Git 仓库来管理，这样能够保证服务之间的独立性，并且可以通过 Git 的 Submodule 或者 Subtree 来管理微服务之间的依赖。”</p><p>“小李，那微服务间如何进行版本控制和依赖管理呢？” 项目经理继续问。</p><p>“我们可以在每个微服务的 Git 仓库中使用标签（<code>tag</code>）来标记每个版本，确保我们可以回溯历史版本。” 小李进一步解释道，“此外，通过在 Git 仓库中配置 CI&#x2F;CD 流程，每个微服务都能独立构建、测试和部署，确保每个服务的更新不会影响其他服务的运行。”</p><p>项目经理表示赞同：“这样的话，每个微服务就能独立管理，同时保持与其他服务的同步，减少了系统的耦合性。”</p><p>小李帮助团队将 Git 应用于微服务架构的管理中，为每个微服务创建了独立的 Git 仓库，并通过 Git 子模块来管理服务之间的依赖。通过这种方式，每个微服务的代码都能独立更新和发布，而 Git 确保了服务之间的同步和版本控制。</p><h4 id="第四十二章：Git-与团队协作工具的集成"><a href="#第四十二章：Git-与团队协作工具的集成" class="headerlink" title="第四十二章：Git 与团队协作工具的集成"></a><strong>第四十二章：Git 与团队协作工具的集成</strong></h4><p>随着团队逐渐壮大，协作工具的使用也变得越来越重要。小李发现，除了 Git，本地的代码管理，团队还需要借助 Slack、Jira、Trello 等工具进行任务管理、沟通和协作。为了提高工作效率，如何将 Git 与这些工具集成，成为了小李的新挑战。</p><p>“小李，我们希望在每次代码提交时，能够自动通知团队成员并更新任务进度，你觉得 Git 能和这些工具集成吗？” 项目经理问道。</p><p>“完全可以，” 小李答道，“我们可以利用 Git 提供的 webhook 功能，将 Git 与 Slack、Jira 等工具集成。当我们提交代码时，可以自动通过 webhook 发送消息到 Slack，通知团队成员代码的更新；同时，我们也可以自动更新 Jira 上的任务状态，让团队成员更清晰地知道当前任务的进度。”</p><p>项目经理听后非常赞同：“这能大大提高我们的沟通效率，让每个团队成员都能第一时间知道任务进展。”</p><p>小李立即开始为团队配置 Git 与 Slack 和 Jira 的集成。他设置了 GitHub 的 webhook，在每次提交时，自动发送消息到团队的 Slack 频道，告知大家新的功能开发进度或 bug 修复的状态。同时，他也将 Jira 与 GitHub 集成，每次提交代码时，相关的任务会自动更新状态，确保任务进展清晰可见。</p><p>“小李，太棒了！通过这种集成，我们的沟通效率大大提高，团队成员能够第一时间看到任务的进展和代码更新。” 项目经理感慨道。</p><h4 id="第四十三章：Git-的未来——AI-与自动化的结合"><a href="#第四十三章：Git-的未来——AI-与自动化的结合" class="headerlink" title="第四十三章：Git 的未来——AI 与自动化的结合"></a><strong>第四十三章：Git 的未来——AI 与自动化的结合</strong></h4><p>随着技术的不断进步，小李看到 Git 的发展趋势将不仅仅停留在传统的版本控制和协作管理上。人工智能（AI）和自动化工具的结合，将使 Git 变得更加智能化，能够自动分析代码质量，提供代码优化建议，甚至自动修复常见的代码问题。</p><p>“小李，你认为 Git 在未来的发展会是怎样的？” 项目经理问道。</p><p>“我认为，未来 Git 会与 AI 更紧密地结合，自动化和智能化的特性将进一步增强。” 小李回答道，“例如，Git 将能够通过 AI 自动分析代码的质量、风格和结构，自动生成优化建议，并通过机器学习模型来预测代码的潜在问题。未来的 Git 可能不仅仅是一个代码管理工具，它也可以作为开发中的智能助手，帮助开发者更高效地编写和管理代码。”</p><p>项目经理笑了笑：“这个思路非常前瞻，的确，AI 与 Git 的结合将为开发者带来更多的便利。也许在不久的将来，Git 就不再是一个仅仅执行命令的工具，而是一个智能的合作伙伴，主动为开发者提供帮助。”</p><p>小李对未来的 Git 充满期待。他相信，随着 AI 和自动化技术的发展，Git 将继续进化，成为开发者不可或缺的智能伙伴，不仅帮助管理版本和代码，更会成为整个开发过程中的决策支持系统。</p><h4 id="第四十四章：Git-与版本控制系统的进化"><a href="#第四十四章：Git-与版本控制系统的进化" class="headerlink" title="第四十四章：Git 与版本控制系统的进化"></a><strong>第四十四章：Git 与版本控制系统的进化</strong></h4><p>随着技术的不断发展和项目需求的不断增加，小李渐渐意识到，Git 的功能已远远超出了传统的版本控制工具的范畴。越来越多的团队开始探索如何利用 Git 在更高层次的工作流和管理中发挥作用。小李对 Git 的深度掌握也逐渐使他开始考虑，未来是否有可能会出现新的版本控制系统，或者 Git 会如何进化以适应未来的需求。</p><p>“小李，你觉得 Git 在未来会如何发展？是否会有其他版本控制工具取代 Git 的地位？” 项目经理在一次会议中向小李提出了这个问题。</p><p>小李思索了一下，回答道：“我认为 Git 目前仍然是最强大的版本控制系统，它已经深深扎根于开发者的工作流中。不过，随着大数据、分布式计算和人工智能的快速发展，Git 可能会在未来进行一些技术性进化，特别是在性能优化、协作和自动化方面。可能我们会看到更多与 Git 集成的工具，或是智能化的 Git 提示系统，甚至 Git 会在管理大规模分布式系统中变得更加高效。”</p><p>他继续说道：“目前的 Git 是为开发者和小型团队设计的，但随着大型开源项目和企业级开发的需求增加，Git 在性能和复杂工作流方面仍然存在一定的局限性。未来可能会出现一种更加适应大规模并行开发、全球分布式协作和高度自动化的版本控制系统。”</p><p>项目经理点头表示认可：“你说得对，随着开发需求的多样化，Git 也必须不断进化，才能保持其在开发中的核心地位。”</p><p>小李也意识到，尽管 Git 强大且灵活，但未来开发模式的变化无疑将推动版本控制系统的发展。基于人工智能和机器学习的自动化工具，可能会为 Git 提供更加智能的版本分析和优化建议，同时帮助开发者快速发现潜在问题，甚至自动修复常见的代码错误。未来的 Git，可能不仅仅是一个代码管理工具，它可能变成一个主动帮助开发者的智能平台。</p><h4 id="第四十五章：Git-与云原生技术的结合"><a href="#第四十五章：Git-与云原生技术的结合" class="headerlink" title="第四十五章：Git 与云原生技术的结合"></a><strong>第四十五章：Git 与云原生技术的结合</strong></h4><p>随着 Kubernetes、Docker 等云原生技术的普及，团队的工作流程和开发环境也发生了巨大的变化。尤其是在微服务架构、容器化和云部署方面，Git 被赋予了新的角色。小李看到了 Git 与云原生技术结合的巨大潜力，开始思考如何将 Git 更好地融入到云原生的开发和部署流程中。</p><p>“小李，考虑到我们现在使用的 Kubernetes 和 Docker，如何将 Git 与这些工具结合，以便更好地支持我们在云环境中的开发和部署？” 项目经理提出了新的挑战。</p><p>小李微笑着回答：“Git 在云原生开发中的作用将变得越来越重要。它不仅可以用于代码管理和版本控制，还能与 CI&#x2F;CD 工具和容器化管理平台如 Docker 和 Kubernetes 紧密集成。通过 Git，我们可以管理云原生应用的代码、配置和容器镜像的版本，并通过 Git 的工作流管理持续集成和交付。”</p><p>“能不能更具体地说说 Git 如何与这些工具集成？” 项目经理继续问。</p><p>“当然。我们可以通过 Git 来管理 Kubernetes 的 YAML 配置文件和 Dockerfile，每次提交都会自动更新这些文件并生成新的容器镜像。然后，我们可以将 Git 与 CI&#x2F;CD 工具（如 Jenkins、GitLab CI 或 CircleCI）集成，让每次代码提交都触发自动化构建、测试和部署流程。当新的容器镜像构建完成后，Kubernetes 可以自动更新集群中的服务，确保所有服务运行的是最新版本的容器。” 小李解释道。</p><p>小李随后展示了如何将 Git、Docker 和 Kubernetes 融合在一起。他为团队配置了 GitLab CI，通过 GitLab 的 CI 管道，自动化地将每次提交的代码构建成 Docker 镜像，并将其推送到容器镜像仓库。然后，GitLab CI 会触发 Kubernetes 的滚动更新，将最新的容器镜像部署到集群中。</p><p>“这样一来，团队可以通过 Git 统一管理应用代码、容器镜像和配置文件，所有的操作都可以自动化完成，大大提升了开发和部署效率。” 小李补充道。</p><p>项目经理非常满意：“通过 Git、Docker 和 Kubernetes 的结合，我们能够实现更高效的开发和部署流程，这样我们的云原生应用管理会变得更加灵活和自动化。”</p><h4 id="第四十六章：Git-与人工智能的融合——自动化代码审查"><a href="#第四十六章：Git-与人工智能的融合——自动化代码审查" class="headerlink" title="第四十六章：Git 与人工智能的融合——自动化代码审查"></a><strong>第四十六章：Git 与人工智能的融合——自动化代码审查</strong></h4><p>小李对 Git 的理解越来越深，他意识到，随着人工智能（AI）的发展，Git 与 AI 的结合将带来革命性的变化，尤其是在代码审查和质量保障方面。人工智能的智能化算法可以帮助自动化审查每次提交的代码，检测潜在的 bug、代码风格问题、甚至安全漏洞，从而进一步提高开发效率和代码质量。</p><p>“小李，你提到过 Git 可以与人工智能结合，我非常感兴趣。你认为 Git 和 AI 会如何结合呢？” 项目经理问道。</p><p>小李兴奋地回答：“未来 Git 可以与 AI 技术结合，自动化地分析每次提交的代码并提供反馈。比如，AI 可以通过机器学习算法分析代码的历史版本，识别出常见的代码问题、低效的代码结构和潜在的安全漏洞。每当开发者提交代码时，AI 会自动进行审查，甚至给出优化建议，帮助开发者提高代码质量。”</p><p>他继续解释：“AI 还可以根据项目的代码库和历史记录学习最佳实践，为每次提交提供个性化的建议。此外，AI 还可以通过分析大量的开源项目，帮助开发者学习其他项目的代码风格和技术实现，优化自己的代码。”</p><p>小李展示了如何将 Git 与 AI 集成，利用 GitHub Actions 和第三方 AI 插件，自动对每次提交的代码进行静态分析，检测潜在的 bug 和性能瓶颈。通过这种方式，开发者可以在代码提交之前获得实时的反馈，大大减少了错误的发生，并提高了代码的质量。</p><p>项目经理听后非常赞赏：“这个方案非常前沿，未来的 Git 不仅仅是一个版本控制工具，它还能成为智能的代码审查助手，主动帮助开发者提升代码质量。”</p><h4 id="第四十七章：Git-与团队文化的深度融合"><a href="#第四十七章：Git-与团队文化的深度融合" class="headerlink" title="第四十七章：Git 与团队文化的深度融合"></a><strong>第四十七章：Git 与团队文化的深度融合</strong></h4><p>随着时间的推移，Git 不仅仅作为一个技术工具融入了团队的开发流程，也深刻影响了团队的文化。通过 Git，团队逐渐形成了一种透明、开放、协作的文化，所有的代码变更都能被追溯，团队成员之间的沟通变得更加顺畅。</p><p>“小李，最近我发现团队成员之间的沟通和协作变得更加高效了，大家在提交代码时，都在 <code>commit message</code> 中写得很清楚，代码审查时也能快速理解每个提交的目的和实现。” 项目经理总结道。</p><p>小李点头表示同意：“是的，Git 让我们的团队更透明。每个提交记录不仅仅是对代码的记录，也是对开发过程的记录。通过清晰的提交信息和 Pull Request，我们能够看到每个开发者的工作进展和思路，及时发现并解决问题。”</p><p>项目经理继续说道：“而且，Git 让我们保持了高效的协作，每个人都能在自己的分支上独立开发，只有经过审查和确认后，才会合并到主分支。这样我们不仅能保证代码质量，也避免了直接推送到主分支可能带来的风险。”</p><p>小李感慨道：“Git 让我们建立了一个高效、透明的开发文化，不仅提高了工作效率，还增强了团队之间的信任和合作。”</p><h4 id="第四十八章：Git-与创新的结合——开源贡献与全球合作"><a href="#第四十八章：Git-与创新的结合——开源贡献与全球合作" class="headerlink" title="第四十八章：Git 与创新的结合——开源贡献与全球合作"></a><strong>第四十八章：Git 与创新的结合——开源贡献与全球合作</strong></h4><p>随着团队项目的不断发展，小李的思维也逐渐扩展到全球开源社区的协作。在一次技术分享会上，项目经理提到了一个新的发展方向：“小李，我们是否可以将我们的部分技术栈或工具开源，贡献给全球开发者社区，获得更多的反馈和贡献？”</p><p>小李顿时眼前一亮：“开源是一个很好的方式，可以通过全球社区的力量推动项目进步。同时，Git 本身就是开源的，已经深深融入了全球开发者的工作流。我们可以通过 GitHub 或 GitLab 等平台将我们的项目开源，让更多的人参与进来。”</p><p>“你说得对，开源不仅能促进技术创新，还能让我们获得来自全球的反馈。” 项目经理笑道，“那我们开始考虑如何将我们的项目开源吧。”</p><p>小李开始探索如何将团队开发的工具和库开源，并通过 Git 管理开源项目。首先，他为项目创建了一个 GitHub 仓库，并在仓库中提供了详细的文档、安装指南、功能说明等内容。接着，他将项目代码整理好，确保它能与其他开发者无缝集成。</p><p>在开源项目的管理过程中，小李使用 Git 的 Pull Request 工作流来管理外部贡献。当其他开发者提出修改或优化时，团队通过审查 Pull Request 来评估贡献质量，最终合并到主仓库中。这种流程帮助团队高效管理外部的贡献，同时确保代码质量和稳定性。</p><p>“小李，看到这些来自全球开发者的贡献，我们不仅提高了工具的质量，也扩大了团队的影响力。” 项目经理感叹道，“通过 Git 和开源，我们实现了全球范围的技术合作。”</p><p>小李也感到无比兴奋，他知道开源不仅是对技术的贡献，更是一种全球化的合作方式。通过 Git 管理的开源项目，他与全球开发者共同推动了技术的进步。</p><h4 id="第四十九章：Git-与-DevOps-2-0——智能化运维与持续交付"><a href="#第四十九章：Git-与-DevOps-2-0——智能化运维与持续交付" class="headerlink" title="第四十九章：Git 与 DevOps 2.0——智能化运维与持续交付"></a><strong>第四十九章：Git 与 DevOps 2.0——智能化运维与持续交付</strong></h4><p>在一次团队会议中，随着 DevOps 实践的深入，小李意识到，随着自动化、人工智能和机器学习的引入，DevOps 也逐渐进入了智能化的新阶段。团队在原有的 CI&#x2F;CD 管道上进一步优化，加入了智能化运维和持续交付（Continuous Delivery，CD）的理念。</p><p>“小李，现在我们已经将 CI&#x2F;CD 集成到项目中，但如何进一步优化并提高运维的智能化水平呢？” 项目经理问道。</p><p>小李思索了一下，回答道：“我认为，我们可以通过将 AI 和机器学习引入到 DevOps 流程中，提升运维和发布的智能化水平。Git 可以与这些新技术结合，通过持续监控、数据分析和预测算法，帮助团队提前发现潜在的问题，甚至自动执行修复。”</p><p>他继续解释：“例如，我们可以通过 Git 与机器学习模型的结合，分析每次提交的代码，预测可能的性能瓶颈或代码错误。根据这些预测，CI&#x2F;CD 管道可以提前生成优化建议，甚至自动修复代码中的常见问题。”</p><p>项目经理对这个思路非常感兴趣：“这个方向非常有前景。Git 不仅仅是管理版本的工具，未来它还可能成为我们 DevOps 2.0 中智能化运维的一部分。”</p><p>小李开始着手实现这一方案。他集成了 Git、Jenkins 和 AI 模型，通过分析每次提交的代码，预测潜在的性能问题和安全漏洞，并将预测结果通过 Git 提交记录反馈给开发者。当 Git 检测到潜在问题时，自动发出警告，并触发自动化修复任务。</p><p>“通过 Git 与 AI 的结合，我们的 DevOps 2.0 管道变得更加智能化，能够自动监控、分析和修复代码中的问题，减少了人工干预。” 小李兴奋地说道。</p><p>项目经理也非常满意：“这不仅能提高开发效率，还能让我们更早地发现并解决问题，减少了运维的负担。”</p><h4 id="第五十章：Git-与自动化测试的无缝对接"><a href="#第五十章：Git-与自动化测试的无缝对接" class="headerlink" title="第五十章：Git 与自动化测试的无缝对接"></a><strong>第五十章：Git 与自动化测试的无缝对接</strong></h4><p>随着团队对自动化的需求增加，团队决定将自动化测试与 Git 完全集成，确保每次提交和部署都能通过自动化测试来保障代码质量。在过去的项目中，虽然有一些自动化测试，但由于没有与 Git 紧密结合，很多时候测试结果无法及时反馈给开发者，导致修复过程缓慢。</p><p>“小李，我们希望能够将自动化测试与 Git 完美集成，实现每次提交后自动执行单元测试，并将测试结果即时反馈给开发者。” 项目经理提出了新的需求。</p><p>“这个方案完全可以实现。” 小李回答道，“通过 Git 与 CI 工具的结合，我们可以在每次代码提交后自动触发测试脚本，确保代码的质量不受影响。同时，我们还可以将测试结果实时显示在 GitHub 或 GitLab 上，开发者可以第一时间看到自己的提交是否通过了测试。”</p><p>于是，小李开始配置 Git 与 Jenkins 和 Selenium 的集成。每次团队成员向 Git 提交代码时，Jenkins 会自动拉取代码并执行自动化测试，确保每个功能模块都经过严格的测试。测试结果会通过邮件或 Git 的通知功能反馈给开发者，确保开发者能及时修复问题。</p><p>“小李，这样一来，我们就能确保每次提交的代码都经过了自动化测试，不仅提高了代码质量，还能加快开发进程。” 项目经理满意地说道。</p><p>小李也意识到，Git 与自动化测试的结合将使开发流程更加高效和安全，减少了因人为错误或疏忽导致的问题。</p><h4 id="第五十一章：Git-与云平台的深度集成"><a href="#第五十一章：Git-与云平台的深度集成" class="headerlink" title="第五十一章：Git 与云平台的深度集成"></a><strong>第五十一章：Git 与云平台的深度集成</strong></h4><p>随着公司逐渐迁移到云平台进行开发和部署，云平台成为了小李和团队工作流程中的重要部分。如何将 Git 与云平台无缝结合，成为了小李在这一阶段的新挑战。</p><p>“小李，我们已经在使用 AWS 和 Azure 进行开发和部署，但如何将 Git 与云平台更好地结合，形成一个高效的开发和部署流程？” 项目经理问道。</p><p>“我们可以通过 Git 与云平台的 DevOps 工具集成，来实现自动化构建、部署和监控。” 小李回答道，“例如，通过将 Git 与 AWS CodePipeline 或 Azure DevOps 集成，每次代码提交后，Git 会自动触发云平台的构建和部署流程。我们还可以通过云平台的监控工具实时获取应用的运行状态，确保代码始终在稳定的环境中运行。”</p><p>小李帮助团队配置了 AWS CodePipeline，并通过 GitHub 集成了自动构建和部署流程。每当团队成员提交代码时，AWS CodePipeline 会自动拉取代码、构建应用并部署到 AWS 上进行运行。通过云平台的监控工具，团队可以实时查看应用的健康状态，确保应用的稳定性。</p><p>“通过 Git 与云平台的集成，我们能够将开发和部署完全自动化，不仅减少了人工操作，还能实时监控应用的状态。” 小李总结道。</p><p>项目经理对这个集成方案表示高度满意：“这大大提高了我们团队的开发和部署效率，也让我们在云平台上的管理更加便捷。”</p><h4 id="第五十二章：：Git——开发者永不止步的伙伴"><a href="#第五十二章：：Git——开发者永不止步的伙伴" class="headerlink" title="第五十二章：：Git——开发者永不止步的伙伴"></a><strong>第五十二章：：Git——开发者永不止步的伙伴</strong></h4><p>回顾这些年，小李的 Git 旅程不仅仅是一个技术学习的过程，它已经深刻地影响了团队的工作方式，推动了团队从传统开发模式走向 DevOps、AI 和云原生的未来。Git 不仅仅是一个版本控制工具，它已经演化为开发流程的核心，融入了团队的文化和协作方式。</p><p>“小李，你的努力让我们的团队在技术上不断创新，从自动化测试到云平台集成，Git 在我们的开发流程中扮演了越来越重要的角色。” 项目经理感慨道。</p><p>“Git 是我职业生涯中最重要的伙伴，它让我从一个新手开发者成长为今天的技术专家。未来我会继续学习，继续进步。” 小李自信地回答。</p><p>无论技术如何进化，Git 都将继续成为开发者的重要工具，帮助他们管理代码、提升协作效率、自动化工作流程。随着时代的变化，Git 也将继续为开发者提供强大的支持，成为他们永不止步的伙伴。</p>]]></content>
    
    
    <summary type="html">以故事形式讲解 Git 版本管理，从新手入职到项目协作的 Git 学习之路。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="GIT" scheme="https://blog.no-claw.com/tags/GIT/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（七）： 解析 Docker Compose Override</title>
    <link href="https://blog.no-claw.com/posts/7ac076b/"/>
    <id>https://blog.no-claw.com/posts/7ac076b/</id>
    <published>2025-07-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>看过很多的 Docker 教程，也都不曾提到过 compose override，第一次接触到这个是在懒猫微服上解开 LPK 看到的，用来注入 docker 引擎的环境变量。但是还以为是懒猫微服的小技巧，今天整理笔记才发现原来的 Docker compose 用来做多环境部署的配置文件，比如用来给开发和生产分别注入不同的环境变量和配置文件。</p><blockquote><p>参考文档：<a href="https://developer.lazycat.cloud/advanced-compose-override.html">Docker Compose Override - LazyCat Developer Guide</a></p></blockquote> <span id="more"></span><p>使用场景是这样，在实际开发中，通常我们需要分别为开发和生产环境配置不同的服务和环境变量。虽然可以为每个环境维护独立的 Compose 文件，Docker Compose 提供了一个非常有用的特性，可以将多个 Compose 文件结合使用，简化配置管理。</p><ul><li><strong>基础配置文件</strong>：第一个 Compose 文件通常作为基础配置，后续的文件可以覆盖该基础文件中的配置。</li><li><strong>覆盖配置</strong>：每个额外的文件不仅可以覆盖基础文件中的已有配置，还可以添加新的配置。</li></ul><p>默认情况下，Compose 会读取以下两个文件：</p><ul><li><strong>docker-compose.yml</strong>：基础配置文件</li><li><strong>docker-compose.override.yml</strong>：覆盖文件</li></ul><p>你还可以通过 <code>-f</code> 参数指定多个非默认的覆盖文件，Compose 会按顺序合并这些文件。</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">docker compose -f docker-compose.yml -f dev.override.yml up</span><br></pre></td></tr></table></figure><ul><li><strong>docker-compose config</strong>：这是一个非常有用的命令，可以帮助你验证最终的配置文件，尤其是在使用多个 Compose 文件时。它显示了合并后的 Compose 配置，帮助你确保配置符合预期。</li></ul><h3 id="示例：Nginx-配置"><a href="#示例：Nginx-配置" class="headerlink" title="示例：Nginx 配置"></a>示例：Nginx 配置</h3><h4 id="docker-compose-yml"><a href="#docker-compose-yml" class="headerlink" title="docker-compose.yml"></a>docker-compose.yml</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br></pre></td></tr></table></figure><h4 id="docker-compose-override-yml"><a href="#docker-compose-override-yml" class="headerlink" title="docker-compose.override.yml"></a>docker-compose.override.yml</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./dev:/usr/share/nginx/html</span> <span class="comment"># 使用本地开发文件夹覆盖默认卷</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DEBUG=true</span> <span class="comment"># 启用开发环境的调试模式</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250717205319123-20250717213705118.png" alt="image-20250717205319123"></p><p>在这个例子中，<code>docker-compose.override.yml</code> 覆盖了 <code>docker-compose.yml</code> 中的配置，添加了开发环境相关的文件挂载和环境变量设置。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250717205507275.png" alt="image-20250717205507275"></p><h3 id="合并后的配置（查看通过-docker-compose-config-命令生成的配置）"><a href="#合并后的配置（查看通过-docker-compose-config-命令生成的配置）" class="headerlink" title="合并后的配置（查看通过 docker-compose config 命令生成的配置）"></a>合并后的配置（查看通过 <code>docker-compose config</code> 命令生成的配置）</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><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">name: <span class="string">&quot;3&quot;</span></span><br><span class="line">services:</span><br><span class="line">  web:</span><br><span class="line">    environment:</span><br><span class="line">      DEBUG: <span class="string">&quot;true&quot;</span></span><br><span class="line">    image: nginx:latest</span><br><span class="line">    networks:</span><br><span class="line">      default: null</span><br><span class="line">    ports:</span><br><span class="line">      - mode: ingress</span><br><span class="line">        target: 80</span><br><span class="line">        published: <span class="string">&quot;80&quot;</span></span><br><span class="line">        protocol: tcp</span><br><span class="line">    volumes:</span><br><span class="line">      - <span class="built_in">type</span>: <span class="built_in">bind</span></span><br><span class="line">        <span class="built_in">source</span>: /Users/name/Desktop/dev</span><br><span class="line">        target: /usr/share/nginx/html</span><br><span class="line">        <span class="built_in">bind</span>:</span><br><span class="line">          create_host_path: <span class="literal">true</span></span><br><span class="line">networks:</span><br><span class="line">  default:</span><br><span class="line">    name: 3_default</span><br></pre></td></tr></table></figure><p>可以看到，通过合并配置，开发环境的调试模式和本地文件夹挂载已经成功加入了配置。</p><h3 id="懒猫应用的上的-compose-override"><a href="#懒猫应用的上的-compose-override" class="headerlink" title="懒猫应用的上的 compose override"></a>懒猫应用的上的 compose override</h3><p>针对一些 lpk 规范目前无法覆盖到的运行权限需求， 可以通过 <a href="https://docs.docker.com/reference/compose-file/merge/">compose override</a> 机制来间接实现。</p><p>通过应用查看器可以看到，这是 <strong>Docker Compose</strong> 配置的一部分，用于定义容器中的 <code>containly</code> 服务，并映射 playground 的 docker 引擎。具体的配置说明如下：</p><ul><li><strong>services</strong>: 这是 Docker Compose 文件的顶级字段，定义了服务列表。<code>containly</code> 是定义的一个服务名称。</li><li><strong>containly</strong>: 这是服务的名称。在此配置下，的服务名是 <code>containly</code>。</li><li><strong>volumes</strong>: 定义了容器与宿主机（本地）之间的文件夹共享和挂载。该部分的配置是映射一个本地目录到容器内部的目录。<ul><li><strong>bind</strong>: 使用绑定挂载的方式（bind mount），允许宿主机的文件或目录直接映射到容器内部。这里设置了 <code>create_host_path: true</code>，意思是如果宿主机上的 <code>/data/playground</code> 目录不存在，它会自动创建。</li><li><strong>source</strong>: 宿主机的路径，映射为容器中的目录。这里指定了 <code>/data/playground</code> 作为源路径，意味着宿主机上的这个目录将被挂载到容器内。</li><li><strong>target</strong>: 容器内的路径，即宿主机上的 <code>source</code> 目录映射到容器内部的 <code>/lzcapp/run/playground</code> 目录。容器内的应用可以访问这个目录。</li><li><strong>type</strong>: 这里设置的是 <code>bind</code>，表示采用绑定挂载方式</li></ul></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250717210507516.png" alt="image-20250717210507516"></p><p>最后 highlight 下 WIKI 里的三句话：</p><ol><li><strong>确认最终生成的 <code>lpk</code> 中存在名为 <code>compose.override.yml</code> 的文件</strong>，并且内容是一个合法的 Compose 合并文件。</li><li><strong>通过 SSH 进入 <code>/data/system/pkgm/run/$appid</code> 目录</strong>，确认该目录下是否存在 <code>override.yml</code> 文件。</li><li><strong>使用 <code>lzc-docker-compose config</code> 命令查看最终合并后的配置</strong>，确保它符合预期。</li></ol><p>子&#x2F;data&#x2F;system&#x2F;pkgm&#x2F;run&#x2F;$appid 目录里，我的结果如下,供参考。</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></pre></td><td class="code"><pre><span class="line">docker compose config</span><br><span class="line"></span><br><span class="line">name: xudeploycontainly</span><br><span class="line">services:</span><br><span class="line">  app:</span><br><span class="line">....</span><br><span class="line">- <span class="built_in">type</span>: <span class="built_in">bind</span></span><br><span class="line">        <span class="built_in">source</span>: /data/playground</span><br><span class="line">        target: /lzcapp/run/playground</span><br><span class="line">        <span class="built_in">bind</span>:</span><br><span class="line">          create_host_path: <span class="literal">true</span></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">(base) lzcbox-029c588e /data/system/pkgm/run/xu.deploy.containly <span class="comment"># ls</span></span><br><span class="line">compose.override.yml  docker-compose.yml  pkg</span><br><span class="line">(base) lzcbox-029c588e /data/system/pkgm/run/xu.deploy.containly <span class="comment"># cat compose.override.yml</span></span><br><span class="line">services:</span><br><span class="line">  containly:</span><br><span class="line">    volumes:</span><br><span class="line">      - <span class="built_in">bind</span>:</span><br><span class="line">          create_host_path: <span class="literal">true</span></span><br><span class="line">        <span class="built_in">source</span>: /data/playground</span><br><span class="line">        target: /lzcapp/run/playground</span><br><span class="line">        <span class="built_in">type</span>: <span class="built_in">bind</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">详解 Docker Compose Override 机制及其在懒猫微服开发中的应用</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十一）：把懒猫微服当作旁路由，客户端无需转发直接访问内外地址</title>
    <link href="https://blog.no-claw.com/posts/5018903b/"/>
    <id>https://blog.no-claw.com/posts/5018903b/</id>
    <published>2025-07-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天在外面通过懒猫转发访问路由器时，发现需要逐一映射多个内网地址，操作起来有点麻烦，于是想到了一种一劳永逸的解决办法。</p><p>最开始我用 Squid 来代理 AWS VPC，后来想到懒猫也可以用作同样的功能。以下是我准备的 Compose 配置，我已经完成了 app 镜像的复制。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">squid:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">squid</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">3128</span><span class="string">:3128</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/ubuntu/squid:c534e32e8e1e766e</span></span><br><span class="line"><span class="attr">networks:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>下面是 Docker 截图：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250718122636189-20250718130918029.png" alt="image-20250718122636189"></p><p>默认安装后，Squid 的默认规则会屏蔽所有网站。若要访问内网地址，就会看到“访问被拒绝”的提示。接下来，我们需要修改转发规则。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250718124511756.png" alt="image-20250718124511756"></p><p>进入 Squid 容器，编辑配置文件，然后重启容器即可生效：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/squid/squid.conf</span><br><span class="line"># 在文件底部添加以下行以允许所有 HTTP 访问</span><br><span class="line">http_access allow all</span><br></pre></td></tr></table></figure><p>现在就可以使用你喜欢的工具进行访问了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250718124727452.png" alt="image-20250718124727452"></p><p>然后就可以在外边访问私有地址了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250718150626296.png" alt="image-20250718150626296"></p><p>或者在终端上，通过设置环境变量以通过代理服务器访问 HTTP 和 HTTPS：</p><figure class="highlight plaintext"><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">export http_proxy=http://your_proxy_ip:3128</span><br><span class="line">export https_proxy=http://your_proxy_ip:3128</span><br></pre></td></tr></table></figure><h4 id="如果你需要白名单功能："><a href="#如果你需要白名单功能：" class="headerlink" title="如果你需要白名单功能："></a>如果你需要白名单功能：</h4><p>首先，创建一个包含你希望允许访问的域名的白名单文件。该文件会用于匹配允许的域名。</p><ol><li><p>打开终端并创建白名单文件。例如，我们将其存放在 <code>/etc/squid/whitelist</code> 路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/squid/whitelist</span><br></pre></td></tr></table></figure></li><li><p>在文件中，每行列出一个希望允许访问的域名。例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.youtube.com</span><br><span class="line">.bilibili.com</span><br><span class="line">.example.com</span><br></pre></td></tr></table></figure><p>这样就允许 <code>youtube.com</code>、<code>bilibili.com</code> 和 <code>example.com</code> 的请求通过代理，而其他未列出的域名将被拒绝。</p></li></ol><h4 id="2-编辑-squid-conf-配置文件"><a href="#2-编辑-squid-conf-配置文件" class="headerlink" title="2. 编辑 squid.conf 配置文件"></a>2. <strong>编辑 <code>squid.conf</code> 配置文件</strong></h4><p>接下来，编辑 Squid 的主配置文件 <code>squid.conf</code>，将白名单配置添加到文件中。</p><ol><li><p>打开 Squid 配置文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/squid/squid.conf</span><br></pre></td></tr></table></figure></li><li><p>在文件中，找到并添加以下规则：</p><ul><li><p><strong>允许 <code>localhost</code> 访问</strong>：</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">http_access allow localhost</span><br></pre></td></tr></table></figure><p>该规则允许本地计算机（localhost）访问代理服务器。</p></li><li><p><strong>添加白名单配置</strong>：</p><figure class="highlight plaintext"><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">acl whitelist dstdomain &quot;/etc/squid/whitelist&quot;</span><br><span class="line">http_access allow whitelist</span><br></pre></td></tr></table></figure><p>这两行配置定义了一个名为 <code>whitelist</code> 的 ACL（访问控制列表），它从 <code>/etc/squid/whitelist</code> 文件中加载允许的域名。然后，我们允许匹配这些域名的请求。</p></li><li><p><strong>拒绝所有其他访问</strong>：</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">http_access deny all</span><br></pre></td></tr></table></figure><p>该规则确保只有白名单中的域名可以访问代理，其他所有请求都将被拒绝。</p></li></ul></li><li><p><strong>配置端口和缓存</strong>：</p><p>你可以在 <code>squid.conf</code> 文件中设置 Squid 的监听端口并配置缓存目录（如有需要）。例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">http_port 3128</span><br><span class="line">coredump_dir /var/spool/squid</span><br></pre></td></tr></table></figure></li></ol>]]></content>
    
    
    <summary type="html">把懒猫微服配置为旁路由，客户端无需转发即可直接访问内外网地址。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="代理" scheme="https://blog.no-claw.com/tags/%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十二）：不登陆机器，如何使用 Docker Context 玩转微服容器？</title>
    <link href="https://blog.no-claw.com/posts/5018903b/"/>
    <id>https://blog.no-claw.com/posts/5018903b/</id>
    <published>2025-07-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>平时管理远端的 Docker 容器，大多数人第一反应是通过 SSH 登录到服务器再执行命令。</p><p>其实 Docker 本身是 C&#x2F;S 架构，只要配置好连接方式，就能在本地直接管理远程容器，甚至用 VS Code 图形化界面操作，完全不必反复登录。</p><p>下面我就用管理微服容器的例子，把实现方法和使用体验记录下来</p><h3 id="1-准备免密登录"><a href="#1-准备免密登录" class="headerlink" title="1. 准备免密登录"></a>1. 准备免密登录</h3><p>为了避免奇怪的认证问题，可以先将 SSH Key 复制到远端，实现免密登录。<br>（&#x2F;root 目录重启不会丢失 SSH Key）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id root@name.heiyu.space</span><br></pre></td></tr></table></figure><h3 id="2-最简单的方式：设置-DOCKER-HOST"><a href="#2-最简单的方式：设置-DOCKER-HOST" class="headerlink" title="2. 最简单的方式：设置 DOCKER_HOST"></a>2. 最简单的方式：设置 DOCKER_HOST</h3><p>直接在本地设置 Docker 引擎的环境变量，指向远端的 docker.sock 文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> DOCKER_HOST=ssh://root@name.heiyu.space</span><br></pre></td></tr></table></figure><p>在 Warp 中（非直接 SSH 登录），执行 <code>docker ps</code> 后，就可以看到远端的容器了。</p><p>不过仔细看，这里其实是系统组件，没有必要随便动，而且<strong>千万不要随便操作系统组件容器</strong>！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250814200633245.png" alt="image-20250814200633245"></p><h3 id="3-使用-Docker-Context-管理非标准路径的-docker-sock"><a href="#3-使用-Docker-Context-管理非标准路径的-docker-sock" class="headerlink" title="3. 使用 Docker Context 管理非标准路径的 docker.sock"></a>3. 使用 Docker Context 管理非标准路径的 docker.sock</h3><p>之前介绍过，playground 和 appstore 的 Docker 配置文件在其他目录。<br>这种情况下可以通过 <code>docker context</code> 引用非标准路径的 docker.sock：</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">docker context create my-remote-sock \</span><br><span class="line">  --docker <span class="string">&quot;host=ssh://root@name.heiyu.space:/data/playground/docker.sock&quot;</span></span><br><span class="line"></span><br><span class="line">docker context use my-remote-sock</span><br></pre></td></tr></table></figure><p>playground 的 Docker 也可以用 <strong>Dockge</strong> 管理，不过当 Dockge 的功能不够用时，就可以用这个作为备用方案。</p><h3 id="4-管理商店容器"><a href="#4-管理商店容器" class="headerlink" title="4. 管理商店容器"></a>4. 管理商店容器</h3><p>商店有时需要执行 <code>lzc-docker exec</code> 或 <code>lzc-docker restart</code>，可以先创建对应的 Context：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker context create lzc-remote-sock \</span><br><span class="line">  --docker <span class="string">&quot;host=ssh://root@name.heiyu.space:/lzcsys/run/lzc-docker/docker.sock&quot;</span></span><br></pre></td></tr></table></figure><p>然后切换 Docker 引擎：</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">docker context use lzc-remote-sock</span><br></pre></td></tr></table></figure><p>查看 Docker 信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">Kernel Version: 6.5.0-0.deb12.4-amd64</span><br><span class="line">Operating System: Debian GNU/Linux 12 (bookworm) (containerized)</span><br><span class="line">OSType: linux</span><br><span class="line">Architecture: x86_64</span><br><span class="line">CPUs: 8</span><br><span class="line">Total Memory: 31.12GiB</span><br><span class="line">Name: lzcbox-029c588e</span><br><span class="line">ID: 0726989e-2e3e-46d1-89ba-753f7dd1a600</span><br><span class="line">Docker Root Dir: /lzcsys/run/data/system/docker</span><br></pre></td></tr></table></figure><h3 id="5-本地-Warp-记录"><a href="#5-本地-Warp-记录" class="headerlink" title="5. 本地 Warp 记录"></a>5. 本地 Warp 记录</h3><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250814195406282.png" alt="image-20250814195406282"></p><p>到这里，你会发现<strong>本地和远端几乎没有区别</strong>。</p><h3 id="6-图形化管理：VS-Code-Docker-插件"><a href="#6-图形化管理：VS-Code-Docker-插件" class="headerlink" title="6. 图形化管理：VS Code Docker 插件"></a>6. 图形化管理：VS Code Docker 插件</h3><p>在 VS Code 中安装 <strong>Microsoft 官方 Docker 插件</strong>，即可在界面中查看远端的 <strong>Image、Container、Logs</strong>，并且可以直接 <code>exec</code> 进入容器。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3398ac4853bb5a0a3be0a5a38d84e1b3.png" alt="3398ac4853bb5a0a3be0a5a38d84e1b3"></p><p>在 <strong>Docker Context</strong> 面板可以直接切换上下文（相当于 <code>docker context use lzc-remote-sock</code>），然后就能在 VS Code 中操作对应容器和镜像，无需手动敲命令。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250814195227594.png" alt="image-20250814195227594"></p><h3 id="7-查看已创建的-Docker-Context"><a href="#7-查看已创建的-Docker-Context" class="headerlink" title="7. 查看已创建的 Docker Context"></a>7. 查看已创建的 Docker Context</h3><p>在命令行查看所有 Context：</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">docker context <span class="built_in">ls</span></span><br></pre></td></tr></table></figure><p>输出示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">NAME              DESCRIPTION                               DOCKER ENDPOINT</span><br><span class="line">default *         Current DOCKER_HOST based configuration   ssh://root@name.heiyu.space</span><br><span class="line">lzc-remote-sock                                             ssh://root@name.heiyu.space:/lzcsys/run/lzc-docker/docker.sock</span><br><span class="line">my-remote-sock                                              ssh://root@name.heiyu.space:/data/playground/docker.sock</span><br><span class="line">orbstack          OrbStack                                  unix:///Users/.orbstack/run/docker.sock</span><br><span class="line"></span><br><span class="line">Warning: DOCKER_HOST environment variable overrides the active context.</span><br><span class="line">To use a context, either set the global --context flag, or unset DOCKER_HOST environment variable.</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过 docker context 配合 VS Code Docker 插件，不仅能用命令行直接操作远端微服容器，还能图形化查看容器状态、镜像和日志。<br>这种方式的好处是：</p><ul><li>免 SSH 登录，管理更高效；</li><li>可切换多个 docker.sock，适合同时维护多个服务环境；</li><li>配合 VS Code，操作体验接近本地容器。</li></ul><p>对于习惯 GUI 操作的人来说，这几乎就是远程 Docker 的“丝滑”管理方式。下次维护微服时，你也可以试试这一套。</p>]]></content>
    
    
    <summary type="html">通过 Docker Context 远程管理懒猫微服容器，无需 SSH 登录机器。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十三）： WebVirtCloud 安装 Windows 使用 virtIO 的注意事项</title>
    <link href="https://blog.no-claw.com/posts/5018903b/"/>
    <id>https://blog.no-claw.com/posts/5018903b/</id>
    <published>2025-07-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 WebVirtCloud 部署 Windows 虚拟机时，如果想要获得更高的磁盘与网络性能，建议使用 <strong>virtIO 半虚拟化驱动</strong>。不过，Windows 并不像大部分 Linux 发行版那样自带 virtIO 驱动，因此安装过程中需要额外设置。本文结合社区已有教程与实测经验，整理了安装 Windows 时使用 virtIO 的注意事项，方便大家快速上手。</p><p><a href="https://lazycat.cloud/playground/guideline/454">社区原教程参考：</a></p> <span id="more"></span><h3 id="半虚拟化与全虚拟化的区别"><a href="#半虚拟化与全虚拟化的区别" class="headerlink" title="半虚拟化与全虚拟化的区别"></a>半虚拟化与全虚拟化的区别</h3><p>半虚拟化（Paravirtualization）和全虚拟化（Full Virtualization）的主要区别在于 <strong>虚拟机是否知道自己“不是物理机”</strong> 以及 <strong>是否需要为虚拟化修改驱动或操作系统</strong>。</p><table><thead><tr><th>特性</th><th>全虚拟化（Full Virtualization）</th><th>半虚拟化（Paravirtualization）</th></tr></thead><tbody><tr><td>客户机是否知道自己在虚拟环境</td><td>否，操作系统认为自己在裸机上运行</td><td>是，操作系统知道自己在虚拟机里</td></tr><tr><td>是否需要修改客户机操作系统</td><td>否（原始 OS 可直接运行）</td><td>是（需要支持 paravirt 接口的内核或驱动）</td></tr><tr><td>是否模拟完整硬件</td><td>是，完全模拟 CPU、BIOS、设备</td><td>否，使用简化接口与 hypervisor 通信</td></tr><tr><td>性能</td><td>一般略低（依赖 VT-x&#x2F;AMD-V 等硬件加速）</td><td>更高（减少陷入与上下文切换）</td></tr><tr><td>示例</td><td>VMware Workstation、QEMU + TCG、VirtualBox</td><td>Xen PV 模式、KVM virtio、Hyper-V Enlightenments</td></tr><tr><td>是否支持热迁移</td><td>支持</td><td>支持</td></tr></tbody></table><p><strong>全虚拟化</strong>：通过软件模拟硬件，客户机无需修改即可运行，但性能相对较低，尤其是 I&#x2F;O。<br><strong>半虚拟化</strong>：使用专用接口与宿主机通信，需要驱动支持，性能更高。</p><p>进一步对比如下：</p><table><thead><tr><th>情境</th><th>全虚拟化</th><th>半虚拟化（virtio-net）</th></tr></thead><tbody><tr><td>客户机看到什么</td><td>模拟 Intel e1000 网卡</td><td>简化的 virtio-net 网卡</td></tr><tr><td>通信方式</td><td>模拟 PCI 总线、MMIO、DMA</td><td>共享内存 + 通知机制（virtqueue）</td></tr><tr><td>性能</td><td>中等（高 CPU 占用）</td><td>高（低延迟、低 CPU 占用）</td></tr><tr><td>兼容性</td><td>高（任何支持 e1000 的 OS 都能用）</td><td>需要安装 virtIO 驱动</td></tr></tbody></table><p>简而言之：<br><strong>全虚拟化 &#x3D; 模拟“骗操作系统”</strong><br><strong>半虚拟化 &#x3D; 协作“告诉操作系统你在虚拟机里”</strong></p><h3 id="安装-Windows-使用-virtIO-的步骤"><a href="#安装-Windows-使用-virtIO-的步骤" class="headerlink" title="安装 Windows 使用 virtIO 的步骤"></a>安装 Windows 使用 virtIO 的步骤</h3><ol><li><p><strong>准备 ISO 镜像</strong></p><ul><li>Windows 安装 ISO</li><li>virtIO 驱动 ISO（建议版本 <code>virtio-win-0.1.266-1.iso</code>）<br>下载地址：<a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/">https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/</a></li></ul></li></ol><p>相当于安装的时候需要从 virtio 提取驱动文件来完成半虚拟化安装。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815210345831.png" alt="image-20250815210345831"></p><ol start="2"><li><p><strong>在 WebVirtCloud 设置中同时挂载两个 ISO</strong></p><ul><li>Windows ISO</li><li>virtIO ISO</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815211218330.png" alt="image-20250815211218330"><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815210909079.png" alt="image-20250815210909079"></p></li><li><p><strong>启动控制台进入安装界面</strong><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815210707501.png" alt="image-20250815210707501"></p></li><li><p><strong>加载 virtIO 驱动</strong></p><ul><li>安装向导默认找不到磁盘（因使用 virtIO 控制器）</li><li>点击“加载驱动程序” → 选择 virtIO ISO 中 <code>w10</code> 目录下的驱动</li><li>刷新后即可识别磁盘</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815211037414.png" alt="image-20250815211037414"><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815210950210.png" alt="image-20250815210950210"></p></li><li><p><strong>正常分区并开始安装</strong><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815211117877.png" alt="image-20250815211117877"></p><p>安装过程，是漫长的等待。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815211134779.png" alt="image-20250815211134779"></p></li><li><p><strong>首次启动时跳过联网</strong></p><ul><li>因为 virtIO 网卡驱动未安装，联网步骤可选择“我没有 Internet”跳过</li><li>进入系统后安装 virtIO 网卡驱动</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250815215024868.png" alt="image-20250815215024868"></p></li><li><p><strong>在系统内安装 guest tools</strong></p><ul><li>打开 virtIO ISO</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20250815215218786.png" alt="图片"></p><ul><li>双击安装 <strong>guest tools</strong>（包含磁盘、网卡等驱动）</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/640-20250815215244474.png" alt="图片"></p></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>在 WebVirtCloud 下安装 Windows 时使用 virtIO，可以显著提升磁盘和网络性能，但前提是正确加载驱动并在系统中安装 guest tools。整个流程的关键点在于：</p><ul><li>安装阶段提前挂载 virtIO ISO 并加载驱动</li><li>首次启动跳过联网，进入系统后安装 guest tools</li><li>Linux 系统通常无需额外驱动，但 Windows 必须手动安装</li></ul><p>这样，你的 Windows 虚拟机不仅能正常运行，还能充分发挥 KVM 的 I&#x2F;O 性能优势。</p>]]></content>
    
    
    <summary type="html">在懒猫微服 WebVirtCloud 中安装 Windows 虚拟机时 virtIO 驱动的注意事项。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>EC2 没有绑定 EIP，重启后 IP 会变？DDNS-GO 自动更新你的域名</title>
    <link href="https://blog.no-claw.com/posts/369cde6d/"/>
    <id>https://blog.no-claw.com/posts/369cde6d/</id>
    <published>2025-07-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 IPV4 即将枯竭的年代，云服务商的 EIP 也开始收费了。对于没有弹性公网 IP（EIP）的云服务器，我们可以通过 DDNS-GO 实现动态域名解析。本文介绍了如何使用 Docker 快速部署 DDNS-GO，并借助 DDNS 实现动态域名绑定，从而让服务器即使公网 IP 变化，也能够实时更新域名解析记录，这样只需要使用域名访问，不再需要在控制台查看。</p><hr><h2 id="安装-Docker"><a href="#安装-Docker" class="headerlink" title="安装 Docker"></a>安装 Docker</h2><p>使用官方安装脚本快速安装 Docker：</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">curl -fsSL https://get.docker.com -o get-docker.sh</span><br><span class="line"><span class="built_in">sudo</span> sh get-docker.sh</span><br></pre></td></tr></table></figure><span id="more"></span><p>设置 Docker 开机自启并立即启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> --now docker</span><br></pre></td></tr></table></figure><hr><h2 id="添加当前用户到-docker-用户组（避免每次用-sudo）"><a href="#添加当前用户到-docker-用户组（避免每次用-sudo）" class="headerlink" title="添加当前用户到 docker 用户组（避免每次用 sudo）"></a>添加当前用户到 <code>docker</code> 用户组（避免每次用 sudo）</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> usermod -aG docker <span class="variable">$USER</span></span><br></pre></td></tr></table></figure><h3 id="生效方式："><a href="#生效方式：" class="headerlink" title="生效方式："></a>生效方式：</h3><ul><li>推荐：<strong>重新登录终端会话</strong></li><li>或使用临时方式立即生效：</li></ul><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">newgrp docker</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">docker info</span><br></pre></td></tr></table></figure><p>若无权限报错，则配置已生效。</p><hr><h2 id="Docker-中部署-DDNS-GO"><a href="#Docker-中部署-DDNS-GO" class="headerlink" title="Docker 中部署 DDNS-GO"></a>Docker 中部署 DDNS-GO</h2><p>我们将使用 Docker 的 <code>host</code> 网络模式挂载主机目录，确保 DDNS 能正常检测本地 IP：</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">docker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root jeessy/ddns-go</span><br></pre></td></tr></table></figure><ul><li><p><code>/opt/ddns-go</code> 是主机目录，你可以替换为任意路径，用于持久化配置。</p></li><li><p>启动后，DDNS-GO 的配置文件为 <code>.ddns-go.yaml</code>，位于挂载目录中。</p></li></ul><h2 id="初始化配置"><a href="#初始化配置" class="headerlink" title="初始化配置"></a>初始化配置</h2><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">http://&lt;Docker主机IP&gt;:9876</span><br></pre></td></tr></table></figure><p>你会看到 DDNS-GO 的初始化页面，如图所示：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715114247261.png" alt="初始化配置页面"></p><p>DDNS-GO 是一个开源的动态域名更新工具，支持多个域名服务商，我的域名托管在 cloudflare 上，所以需要在 cloudflare 上申请一个 API-KEY 来做这个更新。</p><ul><li><strong>TTL 建议设置为“自动”</strong></li><li><strong>IP 获取方式推荐使用外网 API（如 ipip.net）</strong></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715115315906-20250715122303544.png" alt="成功绑定 DDNS 地址"></p><p>前往 Cloudflare 的 <a href="https://dash.cloudflare.com/profile/api-tokens">API Token 页面</a>，为 DDNS-GO 创建一个具备修改 DNS 权限的 Token。</p><p>建议选择 <strong>“Edit zone DNS”</strong> 模板，只赋予必要权限，并可以限制在特定域名范围内使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ceb3433ce7976c7c3199fc54402af084-20250715122245169.png" alt="Cloudflare API Token 创建"></p><p>此外，DDNS-GO 支持 webhook 通知，可选用如 Slack、Server 酱等方式实时通知 IP 变动情况。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715121816313.png" alt="image-20250715121816313"></p><hr><h2 id="验证-DDNS-的效果"><a href="#验证-DDNS-的效果" class="headerlink" title="验证 DDNS 的效果"></a>验证 DDNS 的效果</h2><p>我们尝试停止云主机后再重新开启，公网 IP 会发生变化：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715120531839.png" alt="公网 IP 变化"></p><p>重启后 DDNS-GO 会自动检测 IP 变动并更新域名解析：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715120827569.png" alt="DDNS 更新成功"></p><p>通过域名访问服务仍然保持不变，无需手动更新 IP。</p><hr><p>同时 Server 也会把这个消息推送到手机上：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250715121937366.png" alt="image-20250715121937366"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>即使云服务器没有绑定弹性公网 IP，借助 DDNS-GO 和 Docker，我们依然可以实现动态域名解析：</p><ul><li><strong>低成本</strong>：无需购买 EIP，节省开销；</li><li><strong>自动化</strong>：IP 改变后自动更新域名解析；</li><li><strong>易部署</strong>：Docker 一键运行，配置简单直观。</li></ul>]]></content>
    
    
    <summary type="html">用 DDNS-GO 解决 EC2 无 EIP 重启后 IP 变化的动态域名解析方案</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十二）：使用 DDNS-GO 给 Cloudflare 做动态域名解析</title>
    <link href="https://blog.no-claw.com/posts/6031f818/"/>
    <id>https://blog.no-claw.com/posts/6031f818/</id>
    <published>2025-07-13T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我的域名最初是在 AWS 的 Route53 上购买的，然而在使用过程中发现 Host Zone 的费用并不低，除了每月的托管费用，还包括 TTL 更新等开销。在朋友建议下，我决定将域名解析的任务从 AWS 迁移到 Cloudflare，一方面可以节省成本，另一方面配置也更为灵活。我家里的公网 IPV4 经常变动，所以我使用懒猫微服商店里的 DDNS-GO 和 Cloudflare 做动态域名解析。</p><h2 id="第一步：更改-NS-记录"><a href="#第一步：更改-NS-记录" class="headerlink" title="第一步：更改 NS 记录"></a>第一步：更改 NS 记录</h2><p>准确地说，需要在原域名注册商（我的是 AWS Route53）处修改 Name Server，将默认的 AWS NS 记录改为 Cloudflare 提供的 NS 地址。这样，域名解析权就转移到了 Cloudflare。Cloudflare 可以从懒猫微服的商店进入，也可以使用网页。而 DDNS-GO 使用商店快捷下载就非常方便。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c92dd5fd7cac4bdb400222b89c2771a3.png" alt="c92dd5fd7cac4bdb400222b89c2771a3"></p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bf852b6fc90fdb8aeca974c19ebe15fa-20250714213924253-20250714213959894.png" alt="Route53 修改 NS"><br>Route53 中的 NS 修改界面 如下， <em>替换成 Cloudflare 的 NS 后，域名正式托管在 Cloudflare</em></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250714212603214.png" alt="NS 修改示意"></p><h2 id="第二步：创建-API-Token"><a href="#第二步：创建-API-Token" class="headerlink" title="第二步：创建 API Token"></a>第二步：创建 API Token</h2><p>前往 Cloudflare 的 <a href="https://dash.cloudflare.com/profile/api-tokens">API Token 页面</a>，为 DDNS-GO 创建一个具备修改 DNS 权限的 Token。</p><p>建议选择 <strong>“Edit zone DNS”</strong> 模板，只赋予必要权限，并可以限制在特定域名范围内使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ceb3433ce7976c7c3199fc54402af084.png" alt="Cloudflare API Token 创建"></p><hr><h2 id="第三步：配置-DDNS-GO"><a href="#第三步：配置-DDNS-GO" class="headerlink" title="第三步：配置 DDNS-GO"></a>第三步：配置 DDNS-GO</h2><p>DDNS-GO 是一个开源的动态域名更新工具，支持多个域名服务商（不包括 AWS 的 Route53）。看来不仅迁移出来省钱，还省心。我们把上一步申请的 token 添加到这里，TTL 设置成自动就行。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250714211648662.png" alt="DDNS-GO 配置界面"></p><ul><li><strong>TTL 建议设置为“自动”</strong></li><li><strong>IP 获取方式推荐使用外网 API（如 ipip.net）</strong></li></ul><p>此外，DDNS-GO 支持 webhook 通知，可选用如 Slack、Server 酱等方式实时通知 IP 变动情况。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250714211710302.png" alt="Webhook 配置"></p><h2 id="第四步：查看效果"><a href="#第四步：查看效果" class="headerlink" title="第四步：查看效果"></a>第四步：查看效果</h2><p>我将 DDNS 记录绑定到家用公网 IP（IPv4），未启用 IPv6。保存配置后，前往 Cloudflare 后台查看 DNS 记录，已成功同步更新。</p><blockquote><p>⚠️ 小提示：如果你绑定的 IP 是中国大陆的，<strong>建议不要开启 Cloudflare 的代理功能（小云朵），否则可能出现连接问题</strong>。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250714213533869.png" alt="CF DNS 记录更新"></p><h2 id="Bonus：免费-TLS-证书"><a href="#Bonus：免费-TLS-证书" class="headerlink" title="Bonus：免费 TLS 证书"></a>Bonus：免费 TLS 证书</h2><p>使用 Cloudflare 托管 DNS，访问你的网站时会根据规则<strong>加上 TLS 证书</strong>，实现 HTTPS 加密，非常方便省心。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250714212823244.png" alt="自动 TLS 证书"></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过将域名解析迁移到 Cloudflare，并配合 DDNS-GO 工具进行动态更新，既省钱又省事，还顺带获得了免费证书加持。相比 AWS Route53 的托管费用和配置复杂度，Cloudflare 无疑是个人用户和轻量级应用的更优选择。</p>]]></content>
    
    
    <summary type="html">在懒猫微服上用 DDNS-GO 配合 Cloudflare 实现动态域名解析。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>快速启动 http 站点</title>
    <link href="https://blog.no-claw.com/posts/572b7187/"/>
    <id>https://blog.no-claw.com/posts/572b7187/</id>
    <published>2025-07-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>以下是 Python 内置 HTTP 服务器的几种常用启动方式：</p><ol><li>默认端口启动（8000）：</li></ol><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">python -m http.server</span><br></pre></td></tr></table></figure><span id="more"></span><ol start="2"><li>指定端口启动（示例使用 1378 端口）：</li></ol><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">python -m http.server 1378</span><br></pre></td></tr></table></figure><ol start="3"><li>支持 IPv6 的启动方式：</li></ol><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">python -m http.server 1378 --<span class="built_in">bind</span> ::</span><br></pre></td></tr></table></figure><p>这些命令会在当前目录启动一个简单的 HTTP 文件服务器，方便快速共享文件或测试网页。</p>]]></content>
    
    
    <summary type="html">Python 内置 HTTP 服务器的几种启动方式，一行命令快速搭建本地静态站点。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十一）：使用 Memos 替代 Flomo</title>
    <link href="https://blog.no-claw.com/posts/6f97c31a/"/>
    <id>https://blog.no-claw.com/posts/6f97c31a/</id>
    <published>2025-07-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>曾经用过 Flomo，一度觉得体验不错，但后来因不想续费会员而中止使用。最近在懒猫微服的商店中发现了 <a href="https://github.com/usememos/memos">Memos</a>，完全开源、支持自托管，是个很不错的替代方案。</p><hr><h2 id="什么是-Memos？"><a href="#什么是-Memos？" class="headerlink" title="什么是 Memos？"></a>什么是 Memos？</h2><p><a href="https://github.com/usememos/memos">Memos</a> 是一款开源、极简、现代化的笔记系统，主打「快速记录、随时检索」。适合捕捉灵感、日常备忘、碎片想法、读书笔记等多种用途。</p><p>它具有以下特点：</p><span id="more"></span><ul><li>🧩 极简 Markdown 编辑器，写作无干扰</li><li>🔍 支持标签与多条件过滤，查找便捷</li><li>📱 原生适配移动端界面，自动响应式布局</li><li>🌐 内置开放 API，可接入自动化工具或客户端</li><li>🗂 支持归档、置顶、图像插入、链接跳转等实用功能</li><li>🔐 数据完全自托管，掌控在自己手中</li></ul><p>目前 GitHub star 数已超 6k，开发活跃，文档完善，社区生态也在不断壮大。</p><hr><h2 id="页面结构与使用方式"><a href="#页面结构与使用方式" class="headerlink" title="页面结构与使用方式"></a>页面结构与使用方式</h2><p>Memos 主页面分为两栏：</p><h3 id="✅-左侧侧边栏："><a href="#✅-左侧侧边栏：" class="headerlink" title="✅ 左侧侧边栏："></a>✅ 左侧侧边栏：</h3><ul><li><strong>创建 Memo</strong>：点击加号即可撰写新内容</li><li><strong>标签筛选</strong>：点击任意标签快速过滤</li><li><strong>快捷菜单</strong>：查看所有 Memo、归档 Memo、置顶 Memo、搜索功能等</li></ul><h3 id="✅-右侧内容区："><a href="#✅-右侧内容区：" class="headerlink" title="✅ 右侧内容区："></a>✅ 右侧内容区：</h3><ul><li><p>展示所有 Memo 内容，以时间倒序排列</p></li><li><p>每条 Memo 支持：</p><ul><li>编辑、置顶、归档</li><li>标签添加（支持 <code>#标签名</code> 快捷方式）</li><li>Markdown 格式（支持标题、代码块、列表、引用等）</li><li>拖拽上传图片或截图粘贴</li><li>自动识别链接并可点击跳转</li></ul></li></ul><p>📷 示例页面如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711183947167.png" alt="image-20250711183947167"></p><hr><h2 id="开启-API-支持"><a href="#开启-API-支持" class="headerlink" title="开启 API 支持"></a>开启 API 支持</h2><p>在设置中可以启用 API 功能，系统会生成一个唯一的 <code>API KEY</code>，用于连接第三方客户端或自动化工具（如 Moe Memos）。</p><p>📷 API 开关和密钥生成页面如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711184031658.png" alt="image-20250711184031658"></p><hr><h2 id="Moe-Memos-客户端"><a href="#Moe-Memos-客户端" class="headerlink" title="Moe Memos 客户端"></a>Moe Memos 客户端</h2><p>App Store 上的 Moe Memos 是一款第三方移动端客户端，原生适配 Memos 的 API 接口，界面极简，使用流畅。</p><p>📷 图标预览：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711184107888.png" alt="image-20250711184107888"></p><hr><h2 id="如何登录-Moe-Memos"><a href="#如何登录-Moe-Memos" class="headerlink" title="如何登录 Moe Memos"></a>如何登录 Moe Memos</h2><ol><li>安装 Moe Memos 后，打开 App</li><li>输入你的自托管域名，如 <code>https://memos.name.heiyu.space</code></li><li>填写 API KEY 即可登录（需在系统中开启单租户模式）</li></ol><p>📷 登录后界面展示：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711183848763.png" alt="image-20250711183848763"></p><hr><h2 id="简单对比-Flomo"><a href="#简单对比-Flomo" class="headerlink" title="简单对比 Flomo"></a>简单对比 Flomo</h2><table><thead><tr><th>功能</th><th>Flomo</th><th>Memos（自托管）</th></tr></thead><tbody><tr><td>微信同步</td><td>✅（原生）</td><td>❌（可接 webhook 实现）</td></tr><tr><td>多端支持</td><td>✅</td><td>✅ Moe Memos</td></tr><tr><td>数据隐私</td><td>❌ 云端托管</td><td>✅ 完全自持</td></tr><tr><td>开放 API</td><td>❌ 限制较多</td><td>✅ 支持 API KEY</td></tr><tr><td>成本</td><td>收费会员制</td><td>免费开源（除托管成本）</td></tr><tr><td>Markdown</td><td>❌</td><td>✅ 原生支持</td></tr><tr><td>图片粘贴上传</td><td>❌</td><td>✅</td></tr></tbody></table><p>虽然 Memos 不支持微信原生同步，但凭借开放架构和高度可定制性，能实现更强的私有笔记体验。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Memos 是一款功能恰到好处、设计极简、部署门槛低的笔记系统，非常适合替代 Flomo 这类灵感记录工具。配合懒猫微服提供的部署方案，可以做到：</p><ul><li>一键上线，无需服务器运维知识</li><li>API 集成快捷，客户端体验佳</li><li>数据可备份、可迁移、可掌控</li></ul><p>适合用来做「知识碎片记录」、「阅读随想」、「灵感管理」、「工程笔记」等场景。欢迎大家一起探索更多玩法！</p>]]></content>
    
    
    <summary type="html">在懒猫微服上部署 Memos，自托管的轻量笔记替代 Flomo。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>MacOS 环境下运行 EasySearch 报错无法信任 Java 包的解决方案</title>
    <link href="https://blog.no-claw.com/posts/b77119f9/"/>
    <id>https://blog.no-claw.com/posts/b77119f9/</id>
    <published>2025-07-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h4 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h4><p>近期因 MacBook 系统降级重装，需重新部署 EasySearch 环境。由于系统未预装 Java，选择下载捆绑 JDK 的 EasySearch 版本，但在安装过程中遇到以下问题：</p><ol><li>系统安全机制拦截 Java 运行</li><li>密码认证异常（持续返回 401 错误）<span id="more"></span></li></ol><h4 id="问题现象"><a href="#问题现象" class="headerlink" title="问题现象"></a>问题现象</h4><ol><li><p><strong>安全拦截</strong><br>MacOS Gatekeeper 阻止运行捆绑的 JDK，即使在「系统偏好设置-安全性与隐私」中手动放行后，仍出现权限不足提示（见图 1）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/46af71cf990c6b337b0368dff20c8e83.png" alt="安全拦截提示"></p></li><li><p><strong>认证失效</strong><br>配置文件中的密码校验异常，任何登录尝试均返回 401 状态码。</p></li></ol><h4 id="解决步骤"><a href="#解决步骤" class="headerlink" title="解决步骤"></a>解决步骤</h4><ol><li><p><strong>全局权限设置</strong><br>首先通过终端命令关闭系统安全限制：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> spctl --master-disable</span><br></pre></td></tr></table></figure><p>但发现此操作仍无法解决 JDK 运行问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/16d776ecf82e11ffda565666c96494aa-20250712103722754.png" alt="权限设置无效提示"></p></li><li><p><strong>最终解决方案</strong><br>采用处理「App 损坏」报错的方法：</p><ul><li><p>重新解压安装包</p></li><li><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">xattr -cr jdk/bin/java</span><br></pre></td></tr></table></figure></li><li><p>重新初始化 EasySearch</p></li></ul></li></ol><h4 id="验证结果"><a href="#验证结果" class="headerlink" title="验证结果"></a>验证结果</h4><p>成功运行 EasySearch 并完成系统初始化（见图 3）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250712103428122.png" alt="运行成功截图"></p><h4 id="经验总结"><a href="#经验总结" class="headerlink" title="经验总结"></a>经验总结</h4><p>MacOS 对未公证应用的限制日趋严格，建议：</p><ol><li>优先使用公证版本软件</li><li>遇到权限问题时，<code>xattr -cr</code>命令可有效清除可能导致拦截的扩展属性</li><li>401 错误可能与系统权限深度关联，需综合处理运行环境和配置文件</li></ol>]]></content>
    
    
    <summary type="html">解决 macOS 上运行 Easysearch 时 Java 包不受信任的报错问题</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>用Filebeat OSS 7.10.2将收集日志到Easysearch</title>
    <link href="https://blog.no-claw.com/posts/a0786458/"/>
    <id>https://blog.no-claw.com/posts/a0786458/</id>
    <published>2025-07-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Filebeat OSS (Open Source Software) 7.10.2 版本是 Elastic 公司提供的开源版本。移除了一些<strong>Elasticsearch</strong>商业化的功能插件（但是没移出去干净）</p><p><a href="https://www.elastic.co/cn/downloads/past-releases/filebeat-oss-7-10-2%E3%80%82">https://www.elastic.co/cn/downloads/past-releases/filebeat-oss-7-10-2。</a></p><p>这主要还是 AWS 和 Elastic 公司之间的矛盾，AWS 托管的 ES 会和 Elastic 抢客户。所以 Elastic 搞了一个不能商业化的协议，影响了这个生态圈，也给开发者造成了很多不方便。</p><span id="more"></span><p>还记得这个非常戏剧性的 Issue：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/90d8e0f338c94eccb9d48b6f104730bb.png" alt="在这里插入图片描述"></p><p><a href="https://github.com/elastic/beats/issues/8086">https://github.com/elastic/beats/issues/8086</a></p><p>下面将介绍如何配置该版本 Filebeat 将日志发送到 Easysearch。</p><p>以下是完整的<code>filebeat.yml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">filebeat.inputs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">log</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">paths:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/log/messages</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/log/httpd/*.log</span></span><br><span class="line"></span><br><span class="line"><span class="attr">output.elasticsearch:</span></span><br><span class="line">  <span class="attr">hosts:</span> [<span class="string">&quot;yourEasysearchEndpoint:443&quot;</span>]</span><br><span class="line">  <span class="attr">protocol:</span> <span class="string">&quot;https&quot;</span></span><br><span class="line">  <span class="attr">username:</span> <span class="string">&quot;username&quot;</span></span><br><span class="line">  <span class="attr">password:</span> <span class="string">&quot;password&quot;</span></span><br><span class="line">  <span class="attr">ssl.verification_mode:</span> <span class="string">none</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 以下配置用于处理xpack相关兼容性问题</span></span><br><span class="line"><span class="attr">setup.template.name:</span> <span class="string">&quot;filebeat&quot;</span></span><br><span class="line"><span class="attr">setup.template.pattern:</span> <span class="string">&quot;filebeat-*&quot;</span></span><br><span class="line"><span class="attr">setup.template.enabled:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">setup.ilm.enabled:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>setup.template 和 setup.ilm 相关的操作就是和没移除干净的 xpack 有关系，所以得再配置文件加上这些东西。</p><p>同时也得在 Easysearch 里面开启兼容 ES 的 API，不然会遇到这个报错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250712125907654-20250712130307504.png" alt="image-20250712125907654"></p><p>在我的 MBP 上是这样：（config&#x2F;easysearch.yml）</p><figure class="highlight yml"><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="attr">discovery.type:</span> <span class="string">single-node</span></span><br><span class="line"><span class="attr">network.host:</span> <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">elasticsearch.api_compatibility:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><ol><li><strong>启动并测试 Filebeat</strong>：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start filebeat</span><br><span class="line"><span class="built_in">sudo</span> filebeat <span class="built_in">test</span> output</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>检查服务状态</strong>：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl status filebeat</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>生成测试日志</strong>：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;test log <span class="subst">$(date)</span>&quot;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> -a /var/log/messages</span><br></pre></td></tr></table></figure><ol start="4"><li><strong>在 Easysearch 中查询日志</strong>：<br>使用 Dev Tools 执行以下查询：</li></ol><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></pre></td><td class="code"><pre><span class="line">GET filebeat-*/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;match_all&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><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="attr">&quot;sort&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;@timestamp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;order&quot;</span><span class="punctuation">:</span> <span class="string">&quot;desc&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bd08bfc218c45c962378117b76933e61.png"></p><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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;took&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timed_out&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="attr">&quot;_shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;successful&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;skipped&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;failed&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;relation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eq&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;max_score&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hits&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;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;filebeat-7.10.2-2025.04.18&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;XXXXXXX&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_score&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;@timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-04-29T09:04:09.566Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;log&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;file&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;/var/log/messages&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;offset&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">          <span class="punctuation">&#125;</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;test log Fri Apr 29 09:04:00 UTC 2025&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;input&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;log&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;ecs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.6.0&quot;</span></span><br><span class="line">          <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>曾经在 ubuntu 上遇到过只要启动 filebeat 就报错一堆内存的信息：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7a95ae40a701ef3df2d34cd0a560c7c5.png" alt="7a95ae40a701ef3df2d34cd0a560c7c5"></p><p>这个文章给了一个解决办法：</p><p><a href="https://infinilabs.cn/blog/2025/ubuntu_run_filebeat/%E9%80%9A%E8%BF%87%E4%BB%A5%E4%B8%8A%E9%85%8D%E7%BD%AE%E5%92%8C%E6%AD%A5%E9%AA%A4%EF%BC%8C%E6%82%A8%E5%BA%94%E8%AF%A5%E8%83%BD%E5%A4%9F%E6%88%90%E5%8A%9F%E4%BD%BF%E7%94%A8Filebeat">https://infinilabs.cn/blog/2025/ubuntu_run_filebeat/通过以上配置和步骤，您应该能够成功使用Filebeat</a> OSS 7.10.2 版本将日志收集到 Easysearch 中。</p>]]></content>
    
    
    <summary type="html">使用 Filebeat OSS 7.10.2 采集日志并输出到 Easysearch 的配置教程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二十）：如何使用 Time Machine 将 Mac 备份到懒猫微服</title>
    <link href="https://blog.no-claw.com/posts/c5298d72/"/>
    <id>https://blog.no-claw.com/posts/c5298d72/</id>
    <published>2025-07-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>看了 Apple 的文档，时间机器备份到 SMB 或者外接设备（APFS）。正好懒猫微服带了 SMB 的共享。然后我们可以把时间机器备份到网盘里。虽然默认连接的是懒猫网盘根目录，但是我们也可以映射一个子文件夹 TimeMachine。如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711074625650.png" alt="image-20250711074625650"></p><span id="more"></span><h4 id="一、-在-Mac-上连接到-SMB-共享"><a href="#一、-在-Mac-上连接到-SMB-共享" class="headerlink" title="一、 在 Mac 上连接到 SMB 共享"></a><strong>一、 在 Mac 上连接到 SMB 共享</strong></h4><ol><li>在 Finder 中，点击菜单栏的 <strong>“前往” (Go)</strong> &gt; **“连接服务器…” (Connect to Server…)**。</li><li>在弹出的窗口中，输入 SMB 共享的地址，格式通常是 <code>smb://[懒猫微服的IP地址或主机名]/[共享文件夹名称]</code>。<ul><li>例如：<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">smb://192.168.1.100/用户名/timemachine</span><br></pre></td></tr></table></figure></li><li>（域名也可以）</li></ul></li><li>点击“连接”。系统会提示你输入用户名和密码。（懒猫微服的用户密码）<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710205349694.png" alt="image-20250710205349694"><br>成功连接后，你会在 Finder 中看到这个网络共享。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710205328553.png" alt="image-20250710205328553"></li></ol><h4 id="二、-设置-Time-Machine-进行备份"><a href="#二、-设置-Time-Machine-进行备份" class="headerlink" title="二、 设置 Time Machine 进行备份"></a><strong>二、 设置 Time Machine 进行备份</strong></h4><ol><li><p>打开 **“系统设置” (System Settings)**（或旧版 macOS 中的“系统偏好设置”）。</p></li><li><p>点击 <strong>“通用” (General)</strong> &gt; **“时间机器” (Time Machine)**。</p></li><li><p>点击 **“添加备份磁盘…” (Add Backup Disk…)**。</p></li><li><p>在弹出的列表中，你应该能看到你刚刚连接的 SMB 共享文件夹。</p></li><li><p>如果再次提示输入凭据，请再次输入你在懒猫微服上设置的用户名和密码。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9fb154461fa6adfe4d02abc8540d441d.png" alt="9fb154461fa6adfe4d02abc8540d441d"></p></li></ol><p>Time Machine 会在共享中创建一个特殊的 <code>.sparsebundle</code> 文件，然后开始首次备份。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2347dfcbdd318a7afe54f46467ea48b3.png" alt="yo"></p><h4 id="三、-解决-Time-Machine-备份变慢的问题"><a href="#三、-解决-Time-Machine-备份变慢的问题" class="headerlink" title="三、 解决 Time Machine 备份变慢的问题"></a><strong>三、 解决 Time Machine 备份变慢的问题</strong></h4><p>在某些情况下，macOS 的默认设置或某些第三方应用程序可能会导致 Time Machine 备份变慢。</p><p><strong>1. 暂时禁用磁盘节流（Disk Throttling）</strong></p><p>macOS 默认会对后台进程（包括 Time Machine）进行磁盘 I&#x2F;O 节流，以确保系统响应速度。在首次备份或需要快速完成备份时，可以暂时禁用此节流。</p><ul><li><strong>操作方法</strong>： 打开“终端”应用程序（位于“应用程序”&gt;“实用工具”），输入以下命令并按回车键：<br><code>bash sudo sysctl debug.lowpri_throttle_enabled=0 </code><br>输入管理员密码后，该设置会立即生效。请注意，这会使 Time Machine 占用更多系统资源，可能影响 Mac 在备份期间的流畅性。备份完成后，建议通过以下命令重新启用节流：<br><code>bash sudo sysctl debug.lowpri_throttle_enabled=1 </code><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e32943d816fa309bbdc6c67f67ae80d7.png" alt="e32943d816fa309bbdc6c67f67ae80d7"><br><strong>2. 检查和禁用干扰备份的应用程序</strong></li></ul><p>某些应用程序，特别是防病毒软件、系统清理工具或文件同步工具，可能会频繁访问磁盘，从而干扰 Time Machine 的正常运行。</p><ul><li><strong>操作方法</strong>： 尝试在 Time Machine 备份期间暂时禁用这些应用程序，或者检查它们的设置，看是否可以将 Time Machine 备份盘从其扫描范围中排除。</li></ul><p><strong>3. 确保 Mac 保持唤醒状态</strong></p><p>对于大型备份，如果 Mac 进入睡眠状态，可能会中断或减慢备份进程。</p><ul><li><strong>操作方法</strong>： 在备份期间，你可以前往“系统设置”&gt;“显示器”（或“节能”）调整显示器关闭时间和电脑睡眠设置，或使用 <code>caffeinate</code> 命令让 Mac 保持唤醒：<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">caffeinate -s -m -i -t 36000 &amp;</span><br></pre></td></tr></table></figure>此命令将使 Mac 在 10 小时内（36000 秒）保持唤醒，<code>&amp;</code> 符号表示在后台运行。</li></ul><h4 id="四、-查看备份情况"><a href="#四、-查看备份情况" class="headerlink" title="四、 查看备份情况"></a><strong>四、 查看备份情况</strong></h4><p>可以按到我目前磁盘使用了 90 个 G，在时间机器中看到占用 50G。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710205108688.png" alt="image-20250710205108688"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711073425325.png" alt="image-20250711073425325"></p><p>在懒猫网盘中也可以看到这个数据。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250711072633958.png" alt="image-20250711072633958"></p><p>通过以上步骤，你就可以成功地将你的 Mac 通过 Time Machine 备份到懒猫微服提供的 SMB 共享了。</p>]]></content>
    
    
    <summary type="html">Mac 通过 Time Machine 备份到懒猫微服，实现本地 NAS 自动备份。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十九）：我最喜欢的 RSS 阅读器-FreshRSS</title>
    <link href="https://blog.no-claw.com/posts/9fd148d9/"/>
    <id>https://blog.no-claw.com/posts/9fd148d9/</id>
    <published>2025-07-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>自建的博客都可以接入 RSS 订阅，我之很喜欢的一款 FreshRSS 在懒猫微服的商店上架了，可以联动一下了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710111228317.png" alt="image-20250710111228317"></p><p>我的博客 RSS 订阅是：<a href="https://cloudsmithy.github.io/atom.xml">https://cloudsmithy.github.io/atom.xml</a></p><p>然后点击左上角的订阅管理，先新建一个分类，然后是输入 RSS 订阅地址即可。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710111327135.png" alt="image-20250710111327135"></p><p>我也添加了懒猫微服王总的博客订阅：<a href="https://manateelazycat.github.io/feed.xml">https://manateelazycat.github.io/feed.xml</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/fdb69d74740805003e9eba315c23c30b.png" alt="fdb69d74740805003e9eba315c23c30b"></p><p>这个是我博客的订阅效果，这样在 PC 和移动端都可以访问了：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710110130424.png" alt="image-20250710110130424"></p><p>那如果想让用其他的软件订阅这个 FreshRSS 呢，在右上角点击设置，然后选择认证，点击允许 API 访问。然后就可以通过抓数据的方式进行访问。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710110209454.png" alt="image-20250710110209454"></p><p>然后回到账户管理，这个时候下面就出来了 API 管理这个选项，设置 token 然后就可以使用了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710110308889.png" alt="image-20250710110308889"></p><p>然后我们访问 API 端点：<a href="https://freshrss/">https://freshrss</a>.&lt;机器名字&gt;.heiyu.space&#x2F;api&#x2F;</p><p>这里可以看到 Google Reader compatible API 和 Fever compatible API。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">FreshRSS API endpoints</span><br><span class="line">Google Reader compatible API</span><br><span class="line">Your API address:</span><br><span class="line">https://freshrss.&lt;机器名字&gt;.heiyu.space/api/greader.php</span><br><span class="line">Google Reader API configuration test:</span><br><span class="line">✔️ PASS</span><br><span class="line">Fever compatible API</span><br><span class="line">Your API address:</span><br><span class="line">https://freshrss.micro.&lt;机器名字&gt;.space/api/fever.php</span><br><span class="line">Fever API configuration test:</span><br><span class="line">✔️ PASS</span><br></pre></td></tr></table></figure><p>那接下来使用 fluent-reader 订阅微服里的 FreshRSS。选择 Fever API，输入上边 RSS API 返回的 URL。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/22a10f7d54a372eee2250cd11700ed11.png" alt="22a10f7d54a372eee2250cd11700ed11"></p><p>用户名和密码是登录 FreshRSS 的用户密码，然后端点是 Fever compatible API。（这里没有用到 token）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/776b56ff7038d900f9d7d8c7c08f0b27.png" alt="776b56ff7038d900f9d7d8c7c08f0b27"></p><p>这个就是订阅的效果了。（FreshRSS 夹带私货自动订阅 release）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250710113632046.png" alt="image-20250710113632046"></p><p>在 APP 里还挺好看的。（除了 fluent-reader 不支持检索）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2bb3f4399b86b3d27f86988571d8018b.png" alt="2bb3f4399b86b3d27f86988571d8018b"></p><p>用懒猫微服当作一个 RSS 存储的后端，同时也提供了一个 FreshRSS 的部署版本，很多记忆再也不会丢了。</p>]]></content>
    
    
    <summary type="html">在懒猫微服上部署 FreshRSS，自建 RSS 阅读器订阅博客和资讯。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>给传统 NAS 玩家介绍一下懒猫微服</title>
    <link href="https://blog.no-claw.com/posts/47885d90/"/>
    <id>https://blog.no-claw.com/posts/47885d90/</id>
    <published>2025-07-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>玩过不少 NAS，从最早的黑群晖，威联通开始，后面陆陆续续接触了飞牛，绿联，极空间，UNRAID，EXSI，Proxmox VE，也算有点话语权。</p><p>首先最直观的区别是软件客户端，像群晖，威联通的电脑版客户端是用来发现机器的 IP 地址的，然后后续所有的操作都在浏览器完成，比如新建用户，登录，审计，访问数据，查看监控。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704080937359.png" alt="image-20250704080937359"></p><p>而懒猫微服的所有操作几乎都在客户端完成，网页端更像是一个应用的 Dashboard。然后访问的时候使用域名，配合厂家的穿透服务，无论你是互联网访问还是局域网访问机器，用这一个地址就够了，不再需要在路由器上做端口转发，也基本可以告别自己搭建内网穿透的痛苦了。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704070707399.png" alt="image-20250704070707399"></p><p>还有一个非常主要的是社群，懒猫微服既有官方的大群交流技术，也有 VIP 的小群私人定制。大群里有各式各样的玩家，除了懒猫微服之外大家还会交流各种 Github 的开源项目，Linux 技巧以及电脑外设心得， 然后每天大家都贡献攻略。因为公司 base 在武汉嘛，当然也会有接地气的武汉风情。 VIP 小群会针对个性化的需要做一对一的指导，比如曾经帮我排查了家庭宽带 DNS 污染，UPS 信号干扰，甚至 OpenID Connect (OIDC) 的使用和接入等问题，对于开源软件部署在懒猫上有问题也是尽全力 额度支持，届时可以拉一个小会，然后共享屏幕给他们查看。可以放心的 show linux 命令和飙专业术语啥的。 很多的问题可以在半小时到一个小时就能解决，有时候甚至会更短。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704070841376.png" alt="image-20250704070841376"></p><p>不只是自己写的软件部署上去访问有延迟他们会帮忙排查，而且一些商店里社区用户贡献的开源软件的问题，他们也会帮忙查看并且给出一些解答方式。 毕竟开源软件的 issue 真的不少，他们不会叫你去开 issue 然后漫长的等待，他们会去帮忙追。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704083116697.png" alt="image-20250704083116697"></p><p>而传统 NAS 的社群主要靠的是爱好者自己发起和维护的，有问题在群里问可能最后没有讨论出结果就不了了之了。甚至很多时候都会纠结到底是选 Proxmox VE 还是 EXSI。对于攻略部分，大多是散落在资深用户的个人博客上，比如很多威联通的文章都会发在什么值得买上面。而对于提问能不能得到答案，完全取决于作者用爱发电的程度了。如果商业 NAS 提案例，那么响应时间可能要很久，如果你提简单的案例，那么可能立马有人给你打电话，如果是比较难的问题，可能最后就不了了之了。只能说在服务客户这方面亚马逊是在是开了一个很不好的头，大概是这样：</p><blockquote><p>因为 AWS 负责运行、管理和控制从主机操作系统和虚拟层到服务运营所在设施的物理安全性的组件。客户负责管理来宾操作系统（包括更新和安全补丁）、其他相关应用程序软件以及 AWS 提供的安全组防火墙的配置。客户应该仔细考虑自己选择的服务，因为他们的责任取决于所使用的服务、，这些服务与其 IT 环境的集成以及适用的法律法规。责任共担还为客户提供了部署需要的灵活性和控制力。</p></blockquote><p>所以有时候遇到两个产品交叉的问题，我们一般是很难得到方案，很有可能两方都说这涉及三方产品你需要找对方，我们不了解你这个三方产品，没办法给你方案。而对于三方玩家几乎是没有办法同时把两个产品的售后叫到一起的。无论你是开案例还是拉一个会议，这难度很大很大。</p><p>然后是系统层面的对比，懒猫微服是 基于 debian 优化一套系统，后来又开了 root 权限，所以基本 Linux 能做的事他都能做，更是出厂预置了 dockage，dozzle 这样方面调试 docker 的软件，基本属于开箱即用。甚至我们还能使用 Docker 打包自己的软件然后上架给其他玩家使用，甚至可以对其他人说；</p><blockquote><p>我的懒猫微服教程是中文圈里非常优秀的实战指南。</p><p>我的开发&#x2F;移植软件也有非常多的懒猫用户在使用。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6c12c920-1fd4-4300-b9cf-7918026d7c8f.png" alt="6c12c920-1fd4-4300-b9cf-7918026d7c8f"></p><p>而开源的 NAS 基本上大家只玩虚拟机和按照教程配置 Docker，几乎是没有包管理工具的，所以安装的软件很受限。 如果真的想移植应用，那个开发者文档也是不太好看。起码对于普通玩家是真的劝退了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704081028625.png" alt="image-20250704081028625"></p><p>最后一点也就是很重要的一点，关于 APP 访问这块。在从互联网访问这边，传统 NAS 有一个很大的痛点。一个是移动端软件适配不好，还有就是服务多了端口都得自己记住。再加上路由器转发的端口，真的很难记住了。而懒猫微服在移动端和 PC 上的页面几乎是差不多的，所有操作几乎可以在 APP 内部完成。应用商店上架的应用大部分都是 https 的协议和 443 的端口。不用自己做加密同时以及免去了记忆软件信息烦恼，毕竟即使是专业玩家，也不想天天做服务器的运维工作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704081156422.png" alt="image-20250704081156422"></p><p>普通用户图一个方便。专业用户图一个折腾，然后缺少的就是一个稳定的穿透和传输，精力有限不想自己维护一些底层的东西。买个懒猫微服差不多全能解决了。</p><p>我一直有一个愿望，写一本 NAS 的书，让技术融入到爱好者的生活，技术不是枯燥的理论，当我们遇到问题的时候，才能想清楚他们为什么这么设计。技术人永不为奴。</p>]]></content>
    
    
    <summary type="html">面向传统 NAS 用户，介绍懒猫微服的特色与优势</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（四）：懒猫微服如何使用 OpenID Connect （OIDC）？（上）</title>
    <link href="https://blog.no-claw.com/posts/dcc47393/"/>
    <id>https://blog.no-claw.com/posts/dcc47393/</id>
    <published>2025-07-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>OpenID Connect（OIDC）是一个基于 <strong>OAuth 2.0</strong> 的身份认证协议，允许用户使用一个账号（如 Google、微信、Microsoft 账号）登录多个不同的网站或应用，而无需重复注册。我们经常把他和<strong>OAuth 2.0</strong> 混为一谈。</p><p>它主要用于 <strong>身份认证（Authentication）</strong>，而 OAuth 2.0 主要用于 <strong>授权（Authorization）</strong>。简单来说：</p><ul><li><strong>OAuth 2.0</strong> → 让应用能访问你的数据（如获取微信头像），还要自己做用户管理。</li><li><strong>OIDC</strong> → 让应用能确认“你是谁”（如用微信账号登录）</li></ul><p>下面以我的懒猫 ENV 查看器为例，来讲解这个登录流程。</p><p>当你在应用处点击登录就会重定向到登录中心，我们通常管这个叫做身份提供商（IDP），如果是其他的软件有可能是 <strong>“使用 Google 登录”</strong> 或 <strong>“微信登录”</strong> 。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702210408529.png" alt="image-20250702210408529"></p><p>跳转到认证中心，一般都会提示你是否确认登录，某某应用将要获取登录的权限，查看你的信息。在懒猫微服里这直接点击 Grant Access 即可。在其他的 IDP 中，会让你输入账号密码登录，并同意授权该网站访问你的基本信息（如邮箱、昵称）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702210333539.png" alt="image-20250702210333539"></p><p>当 IDP 验证完的身份后，返回一个 <strong>JWT（JSON Web Token）</strong>，其中包含你的身份信息。当网站验证 JWT 后，确认你的身份，并让你登录成功。我们也可以在 jwt.io 和 jwt.ms 这个网站去做解码。</p><p>我解码了其中一个 token，我们可以看到里面的信息，可以看到加密算法，颁发机构，过期时间，用户信息什么的，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702212449226.png" alt="image-20250702212449226"></p><p>这个是一般登录的流程，比如首次用户名和密码登录成功之后会返回一个 JWT，然后后续把这个 JWT 当做 bear token 来请求后面的资源。我们的 OIDC 和这个原理类似，只不过稍微复杂一些。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7aed62612920d1042f175cb87c1f049e.png" alt="7aed62612920d1042f175cb87c1f049e"></p><p>在 <code>OIDC</code> 协议中，会遇到三种 Token: <code>id_token</code>, <code>access_token</code> 和 <code>refresh_token</code>。</p><ol><li><p>Access Token 用于基于 Token 的认证模式，允许应用访问一个资源 API。用户认证授权成功后，Authing 会签发 Access Token 给应用。应用后面就带着这个** Access Token** 访问资源 API。</p></li><li><p>ID Token 相当于用户的身份凭证，开发者的前端访问后端接口时可以携带 <strong>ID Token</strong>，<strong>开发者服务器</strong>可以校验用户的 <strong>ID Token</strong> 以确定用户身份，<a href="https://docs.authing.cn/v2/guides/faqs/how-to-validate-user-token.html">验证</a>通过后返回相关资源。</p></li></ol><p>AccessToken 和 IdToken 都是 JWT，<strong>有效时间</strong>通常较短。通常用户在获取资源的时候需要携带 AccessToken，当 AccessToken 过期后，用户需要获取一个新的 AccessToken。</p><ol start="3"><li>Refresh Token 用于获取新的 AccessToken。这样可以缩短 AccessToken 的过期时间保证安全，同时又不会因为频繁过期重新要求用户登录。用户在初次认证时，Refresh Token 会和 AccessToken、IdToken 一起返回。应用携带 Refresh Token 向 Token 端点发起请求时，这个时候会续签 AccessToken 和 IdToken 与 ID token。</li></ol><p>所以我们一般说的 JWT 就是 Access Token 的部分用于授权。而<strong>ID Token</strong> 用户标注用户信息，Refresh Token 用来续签 Access Token 。</p><p>在懒猫微服上使用 OIDC 有一个好处就是，不用在 IDP 上填写申请信息，在程序运行过程中可以直接注入相应的环境变量，这样我们直接用就可以了。相当于传统 IDP 需要填写应用名称，做分组控制而言，这个自动注入的 OIDC 开箱即用很方便。</p><p>一般是有这几个信息：</p><ol><li>CLIENT_ID：从我的 app 来看，这个就是包名</li><li>CLIENT_SECRET： 这个是随机的</li><li>ISSUER_URI：https:&#x2F;&#x2F;微服域名&#x2F;sys&#x2F;oauth</li><li>TOKEN_URI：https:&#x2F;&#x2F;微服域名&#x2F;sys&#x2F;oauth&#x2F;token</li><li>USERINFO_URI：https:&#x2F;&#x2F;微服域名&#x2F;sys&#x2F;oauth&#x2F;userinfo</li></ol><p>先说 ISSUER_URI，这个是 OIDC 的入口，其中.well-known&#x2F;openid-configuration 里可以拿到各种 URL，算是 OIDC 的入口，即使环境变量中没给信息我们也可以在这里查看。比如用来校验 JWT 的 jwks_uri。</p><p>GET https:&#x2F;&#x2F;&lt;微服域名&gt;&#x2F;sys&#x2F;oauth&#x2F;.well-known&#x2F;openid-configuration 结果如下：</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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;issuer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;authorization_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/auth&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/token&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;jwks_uri&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/keys&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;userinfo_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/userinfo&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;device_authorization_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/device/code&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;introspection_endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://&lt;name&gt;.heiyu.space/sys/oauth/token/introspect&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;grant_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;authorization_code&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;refresh_token&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;urn:ietf:params:oauth:grant-type:device_code&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;urn:ietf:params:oauth:grant-type:token-exchange&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;response_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;code&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;subject_types_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;public&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id_token_signing_alg_values_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;RS256&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;code_challenge_methods_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;S256&quot;</span><span class="punctuation">,</span> <span class="string">&quot;plain&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scopes_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;openid&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;email&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;groups&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;profile&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;offline_access&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token_endpoint_auth_methods_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;client_secret_basic&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;client_secret_post&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;claims_supported&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;iss&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;sub&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;aud&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;iat&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;exp&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;email&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;email_verified&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;locale&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;name&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;preferred_username&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;at_hash&quot;</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>至于回调 URL，这个是需要自己设置的部分。可能由于开发习惯导致每个应用的回调 URL 不一样。相对于在 IDP 中填写信息，在懒猫微服的 lzc-manifest.yml 中加这么一行即可。也只有设置了 application.oidc_redirect_path 之后，才能使用 OIDC 相关的环境变量。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">application.oidc_redirect_path:</span> <span class="string">/callback</span></span><br></pre></td></tr></table></figure><p>可以看看我的懒猫 ENV 查看器的设置。通过 oidc_redirect_path 设置回调地址，然后使用 environment 字段还这是需要的环境变量。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="number">0.1</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">懒猫ENV查看器</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">xu.deploy.env</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.2</span></span><br><span class="line"><span class="attr">description:</span></span><br><span class="line"><span class="attr">license:</span> <span class="string">https://choosealicense.com/licenses/mit/</span></span><br><span class="line"><span class="attr">homepage:</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">xu</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">env</span></span><br><span class="line">  <span class="attr">oidc_redirect_path:</span> <span class="string">/callback</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=exec://5005,./lzcapp/pkg/content/run.sh</span></span><br><span class="line">  <span class="attr">environment:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_CLIENT_ID=$&#123;LAZYCAT_AUTH_OIDC_CLIENT_ID&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_CLIENT_SECRET=$&#123;LAZYCAT_AUTH_OIDC_CLIENT_SECRET&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_AUTH_URI=$&#123;LAZYCAT_AUTH_OIDC_AUTH_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_TOKEN_URI=$&#123;LAZYCAT_AUTH_OIDC_TOKEN_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_USERINFO_URI=$&#123;LAZYCAT_AUTH_OIDC_USERINFO_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_ISSUER_URI=$&#123;LAZYCAT_AUTH_OIDC_ISSUER_URI&#125;</span></span><br></pre></td></tr></table></figure><p>然后我们来看 OIDC 的几种授权模式。</p><table><thead><tr><th align="left">应用类型</th><th align="left">授权模式</th></tr></thead><tbody><tr><td align="left">有后端场景</td><td align="left">授权码模式</td></tr><tr><td align="left">SPA，无后端</td><td align="left">隐式模式</td></tr><tr><td align="left">应安全存储密钥</td><td align="left">密码模式</td></tr><tr><td align="left">服务器之间</td><td align="left">Client Credentials</td></tr></tbody></table><p>这个是 Authing 推荐的选择方式，不过据我的经验来讲，就 Web 开发而言大多还是选择隐式授权的居多。看的出来懒猫的 OIDC 也是用的这种。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7c23a4d3-edef-4873-af13-ac75a1ab1094.png" alt="7c23a4d3-edef-4873-af13-ac75a1ab1094"></p><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">https://url/callback?code=xxxx</span><br></pre></td></tr></table></figure><p>其实一个良好的 OIDC 流程是这样的：</p><ol start="0"><li><p>当访问没有权限的路由的时候，在路由守卫中重定向到登录页面。</p></li><li><p>当用户登录的时候，跳转到对应的的 IDP 控制页面，然后输入用户凭证。这个时候会走 IDP 的认证。</p></li><li><p>认证之后会颁发一次性 code（授权码模式），如果是简单的密码模式，那么就会直接返回 Access Token，ID token 以及 refresh token。</p></li><li><p>使用授权码 code 换取 AccessToken、IdToken 以及 refresh token。授权码模式的好处是，把真正的令牌藏在后端交换，只暴露一次性 code，从而极大降低令牌泄露和被滥用的风险。</p></li><li><p>最后我们再用 AccessToken 来访问资源。</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/98e71384-dfcb-426b-9f6e-3190f4e09701.png" alt="98e71384-dfcb-426b-9f6e-3190f4e09701"></p><p>以上是基于懒猫的 OpenID Connect （OIDC）的理论讲解的部分，后面我们会进行实操，手把手创建可以接入 OIDC 的应用。</p><p>备注：关于部分 OIDC 的图文来自 Authing 文档。</p><p><a href="https://docs.authing.cn/v2/concepts/oidc/choose-flow.html">https://docs.authing.cn/v2/concepts/oidc/choose-flow.html</a></p>]]></content>
    
    
    <summary type="html">懒猫微服 OIDC 单点登录开发教程（上篇），从原理到接入配置。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>京东售后拒修 AirPods 称&quot;无问题&quot;，苹果官方检测后直接换新！京东竟删除检测记录？</title>
    <link href="https://blog.no-claw.com/posts/c67acea7/"/>
    <id>https://blog.no-claw.com/posts/c67acea7/</id>
    <published>2025-07-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:05:15.065Z</updated>
    
    <content type="html"><![CDATA[<p>去年在京东买的 Airpod Pro2 售后记录，以前都是在官网买的，还是图省事了。结果闹了个哭笑不得。</p><p>故障问题是，只有一边的耳机有声音，于是京东寄修，得到的答案是没问题，直接给我原反了。然后后台给我一个一张检测单（请记住这个，后面京东后台竟然把这个删了）</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250709052850888.png" alt="image-20250709052850888"></p><p>所谓京东的人给我打电话，是沈阳口音，然后听起来就是一点不懂技术的样子。说耳机是固件的 bug，然后直接把固件降级然后就好了。</p><p>然后我直接三连问：</p><ol><li>airpod 的固件是自动升级的，用户没办法干预，怎么保证下次自动升级不会再有问题？</li><li>有没有什么硬件问题？有时候合上盖子还有声音？</li><li>有时候一个耳机掉电快，另外一个基本满电能不能看一看？</li></ol><p>口头说好找人看，然后耳机直接给寄回来了，没有任何后续。</p><p>京东 Apple 里的耳机检测图片，一看就很不专业，像是某种小店。一口一个有苹果官方的人员主场，一口一个不解决问题，怕不是外包 hhh</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9787159f629105169a842120863bc4ed.jpg" alt="9787159f629105169a842120863bc4ed"></p><p>但是，寄回来的耳机仍然是一样的问题。再次申请售后是不予受理。理由是上一次检测没有问题，让我去 Apple 线下自行解决。从普通客服到专员，再到所谓的客服经理无一不如此。真是，体验简直太差。那就让我去 Apple 直营店打你们的脸吧。</p><p>去了 Apple 三里屯，检测确实有硬件问题，左耳机收音有问题，右耳机能隔着盒子放出来声音，于是给换新了。贴一下检测单。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250709055342011.png" alt="image-20250709055342011"></p><p>然后继续去京东 battle，说 apple 给换新了，你们京东为啥检测不出来。然后仍然是不予受理，继续装死。然后默默的把之前检测没问题的单子给删除了。（最前面的一张）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250709055117286.png" alt="image-20250709055117286"></p><p>这里的红框圈起来的部分，本来是原来那个检测单的，后来竟然给删了，销毁证据，京东你真行。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250709055010618.png" alt="image-20250709055010618"></p><p>以后电子产品大件还怎么放心在京东买啊？</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;去年在京东买的 Airpod Pro2 售后记录，以前都是在官网买的，还是图省事了。结果闹了个哭笑不得。&lt;/p&gt;
&lt;p&gt;故障问题是，只有一边的耳机有声音，于是京东寄修，得到的答案是没问题，直接给我原反了。然后后台给我一个一张检测单（请记住这个，后面京东后台竟然把这个删了）&lt;/p&gt;</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（三）：如何将已有 Docker Compose 应用移植到懒猫微服</title>
    <link href="https://blog.no-claw.com/posts/5e5f8aaa/"/>
    <id>https://blog.no-claw.com/posts/5e5f8aaa/</id>
    <published>2025-07-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文延续前两篇，演示如何把一个已经在本地运行良好的 Docker Compose 应用打包并上架到懒猫微服应用商店。以 Milvus 为例，逐步拆解 Manifest 配置、路由映射、数据卷绑定以及镜像加速等关键环节，帮助大家快速完成移植。</p></blockquote><h3 id="1-目录结构与核心文件"><a href="#1-目录结构与核心文件" class="headerlink" title="1. 目录结构与核心文件"></a>1. 目录结构与核心文件</h3><p>在懒猫微服中，一个最小可用的应用包仅需两个文件：</p><table><thead><tr><th>文件</th><th>作用</th></tr></thead><tbody><tr><td><code>lzc-build.yml</code></td><td>描述打包流程及应用图标。简单应用只需指定 <code>icon</code> 即可。</td></tr><tr><td><code>lzc-manifest.yml</code></td><td>定义应用元数据与服务编排，是移植的重点。</td></tr></tbody></table><p>本文主要关注 <code>lzc-manifest.yml</code> 的编写。</p><h3 id="2-lzc-manifest-yml-字段逐一解析"><a href="#2-lzc-manifest-yml-字段逐一解析" class="headerlink" title="2. lzc-manifest.yml 字段逐一解析"></a>2. <code>lzc-manifest.yml</code> 字段逐一解析</h3><p>现在有了懒猫应用查看器很方便，我们以商店里的 Milvus 的示例 Manifest 为例，并附带注释说明。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702125730816.png" alt="image-20250702125730816"></p><p>这个 lzc-mainfest.yml 解析是重点。主要是 subdomain，ingress，services 这几个字段。总体上还是延续了 Docker compose 的风格。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">Milvus</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">in.zhaoj.milvus</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">2.5</span><span class="number">.8</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">milvus</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="comment"># 子域名：应用上线后将访问 https://milvus.&lt;机器名&gt;.heiyu.space</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">milvus</span></span><br><span class="line">  <span class="attr">background_task:</span> <span class="literal">false</span> <span class="comment"># 是否允许后台运行</span></span><br><span class="line">  <span class="attr">multi_instance:</span> <span class="literal">false</span> <span class="comment"># 是否允许多实例</span></span><br><span class="line">  <span class="attr">gpu_accel:</span> <span class="literal">false</span> <span class="comment"># 是否请求 GPU</span></span><br><span class="line">  <span class="attr">routes:</span> <span class="comment"># 七层（HTTP）路由</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://attu.in.zhaoj.milvus.lzcapp:3000/</span></span><br><span class="line">  <span class="attr">ingress:</span> <span class="comment"># 四层（TCP）转发</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">19530</span></span><br><span class="line">      <span class="attr">service:</span> <span class="string">standalone</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">9091</span></span><br><span class="line">      <span class="attr">service:</span> <span class="string">standalone</span></span><br><span class="line"><span class="attr">services:</span> <span class="comment"># 以下基本等同于 docker‑compose 中的 services</span></span><br><span class="line">  <span class="attr">etcd:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/longixaoyi/milvusdb/etcd:v3.5.18</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_AUTO_COMPACTION_MODE=revision</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_AUTO_COMPACTION_RETENTION=1000</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_QUOTA_BACKEND_BYTES=4294967296</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_SNAPSHOT_COUNT=50000</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      etcd -advertise-client-urls=http://127.0.0.1:2379</span></span><br><span class="line"><span class="string">           -listen-client-urls http://0.0.0.0:2379</span></span><br><span class="line"><span class="string">           --data-dir /etcd</span></span><br><span class="line"><span class="string"></span>    <span class="attr">binds:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/lzcapp/var/etcd:/etcd</span></span><br><span class="line">  <span class="attr">minio:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/longixaoyi/milvusdb/minio:RELEASE.2023-03-20T20-16-18Z</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MINIO_ACCESS_KEY=minioadmin</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MINIO_SECRET_KEY=minioadmin</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">minio</span> <span class="string">server</span> <span class="string">/minio_data</span></span><br><span class="line">    <span class="attr">binds:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/lzcapp/var/minio_data:/minio_data</span></span><br><span class="line">    <span class="attr">health_check:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;curl&quot;</span>, <span class="string">&quot;-f&quot;</span>, <span class="string">&quot;http://localhost:9000/minio/health/live&quot;</span>]</span><br><span class="line">  <span class="attr">standalone:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/longixaoyi/milvusdb/milvus:v2.6.0-rc1</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">milvus</span> <span class="string">run</span> <span class="string">standalone</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_ENDPOINTS=etcd:2379</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MINIO_ADDRESS=minio:9000</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">etcd</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">minio</span></span><br><span class="line">    <span class="attr">binds:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/lzcapp/var/milvus:/var/lib/milvus</span></span><br><span class="line">  <span class="attr">attu:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/longixaoyi/zilliz/attu:latest</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MILVUS_URL=standalone:19530</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">standalone</span></span><br></pre></td></tr></table></figure><h4 id="2-1-subdomain"><a href="#2-1-subdomain" class="headerlink" title="2.1 subdomain"></a>2.1 <code>subdomain</code></h4><p>subdomain 是应用程序上线的域名，例如上述配置上线后即为 <code>https://milvus.&lt;节点名&gt;.heiyu.space</code>。</p><h4 id="2-2-routes"><a href="#2-2-routes" class="headerlink" title="2.2 routes"></a>2.2 <code>routes</code></h4><p>route 来做七层的 HTTP 转发，类似 Nginx 的反向代理。规则格式为 <code>本地路径 = 目标 URL</code>。在示例中，根路径 <code>/</code> 被转发到前端服务 <code>attu</code> 的 3000 端口。</p><p>URL 规则：服务名.包名.lzcapp:端口。（包名随意起）</p><pre><code>- /=http://attu.in.zhaoj.milvus.lzcapp:3000/</code></pre><p>这里 attu 是服务名，端口是 3000，in.zhaoj.milvus 是包名。</p><h4 id="2-3-ingress"><a href="#2-3-ingress" class="headerlink" title="2.3 ingress"></a>2.3 <code>ingress</code></h4><p>用于四层直通转发，适用于非 HTTP 协议（数据库、SSH 等）。示例将 Milvus 的 gRPC (19530) 与 HTTP (9091) 端口暴露给外部。</p><h4 id="2-4-bind"><a href="#2-4-bind" class="headerlink" title="2.4 bind"></a>2.4 <code>bind</code></h4><p>Manifest 中的绑定路径以 <code>/lzcapp/var</code> 为前缀。发布后会被映射到宿主机的 <code>/data/app/var/&lt;package&gt;</code>，也算是为了简化程序移植和学习成本。和 Docker-compose 写绝对路径来说，这里的可移植性执行更好。</p><p>健康检查在这里不是必须的，因为打包的上架的时候服务会帮忙做这个事情。</p><h3 id="3-服务映射与-docker-compose-yml-对照"><a href="#3-服务映射与-docker-compose-yml-对照" class="headerlink" title="3. 服务映射与 docker-compose.yml 对照"></a>3. 服务映射与 <code>docker-compose.yml</code> 对照</h3><p>懒猫 Manifest 的 <code>services</code> 段几乎一一复刻了传统 Compose 配置，常用键均保持一致。以下列出了 Milvus 官方 <code>docker-compose.yml</code>，方便对照理解：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.5&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">etcd:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">milvus-etcd</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">quay.io/coreos/etcd:v3.5.18</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_AUTO_COMPACTION_MODE=revision</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_AUTO_COMPACTION_RETENTION=1000</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_QUOTA_BACKEND_BYTES=4294967296</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ETCD_SNAPSHOT_COUNT=50000</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;DOCKER_VOLUME_DIRECTORY:-.&#125;/volumes/etcd:/etcd</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">etcd</span> <span class="string">-advertise-client-urls=http://etcd:2379</span> <span class="string">-listen-client-urls</span> <span class="string">http://0.0.0.0:2379</span> <span class="string">--data-dir</span> <span class="string">/etcd</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;etcdctl&quot;</span>, <span class="string">&quot;endpoint&quot;</span>, <span class="string">&quot;health&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">20s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">minio:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">milvus-minio</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">minio/minio:RELEASE.2023-03-20T20-16-18Z</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">MINIO_ACCESS_KEY:</span> <span class="string">minioadmin</span></span><br><span class="line">      <span class="attr">MINIO_SECRET_KEY:</span> <span class="string">minioadmin</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9001:9001&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9000:9000&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;DOCKER_VOLUME_DIRECTORY:-.&#125;/volumes/minio:/minio_data</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">minio</span> <span class="string">server</span> <span class="string">/minio_data</span> <span class="string">--console-address</span> <span class="string">&quot;:9001&quot;</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;curl&quot;</span>, <span class="string">&quot;-f&quot;</span>, <span class="string">&quot;http://localhost:9000/minio/health/live&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">20s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">standalone:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">milvus-standalone</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">milvusdb/milvus:v2.6.0-rc1</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;milvus&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;standalone&quot;</span>]</span><br><span class="line">    <span class="attr">security_opt:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">seccomp:unconfined</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">ETCD_ENDPOINTS:</span> <span class="string">etcd:2379</span></span><br><span class="line">      <span class="attr">MINIO_ADDRESS:</span> <span class="string">minio:9000</span></span><br><span class="line">      <span class="attr">MQ_TYPE:</span> <span class="string">woodpecker</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;DOCKER_VOLUME_DIRECTORY:-.&#125;/volumes/milvus:/var/lib/milvus</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;curl&quot;</span>, <span class="string">&quot;-f&quot;</span>, <span class="string">&quot;http://localhost:9091/healthz&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">      <span class="attr">start_period:</span> <span class="string">90s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">20s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;19530:19530&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9091:9091&quot;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;etcd&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;minio&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">default:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">milvus</span></span><br></pre></td></tr></table></figure><p>差异点主要体现在：</p><ul><li>镜像源：建议通过 <code>lzc-cli appstore copy-image &lt;image&gt;</code> 把镜像同步到 <code>registry.lazycat.cloud</code>，解决国内网络拉取问题。</li><li>端口声明：在 Manifest 中，外部访问端口通过 <code>ingress</code>，不再使用 Compose 的 <code>ports</code>。</li><li>健康检查：懒猫平台会统一探测容器存活，可按需省略 <code>healthcheck</code>。</li></ul><h3 id="4-镜像加速实践"><a href="#4-镜像加速实践" class="headerlink" title="4. 镜像加速实践"></a>4. 镜像加速实践</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></pre></td><td class="code"><pre><span class="line">lzc-cli appstore copy-image nginx                                                                                                                           (base) 13:15:36</span><br><span class="line">Waiting ... ( copy nginx to lazycat offical registry)</span><br><span class="line">uploading</span><br><span class="line">23e05839: [####################################################################################################] 100%</span><br><span class="line">23e05839: [####################################################################################################] 100%</span><br><span class="line">23e05839: [####################################################################################################] 100%</span><br><span class="line">3da95a90: [####################################################################################################] 100%</span><br><span class="line">48670a58: [####################################################################################################] 100%</span><br><span class="line">6c8e51cf: [####################################################################################################] 100%</span><br><span class="line">9bbbd7ee: [####################################################################################################] 100%</span><br><span class="line">ce713206: [####################################################################################################] 100%</span><br><span class="line">ee95256d: [####################################################################################################] 100%</span><br><span class="line"></span><br><span class="line">uploaded:  registry.lazycat.cloud/u04123229/library/nginx:d037205fbaf7d60a</span><br></pre></td></tr></table></figure><p>将生成的地址替换到 Manifest 的 <code>image</code> 字段即可。</p><h3 id="5-小结"><a href="#5-小结" class="headerlink" title="5. 小结"></a>5. 小结</h3><ul><li>Manifest 为核心：<code>lzc-manifest.yml</code> 描述了全部运行时需求，移植时优先完善此文件。</li><li>路由分层：<code>routes</code> 管理 HTTP，<code>ingress</code> 管理 TCP，二者配合即可覆盖绝大多数场景。</li><li>统一数据目录：使用 <code>/lzcapp/var</code> 避免硬编码路径，便于跨节点迁移。</li><li>镜像国内托管：通过 <code>lzc-cli appstore copy-image</code> 自动同步到 LazyCat Registry，稳定又快速。</li></ul><p>至此，Milvus 的 Docker Compose 应用已成功移植到懒猫微服。更多进阶玩法，例如 OIDC、VNC 集成等，我们将在后续文章继续分享。</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文延续前两篇，演示如何把一个已经在本地运行良好的 Docker Compose 应用打包并上架到懒猫微服应用商店。以 Milvus 为例，逐步拆解 Manifest 配置、路由映射、数据卷绑定以及镜像加速等关键环节，帮助大家快速完成移植。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-目录结构与核心文件&quot;&gt;&lt;a href=&quot;#1-目录结构与核心文件&quot; class=&quot;headerlink&quot; title=&quot;1. 目录结构与核心文件&quot;&gt;&lt;/a&gt;1. 目录结构与核心文件&lt;/h3&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lzc-build.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;描述打包流程及应用图标。简单应用只需指定 &lt;code&gt;icon&lt;/code&gt; 即可。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lzc-manifest.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;定义应用元数据与服务编排，是移植的重点。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;本文主要关注 &lt;code&gt;lzc-manifest.yml&lt;/code&gt; 的编写。&lt;/p&gt;
&lt;h3 id=&quot;2-lzc-manifest-yml-字段逐一解析&quot;&gt;&lt;a href=&quot;#2-lzc-manifest-yml-字段逐一解析&quot; class=&quot;headerlink&quot; title=&quot;2. lzc-manifest.yml 字段逐一解析&quot;&gt;&lt;/a&gt;2. &lt;code&gt;lzc-manifest.yml&lt;/code&gt; 字段逐一解析&lt;/h3&gt;&lt;p&gt;现在有了懒猫应用查看器很方便，我们以商店里的 Milvus 的示例 Manifest 为例，并附带注释说明。&lt;/p&gt;</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Mac 上 Fn + F12 怎么改成 F12？（适用于 macOS 13 Ventura）</title>
    <link href="https://blog.no-claw.com/posts/18eac60e/"/>
    <id>https://blog.no-claw.com/posts/18eac60e/</id>
    <published>2025-07-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多刚换到 Mac 的用户会遇到一个小困扰：<br>在键盘上直接按 F12 默认是<strong>调节音量</strong>，而不是我们常用的 <strong>F12 功能键</strong>（比如在浏览器里打开开发者工具）。<br>这时候，就得按 <strong>Fn + F12</strong> 才能触发真正的 F12。</p><p>其实在 macOS 13 Ventura 系统中，我们可以很轻松地把键位逻辑切换过来，让 <strong>F12 默认就是 F12</strong>，而音量调节则通过 <strong>Fn + F12</strong> 来实现。下面是详细操作步骤。</p><span id="more"></span><h3 id="设置步骤"><a href="#设置步骤" class="headerlink" title="设置步骤"></a>设置步骤</h3><ol><li><p>打开 **系统设置 (System Settings)**。<br>（屏幕左上角点苹果图标 → 系统设置）</p></li><li><p>在左侧列表里找到并点击 **键盘 (Keyboard)**。</p></li><li><p>在右侧找到 <strong>键盘快捷键… (Keyboard Shortcuts…)</strong> 按钮，点击进入。</p></li><li><p>在弹出的窗口中，往下拉到最下面，找到 **功能键 (Function Keys)**。</p></li><li><p>打开 <strong>“将 F1、F2 等键用作标准功能键”</strong> （Use F1, F2, etc. keys as standard function keys）开关。</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250914203553793.png" alt="image-20250914203553793"></p><h3 id="设置后的效果"><a href="#设置后的效果" class="headerlink" title="设置后的效果"></a>设置后的效果</h3><ul><li><strong>直接按 F12</strong> → 触发 F12 功能键（开发者工具、应用快捷键等）。</li><li><strong>Fn + F12</strong> → 调节音量。</li></ul><p>这样一来，就能让 F12 回归它的本职工作，日常办公和开发都方便很多。</p><h3 id="小贴士"><a href="#小贴士" class="headerlink" title="小贴士"></a>小贴士</h3><ul><li>如果你经常调节音量、亮度，可以保持默认模式更方便；</li><li>如果你更常用功能键（F1–F12），建议切换成标准功能键模式；</li><li>进阶用户还可以安装 <strong>Karabiner-Elements</strong> 来做更复杂的键位映射。</li></ul><p>📌 结语：<br>几步设置，就能让你的 Mac 更顺手。如果你也因为 Fn+F12 而烦恼，不妨试试这个小技巧。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;很多刚换到 Mac 的用户会遇到一个小困扰：&lt;br&gt;在键盘上直接按 F12 默认是&lt;strong&gt;调节音量&lt;/strong&gt;，而不是我们常用的 &lt;strong&gt;F12 功能键&lt;/strong&gt;（比如在浏览器里打开开发者工具）。&lt;br&gt;这时候，就得按 &lt;strong&gt;Fn + F12&lt;/strong&gt; 才能触发真正的 F12。&lt;/p&gt;
&lt;p&gt;其实在 macOS 13 Ventura 系统中，我们可以很轻松地把键位逻辑切换过来，让 &lt;strong&gt;F12 默认就是 F12&lt;/strong&gt;，而音量调节则通过 &lt;strong&gt;Fn + F12&lt;/strong&gt; 来实现。下面是详细操作步骤。&lt;/p&gt;</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>高版本的MacOS如何降级？</title>
    <link href="https://blog.no-claw.com/posts/5b6f9bd/"/>
    <id>https://blog.no-claw.com/posts/5b6f9bd/</id>
    <published>2025-07-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很早问过 Apple 客服 MacOS 的降级机制，半天也没说明白。但是 M 芯片的 Macbook 经常崩溃，如果说 windows 的蓝屏时，那我这个 MacBook 就能崩溃出彩虹色。</p><p>寻找过 Apple 支持，答案就是升级系统。Apple 的行政关系团队给我找了一个非常不靠谱的人，一问三不知，问她什么就是再转问工程团队，然后所有的事情都推第三方软件。然后行政关系团队陈某说对技术不做评价，然后一再坚持他们的人都是专业培训上岗的，然后坚持不换人，坚持不解决电脑问题来给客户扣不配合的帽子。</p><span id="more"></span><p>然后一直拖到过保。以前 iphone 接不到电话是这样，现在 Macbook 还是这样。</p><p>言归正传。Mac 刷机一般几种办法。</p><ol><li>U 盘刷机，这个是传统了，玩过 PE 的都懂。</li><li>系统内格式化：就跟手机差不多的那种。个人感觉不彻底。</li><li>DFU 刷机：需要你有另外一个 MacOS 的电脑。类似于安卓线刷。</li></ol><p>MacOS 降级我采用的是 U 盘装机。参考这个帖子</p><p><a href="https://support.apple.com/zh-cn/101578">https://support.apple.com/zh-cn/101578</a></p><p>从 Apple Store 下载 OS，然后把 U 盘的 label 改成&#x2F;Volumes&#x2F;MyVolume，最后做随身碟</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250708203135382.png" alt="image-20250708203135382"></p><p>可以选择各个 MacOS 大版本的最后的 release。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250708203344815.png" alt="image-20250708203344815"></p><p>烧录命令是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> /Applications/Install\ macOS\ Ventura.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume</span><br></pre></td></tr></table></figure><p>如果需要其他的系统，那么换一个版本号即可。</p><p>我的 23 年的 M2 Pro，当时出长的时候是 MacOS13 Ventura，所以当我想换回 MacOS12 的时候下载都报错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250708203409606.png" alt="image-20250708203409606"></p><p>如果直接安装还会报错：这个卷无法降级。（不理解这个操作，windows 的话随便格式化）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/de83700ed1e47e0386ebf756f594c4d3.jpg" alt="de83700ed1e47e0386ebf756f594c4d3"></p><p>需要进入磁盘工具降级：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/v2-674c2048221fed20e5536c5aa67ff964_1440w.webp" alt="img"></p><p>然后安装就可可以了，剩下就是漫长的等待。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250708203801861.png" alt="image-20250708203801861"></p><p>从 Intel 黑苹果时代走过来的，结果白的还没黑的好用。。。。</p>]]></content>
    
    
    <summary type="html">macOS 高版本降级到低版本的方法与注意事项。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十八）：用 planka 做项目管理</title>
    <link href="https://blog.no-claw.com/posts/51800531/"/>
    <id>https://blog.no-claw.com/posts/51800531/</id>
    <published>2025-07-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>工作里用 Jira 管理项目进度。家里也是想找个类似的软件来管理一些长期的事情，如果能带一多人合作的功能就更好了。在网上找了很久也没有太好用的，直到在懒猫微服中找到 planka，在对比了几个同类型的软件之后，决定使用这个软件来管理自己的一些事情。</p><span id="more"></span><p>其他的软件体验不佳的点主要是：</p><ol><li>付费：还都是订阅制，没有找到终身制的软件</li><li>界面太丑，操作太复杂</li><li>Saas 免费版本延迟太高</li><li>移动端访问体验不佳</li></ol><p>Planka 算是解决了大部分的问题：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250706142600994.png" alt="image-20250706142600994">虽然这个页面不是很现代化风格，但是也不丑，页面响应速度很快。而其他软件大多臃肿体验不佳，要么就是延迟的很高耽误体验。</p><p>简介的 UI 和快速的响应很好的诠释了 less is more 的原则。</p><p>进入主页之后可以新建多个项目，算是一个隔离吧，虽然我目前也只是用到了一个。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250706140800760.png" alt="image-20250706140800760"></p><p>每一个 Project 都能开好几个 board，这样就把不同类别的事情跟区分开了。</p><p>然后每个 board 上标注 Todo，Doing，Pending，Done，Deprecated 来确认事情的进度。有点白版卡片的那个味道了，不用自己像线下那种写贴纸来回移动，也不用再花费软件的订阅的费用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/103a3a28-0ceb-466d-9d96-50161d150b04.png" alt="103a3a28-0ceb-466d-9d96-50161d150b04"></p><p>这个是我和前端协作开发时候一起做的 Dashboard，当时在一起开发一个前后端分离的大语言模型的 APP。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8703083443e36972236a8450eab7c1c7.png" alt="8703083443e36972236a8450eab7c1c7"></p><p>只要给他新建一个用户就可以了，然后分给他某个面板的权限，然后就可以可以一起愉快的协作了。（前提给他安装了懒猫微服的客户端，分了 app 权限）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250706145557778.png" alt="image-20250706145557778"></p><p>这个是面板的详细参数，可以把 task 分配给某个成员，然后也可以添加 task 描述，task 子任务拆解以及成员评论，拿来记录一些 change log 还是不错的，起码测试的一些过程可以随手记录在这里了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250706140941967.png" alt="image-20250706140941967"></p><p>Dashboard 功能还需要继续探索，比如计时，打标签一些的功能。</p><p>不过我目前用到的功能就这么多，也算能基本覆盖全部的场景了。</p><p>这个是我用 planka 来追踪关于懒猫微服的写作和上架应用的一些事情，真的帮助了我很多很多。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250707080727189.png" alt="image-20250707080727189"></p>]]></content>
    
    
    <summary type="html">在懒猫微服上部署 Planka 做个人项目管理，类 Jira 的看板工具。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（二）：远程调试之 Devshell</title>
    <link href="https://blog.no-claw.com/posts/2c167bb4/"/>
    <id>https://blog.no-claw.com/posts/2c167bb4/</id>
    <published>2025-07-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们上一篇讲述了如何打包上架 APP，这期我们就来看如何远程调试即将上架的 APP。</p><p>简单来讲，这个 devshell 其实就是在微服上打开了一个虚拟机环境，然后我们可以进去里面测试我们的命令。非要说技术实现，那就是 docker exec 了。只不过是做成了本地和微服之间同步的样子。</p><p>输入微服的名字&#x2F;v2&#x2F;_catalog，可以看到微服里面 Docker 仓库存放的镜像，debug.bridge 开头的就是 devshell 的 image。</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;repositories&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;debug.bridge/231ee20d4e4d8edbd2004e7609fd9c15&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;debug.bridge/ddc6dbf609125b7bd2c0efb0ed4254d1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;helloworld&quot;</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>在 lzc-build.yml 里定义了 devshell 的配置，这个的意思是就是安装 node 和 python 的环境，设置国内源，然后讲根目录转发到 5173，这个是 vue 的端口。所以我们可以推断，这个是一个 Vue+python 的全栈项目，所以我们可以开两个终端来进入 devshell，分别调试前后端。</p><span id="more"></span><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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="attr">devshell:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://127.0.0.1:5173</span></span><br><span class="line">  <span class="attr">dependencies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">nodejs</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">npm</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">python3</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">py3-pip</span></span><br><span class="line">  <span class="attr">setupscript:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    export npm_config_registry=https://registry.npmmirror.com</span></span><br><span class="line"><span class="string">    export PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple</span></span><br></pre></td></tr></table></figure><p>使用 lzc-cli project devshell -f 进入 devshell，可以看到这个使用了 registry.lazycat.cloud&#x2F;lzc-cli&#x2F;devshell 作为 base image，然后换源安装依赖。最后设置 setupscript 里面的 ENV。而 CMD [“sleep”, “infinity”]<code>会让容器启动后执行</code>sleep infinity&#96; 命令，即无限休眠。目的是防止容器因没有前台进程而自动退出（保持容器处于“运行”状态）。</p><p>这里有个问题，如果你在 lzc-manifest.yml 里指定了 routes 也同样会生效。如果你要跑一些初始化脚本，可以在使用这两个办法</p><ol><li>在 lzc-manifest.yml 文件中使用&#x2F;api&#x2F;&#x3D;exec:&#x2F;&#x2F;3000,.&#x2F;lzcapp&#x2F;pkg&#x2F;content&#x2F;backend&#x2F;run.sh</li><li>lzc-build.yml 中注入脚本 setupscript。</li></ol><p>如果你偷懒在 lzc-build.yml 里 routes 的执行&#x2F;api&#x2F;&#x3D;exec:&#x2F;&#x2F;3000,.&#x2F;lzcapp&#x2F;pkg&#x2F;cache&#x2F;backend&#x2F;run.sh，似乎也只能转发端口，不能运行脚本。</p><p>从日志看，也就是说还是安装了一个应用程序上去，只不过我们可以通过类似 ssh remote 的方式来动态调试。如果 APP 上架之后，能做的恐怕只有 lzc-docker exec 了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">跳过执行 buildscript</span><br><span class="line">跳过拷贝 contentdir 内容</span><br><span class="line"></span><br><span class="line">Dockerfile</span><br><span class="line">STEP 1/4: FROM registry.lazycat.cloud/lzc-cli/devshell:v0.0.5</span><br><span class="line">STEP 2/4: RUN sed -i &#x27;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g&#x27; /etc/apk/repositories</span><br><span class="line">--&gt; Using cache 2302149ded79afb639f9935a07e7ea0d63b5644b10e9890d49ad06786e7b31db</span><br><span class="line">--&gt; 2302149ded79</span><br><span class="line">STEP 3/4: RUN apk add --no-cache bash nodejs npm py3-pip python3   &amp;&amp; echo &quot;root:root&quot; | chpasswd</span><br><span class="line">--&gt; Using cache 9bb679fa2c9d10ab1a2433be4c59c852affb2a6844c62ecd9eb8d727505821fb</span><br><span class="line">--&gt; 9bb679fa2c9d</span><br><span class="line">STEP 4/4: CMD [&quot;sleep&quot;, &quot;infinity&quot;]</span><br><span class="line">--&gt; Using cache 5ed701af0e0f7040c3dc5409f547b271b4f4e792fd4fcbfc4af8a3abecf8d363</span><br><span class="line">COMMIT debug.bridge/ddc6dbf609125b7bd2c0efb0ed4254d1:latest</span><br><span class="line">--&gt; 5ed701af0e0f</span><br><span class="line">Successfully tagged debug.bridge/ddc6dbf609125b7bd2c0efb0ed4254d1:latest</span><br><span class="line">5ed701af0e0f7040c3dc5409f547b271b4f4e792fd4fcbfc4af8a3abecf8d363</span><br><span class="line">Getting image source signatures</span><br><span class="line">Copying blob sha256:c164879b06ca56693b742ec917059cce495320d4d8f6140bde7e875f53377ea1</span><br><span class="line">Copying blob sha256:69bc8c25bce956e4c34fdfee091c531ffa8660454526ea9f76c956f9b930c57b</span><br><span class="line">Copying blob sha256:98e60e58e2d093fd95b5d5d61ae6dac25bff89ba1b46d42395d480dfb75bddab</span><br><span class="line">Copying blob sha256:d4fc045c9e3a848011de66f34b81f052d4f2c15a17bb196d637e526349601820</span><br><span class="line">Copying blob sha256:b244bd08b327b00bfbbbbf4f424ade914ee891a52d9192a573dbe484e22d86ab</span><br><span class="line">Copying config sha256:5ed701af0e0f7040c3dc5409f547b271b4f4e792fd4fcbfc4af8a3abecf8d363</span><br><span class="line">Writing manifest to image destination</span><br><span class="line">输出lpk包 /Users/xu/Desktop/todolist-py-lzcapp-demo/cloud.lazycat.app.todolistpy-v0.0.1.lpk</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">👉 请在浏览器中访问 https://todolistpy.dev.heiyu.space</span><br><span class="line">👉 并使用微服的用户名和密码登录</span><br><span class="line"></span><br><span class="line">+ export &#x27;npm_config_registry=https://registry.npmmirror.com&#x27;</span><br><span class="line">+ export &#x27;PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple&#x27;</span><br><span class="line">+ set -e</span><br><span class="line">+ mkdir -p /lzcapp/cache/devshell</span><br><span class="line"></span><br><span class="line">+ cd /lzcapp/cache/devshell</span><br><span class="line">+ exec /bin/sh</span><br><span class="line">/lzcapp/cache/devshell #</span><br><span class="line">/lzcapp/cache/devshell # ls</span><br><span class="line">README.md         backend           build.sh          lzc-build.yml     lzc-icon.png      lzc-manifest.yml  ui</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们可以看到文件夹的内容已经被同步过来了，我们可以实时同步开发机文件的创建和修改。不过目前和移动，重命名相关的同步有点小问题，具体表现为</p><ol><li>本地删除旧的文件，但是 devshell 里不会同步删除</li><li>删除文件 a，但是把 b 重名为 a，devshell 不会同步更改（但是删除 a 重建可以）</li></ol><p>相信这两个小问题修好也只是时间问题，当然也有 workaround。</p><p>从&#x2F;data&#x2F;app&#x2F;cache&#x2F;包名这个目录删除 devshell 文件夹之后，然后重新执行 lzc-cli project devshell -f ，这样工作区就被清理干净了。</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">(base) lzcbox-029c588e /data/app/cache/cloud.lazycat.app.todolistpy <span class="comment"># ls</span></span><br><span class="line">devshell</span><br><span class="line">(base) lzcbox-029c588e /data/app/cache/cloud.lazycat.app.todolistpy <span class="comment"># cd devshell/</span></span><br><span class="line">(base) lzcbox-029c588e /data/app/cache/cloud.lazycat.app.todolistpy/devshell <span class="comment"># ls</span></span><br><span class="line">1  1312  README.md  backend  build.sh  lzc-build.yml  lzc-icon.png  lzc-manifest.yml  ui</span><br></pre></td></tr></table></figure><p>另外，我们再通过 ssh 进入&#x2F;data&#x2F;app 这个目录，有两个子文件夹，一个叫做 cache，一个叫做 var。 cache 就是我们这个 devshell 的工作区。var 就是数据持久化的目录。如果在这里新建一个目录，那么就可以在网盘里实时看到。</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">/data/app/var/xu.deploy.lazycat-nav <span class="comment"># touch test</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702063901802.png" alt="image-20250702063901802"></p><p>以上就是 devshell 的全部内容了，如果你需要借助微服的一些特性还进行开发，比如获取环境变量，OIDC 什么的，可以用这个方法来轻松调试。</p>]]></content>
    
    
    <summary type="html">使用 Devshell 远程调试懒猫微服上的应用，提升开发效率。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（五）：懒猫微服如何使用 OpenID Connect （OIDC）？（下）</title>
    <link href="https://blog.no-claw.com/posts/48285dc8/"/>
    <id>https://blog.no-claw.com/posts/48285dc8/</id>
    <published>2025-07-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>先决条件：</p><p>需要在<code>lzc-manifest.yml</code>定义 oidc_redirect_path 和 environment。</p><h4 id="配置lzc-manifest-yml"><a href="#配置lzc-manifest-yml" class="headerlink" title="配置lzc-manifest.yml"></a>配置<code>lzc-manifest.yml</code></h4><p>oidc_redirect_path 就是你的应用的回调地址，只有写了这个之后才能正确使用 OpenID Connect 的环境变量。</p><p>回调地址是按照应用而定的，有的是&#x2F;callback，&#x2F;oidc&#x2F;callback 或者&#x2F;oauth&#x2F;callback。</p><span id="more"></span><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="number">0.1</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">懒猫ENV查看器</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">xu.deploy.env</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.2</span></span><br><span class="line"><span class="attr">description:</span></span><br><span class="line"><span class="attr">license:</span> <span class="string">https://choosealicense.com/licenses/mit/</span></span><br><span class="line"><span class="attr">homepage:</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">xu</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">env</span></span><br><span class="line">  <span class="attr">oidc_redirect_path:</span> <span class="string">/callback</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=exec://5005,./lzcapp/pkg/content/run.sh</span></span><br><span class="line">  <span class="attr">environment:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_CLIENT_ID=$&#123;LAZYCAT_AUTH_OIDC_CLIENT_ID&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_CLIENT_SECRET=$&#123;LAZYCAT_AUTH_OIDC_CLIENT_SECRET&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_AUTH_URI=$&#123;LAZYCAT_AUTH_OIDC_AUTH_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_TOKEN_URI=$&#123;LAZYCAT_AUTH_OIDC_TOKEN_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_USERINFO_URI=$&#123;LAZYCAT_AUTH_OIDC_USERINFO_URI&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">LAZYCAT_AUTH_OIDC_ISSUER_URI=$&#123;LAZYCAT_AUTH_OIDC_ISSUER_URI&#125;</span></span><br></pre></td></tr></table></figure><p>定义了环境变量之后，我们就可以在代码中使用环境变量：</p><p>开机时一次性从环境变量读取懒猫微服的应用域名、OIDC 客户端 ID&#x2F;密钥，以及授权、令牌、用户信息三个核心端点，并根据应用域名拼出默认 Redirect URI，从而把所有与 OpenID Connect 登录相关的敏感信息解耦。</p><p>这里的 callback 是应用的回调 URL，需要根据应用调整。</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></pre></td><td class="code"><pre><span class="line">LAZYCAT_BOX_DOMAIN = os.environ.get(<span class="string">&#x27;LAZYCAT_BOX_DOMAIN&#x27;</span>)</span><br><span class="line">LAZYCAT_APP_DOMAIN = os.environ.get(<span class="string">&#x27;LAZYCAT_APP_DOMAIN&#x27;</span>)</span><br><span class="line"></span><br><span class="line">CLIENT_ID      = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_CLIENT_ID&quot;</span>)</span><br><span class="line">CLIENT_SECRET  = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_CLIENT_SECRET&quot;</span>)</span><br><span class="line">AUTH_ENDPOINT       = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_AUTH_URI&quot;</span>)</span><br><span class="line">TOKEN_ENDPOINT      = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_TOKEN_URI&quot;</span>)</span><br><span class="line">USERINFO_ENDPOINT   = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_USERINFO_URI&quot;</span>)</span><br><span class="line">REDIRECT_URI   = os.getenv(<span class="string">&quot;OIDC_REDIRECT_URI&quot;</span>, <span class="string">f&quot;https://<span class="subst">&#123;LAZYCAT_APP_DOMAIN&#125;</span>/callback&quot;</span>)</span><br></pre></td></tr></table></figure><h4 id="登录功能"><a href="#登录功能" class="headerlink" title="登录功能"></a>登录功能</h4><p>在用户访问 <code>/login</code> 时动态生成一对 PKCE 凭据（随机 code verifier 和其 SHA-256 派生的 code challenge），把 verifier 暂存进会话，再携带 challenge 等参数构造 OIDC 授权码请求，并将用户浏览器重定向到身份提供方完成安全登录；回调阶段可用 session 中的 code verifier 与返回的 code exchange 配合，防止授权码被劫持或重放，从而提升 OAuth 2.0&#x2F;OIDC 的安全性。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ======= 生成 PKCE Code Verifier &amp; Challenge =======</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_pkce_pair</span>():</span><br><span class="line">    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(<span class="number">32</span>)).rstrip(<span class="string">b&#x27;=&#x27;</span>).decode()</span><br><span class="line">    code_challenge = base64.urlsafe_b64encode(</span><br><span class="line">        hashlib.sha256(code_verifier.encode()).digest()</span><br><span class="line">    ).rstrip(<span class="string">b&#x27;=&#x27;</span>).decode()</span><br><span class="line">    <span class="keyword">return</span> code_verifier, code_challenge</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/login&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    code_verifier, code_challenge = generate_pkce_pair()</span><br><span class="line">    session[<span class="string">&#x27;code_verifier&#x27;</span>] = code_verifier</span><br><span class="line"></span><br><span class="line">    auth_url = (</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;AUTH_ENDPOINT&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;?response_type=code&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;client_id=<span class="subst">&#123;CLIENT_ID&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;redirect_uri=<span class="subst">&#123;REDIRECT_URI&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;scope=openid profile email&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;code_challenge=<span class="subst">&#123;code_challenge&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;code_challenge_method=S256&quot;</span></span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(auth_url)</span><br><span class="line">    <span class="keyword">return</span> redirect(auth_url)</span><br></pre></td></tr></table></figure><ol><li><code>generate_pkce_pair()</code><ul><li>使用 <code>secrets.token_bytes(32)</code> 随机生成 32 字节高强度随机数。</li><li>先经 <code>base64.urlsafe_b64encode</code> 再去掉尾部 <code>=</code> 得到 <strong>code verifier</strong>。</li><li>对 code verifier 做 SHA-256 散列后再次 base64 URL 安全编码并去掉 <code>=</code>，得到 <strong>code challenge</strong>。</li><li>返回二元组 <code>(code_verifier, code_challenge)</code>。</li></ul></li><li><code>/login</code> 路由<ul><li>调用 <code>generate_pkce_pair()</code> 生成并拿到 <code>code_verifier</code> 和 <code>code_challenge</code>。</li><li>将 <code>code_verifier</code> 写入 Flask <code>session</code>，以便稍后在回调时校验。</li><li>拼接授权端点 <code>AUTH_ENDPOINT</code> 形成认证 URL：<ul><li><code>response_type=code</code> 采用授权码模式</li><li><code>client_id</code>、<code>redirect_uri</code>、<code>scope</code> 等常规 OIDC 参数</li><li><code>code_challenge</code> 与 <code>code_challenge_method=S256</code> 声明使用 PKCE(S256)</li></ul></li><li><code>redirect(auth_url)</code> 将浏览器跳转到身份提供方进行登录 + 授权</li></ul></li></ol><h4 id="回调地址"><a href="#回调地址" class="headerlink" title="回调地址"></a>回调地址</h4><p><code>/callback</code> 处理函数先从回调参数取出授权码，再用会话里的 code verifier 按 PKCE + 授权码模式向令牌端点换取 access token 和 ID token；成功后用 access token 调 <code>/userinfo</code> 获取用户资料，并把三者一起返回。如此既完成了 OAuth 2.0 的安全换码，又拿到了 OIDC 提供的登录身份信息，实现前后端分离的单点登录闭环。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/callback&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">callback</span>():</span><br><span class="line">    code = request.args.get(<span class="string">&#x27;code&#x27;</span>)</span><br><span class="line">    code_verifier = session.get(<span class="string">&#x27;code_verifier&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 请求 access token</span></span><br><span class="line">    headers = &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>&#125;</span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&#x27;grant_type&#x27;</span>: <span class="string">&#x27;authorization_code&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;client_id&#x27;</span>: CLIENT_ID,</span><br><span class="line">        <span class="string">&#x27;client_secret&#x27;</span>: CLIENT_SECRET,</span><br><span class="line">        <span class="string">&#x27;code&#x27;</span>: code,</span><br><span class="line">        <span class="string">&#x27;redirect_uri&#x27;</span>: REDIRECT_URI,</span><br><span class="line">        <span class="string">&#x27;code_verifier&#x27;</span>: code_verifier,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    token_resp = requests.post(TOKEN_ENDPOINT, data=data, headers=headers)</span><br><span class="line">    <span class="built_in">print</span>(token_resp)</span><br><span class="line">    token_data = token_resp.json()</span><br><span class="line">    <span class="built_in">print</span>(token_data)</span><br><span class="line"></span><br><span class="line">    access_token = token_data.get(<span class="string">&#x27;access_token&#x27;</span>)</span><br><span class="line">    id_token = token_data.get(<span class="string">&#x27;id_token&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取用户信息</span></span><br><span class="line">    userinfo_resp = requests.get(USERINFO_ENDPOINT, headers=&#123;</span><br><span class="line">        <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">f&#x27;Bearer <span class="subst">&#123;access_token&#125;</span>&#x27;</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">&#x27;Access Token&#x27;</span>: access_token,</span><br><span class="line">        <span class="string">&#x27;ID Token&#x27;</span>: id_token,</span><br><span class="line">        <span class="string">&#x27;UserInfo&#x27;</span>: userinfo_resp.json()</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><ol><li><p><code>code = request.args.get(&#39;code&#39;)</code></p><ul><li>从回调 URL 查询参数中取出授权服务器返回的 <code>code</code>（授权码）。</li></ul></li><li><p><code>code_verifier = session.get(&#39;code_verifier&#39;)</code></p><ul><li>读取先前 <code>/login</code> 时存进会话的 <strong>code verifier</strong>，准备用于 PKCE 校验。</li></ul></li><li><p><strong>准备换取令牌的 HTTP POST 请求</strong></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">headers = &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>&#125;</span><br><span class="line">data = &#123;</span><br><span class="line">    <span class="string">&#x27;grant_type&#x27;</span>: <span class="string">&#x27;authorization_code&#x27;</span>,     <span class="comment"># 授权码模式</span></span><br><span class="line">    <span class="string">&#x27;client_id&#x27;</span>: CLIENT_ID,</span><br><span class="line">    <span class="string">&#x27;code&#x27;</span>: code,                           <span class="comment"># 回调拿到的授权码</span></span><br><span class="line">    <span class="string">&#x27;redirect_uri&#x27;</span>: REDIRECT_URI,           <span class="comment"># 必须与首跳一致</span></span><br><span class="line">    <span class="string">&#x27;code_verifier&#x27;</span>: code_verifier,         <span class="comment"># PKCE 关键参数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>code_verifier</code> 会被身份提供方与首跳收到的 <code>code_challenge</code> 做 SHA-256 对比，从而证明客户端的“持有者”身份，防止授权码被截获后被第三方滥用。</li></ul></li><li><p><code>token_resp = requests.post(TOKEN_ENDPOINT, data=data, headers=headers)</code></p><ul><li>向令牌端点发送表单数据以换取 <strong>Access Token &#x2F; ID Token</strong>。</li></ul></li><li><p><code>token_data = token_resp.json()</code></p><ul><li><p>解析 JSON 响应。例如：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;access_token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id_token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;expires_in&quot;</span><span class="punctuation">:</span> <span class="number">3600</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Bearer&quot;</span><span class="punctuation">,</span></span><br><span class="line">  ...</span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><code>access_token = token_data.get(&#39;access_token&#39;)</code></p><ul><li>读取访问令牌，用于调用受保护 API。<br><code>id_token = token_data.get(&#39;id_token&#39;)</code></li><li>读取 OIDC <strong>ID Token</strong>，携带用户身份声明，可本地解码验证。</li></ul></li><li><p><strong>获取用户信息</strong></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></pre></td><td class="code"><pre><span class="line">userinfo_resp = requests.get(</span><br><span class="line">    USERINFO_ENDPOINT,</span><br><span class="line">    headers=&#123;<span class="string">&#x27;Authorization&#x27;</span>: <span class="string">f&#x27;Bearer <span class="subst">&#123;access_token&#125;</span>&#x27;</span>&#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li>按 OIDC 规范，用 Bearer Token 调 <code>/userinfo</code> 端点，拿到 JSON 形式的用户信息。</li></ul></li><li><p><strong>返回聚合结果（此处直接返回给浏览器以便演示）</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="string">&#x27;Access Token&#x27;</span>: access_token,</span><br><span class="line">    <span class="string">&#x27;ID Token&#x27;</span>: id_token,</span><br><span class="line">    <span class="string">&#x27;UserInfo&#x27;</span>: userinfo_resp.json()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p>在浏览器中可以看到这个登录跳转：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250705095459340.png" alt="image-20250705095459340"></p><p>还是这个图，我们继续看这个流程：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7a6f946c-b299-410b-8286-2958d488caa2.png" alt="7a6f946c-b299-410b-8286-2958d488caa2"></p><p>拿到 code 之后可以就可以换到 Access token 和 ID Token 了，这个 code 只有一次有效，可以达到防重放的效果。当然这个只是 OIDC 的一个例子，在生产环境的 APP 中还需要做路由守卫以及 access 续签的操作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250705100147125.png" alt="image-20250705100147125"></p><h5 id="完整代码如下："><a href="#完整代码如下：" class="headerlink" title="完整代码如下："></a>完整代码如下：</h5><ol><li><strong>读取配置</strong><ul><li>从环境变量获取 Lazycat 平台的域名、OIDC 客户端 ID&#x2F;密钥，以及授权端点、令牌端点、用户信息端点和回调地址。</li><li>用随机 <code>app.secret_key</code> 支持 Flask Session。</li></ul></li><li><strong>PKCE 安全增强</strong><ul><li><code>generate_pkce_pair()</code> 动态生成 <code>code_verifier / code_challenge</code> ；后者随登录请求携带，前者保存在 Session，回调时再带给 Token 端点，防止授权码被劫持。</li></ul></li><li><strong>核心路由</strong><ul><li><code>/</code>：渲染首页（需自备 <code>index.html</code>）。</li><li><code>/login</code>：<ol><li>生成 PKCE 对；</li><li>拼接授权 URL（<code>response_type=code</code>，scope 含 <code>openid profile email</code>）；</li><li>浏览器重定向到 IdP 登录&#x2F;授权页面。</li></ol></li><li><code>/callback</code>：<ol><li>取回 <code>code</code> 与 <code>code_verifier</code>；</li><li>POST 到 <code>TOKEN_ENDPOINT</code> 换取 <code>access_token</code> 和 <code>id_token</code>；</li><li>用 <code>access_token</code> 调用 <code>USERINFO_ENDPOINT</code> 拿到用户信息；</li><li>以 JSON 形式返回令牌与用户资料。</li></ol></li></ul></li></ol><blockquote><p><strong>注意</strong>：生产环境应关闭 <code>debug=True</code>、使用 HTTPS、校验 <code>state</code> 参数防 CSRF，并妥善处理 Token 异常和错误分支。</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"><span class="keyword">import</span> secrets</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, redirect, request, session, url_for, render_template</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = os.urandom(<span class="number">24</span>)</span><br><span class="line"></span><br><span class="line">LAZYCAT_BOX_DOMAIN = os.environ.get(<span class="string">&#x27;LAZYCAT_BOX_DOMAIN&#x27;</span>)</span><br><span class="line">LAZYCAT_APP_DOMAIN = os.environ.get(<span class="string">&#x27;LAZYCAT_APP_DOMAIN&#x27;</span>)</span><br><span class="line"></span><br><span class="line">CLIENT_ID      = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_CLIENT_ID&quot;</span>)</span><br><span class="line">CLIENT_SECRET  = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_CLIENT_SECRET&quot;</span>)</span><br><span class="line">AUTH_ENDPOINT       = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_AUTH_URI&quot;</span>)</span><br><span class="line">TOKEN_ENDPOINT      = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_TOKEN_URI&quot;</span>)</span><br><span class="line">USERINFO_ENDPOINT   = os.getenv(<span class="string">&quot;LAZYCAT_AUTH_OIDC_USERINFO_URI&quot;</span>)</span><br><span class="line">REDIRECT_URI   = os.getenv(<span class="string">&quot;OIDC_REDIRECT_URI&quot;</span>, <span class="string">f&quot;https://<span class="subst">&#123;LAZYCAT_APP_DOMAIN&#125;</span>/callback&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(CLIENT_ID)</span><br><span class="line"><span class="built_in">print</span>(CLIENT_SECRET)</span><br><span class="line"><span class="built_in">print</span>(AUTH_ENDPOINT)</span><br><span class="line"><span class="built_in">print</span>(TOKEN_ENDPOINT)</span><br><span class="line"><span class="built_in">print</span>(USERINFO_ENDPOINT)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(REDIRECT_URI)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ======= 生成 PKCE Code Verifier &amp; Challenge =======</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_pkce_pair</span>():</span><br><span class="line">    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(<span class="number">32</span>)).rstrip(<span class="string">b&#x27;=&#x27;</span>).decode()</span><br><span class="line">    code_challenge = base64.urlsafe_b64encode(</span><br><span class="line">        hashlib.sha256(code_verifier.encode()).digest()</span><br><span class="line">    ).rstrip(<span class="string">b&#x27;=&#x27;</span>).decode()</span><br><span class="line">    <span class="keyword">return</span> code_verifier, code_challenge</span><br><span class="line"></span><br><span class="line"><span class="comment"># ======= 首页 =======</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index</span>():</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;index.html&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ======= 跳转授权页 =======</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/login&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    code_verifier, code_challenge = generate_pkce_pair()</span><br><span class="line">    session[<span class="string">&#x27;code_verifier&#x27;</span>] = code_verifier</span><br><span class="line"></span><br><span class="line">    auth_url = (</span><br><span class="line">        <span class="string">f&quot;<span class="subst">&#123;AUTH_ENDPOINT&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;?response_type=code&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;client_id=<span class="subst">&#123;CLIENT_ID&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;redirect_uri=<span class="subst">&#123;REDIRECT_URI&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;scope=openid profile email&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;code_challenge=<span class="subst">&#123;code_challenge&#125;</span>&quot;</span></span><br><span class="line">        <span class="string">f&quot;&amp;code_challenge_method=S256&quot;</span></span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(auth_url)</span><br><span class="line">    <span class="keyword">return</span> redirect(auth_url)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ======= 回调处理 =======</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/callback&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">callback</span>():</span><br><span class="line">    code = request.args.get(<span class="string">&#x27;code&#x27;</span>)</span><br><span class="line">    code_verifier = session.get(<span class="string">&#x27;code_verifier&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 请求 access token</span></span><br><span class="line">    headers = &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>&#125;</span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&#x27;grant_type&#x27;</span>: <span class="string">&#x27;authorization_code&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;client_id&#x27;</span>: CLIENT_ID,</span><br><span class="line">        <span class="string">&#x27;client_secret&#x27;</span>: CLIENT_SECRET,</span><br><span class="line">        <span class="string">&#x27;code&#x27;</span>: code,</span><br><span class="line">        <span class="string">&#x27;redirect_uri&#x27;</span>: REDIRECT_URI,</span><br><span class="line">        <span class="string">&#x27;code_verifier&#x27;</span>: code_verifier,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    token_resp = requests.post(TOKEN_ENDPOINT, data=data, headers=headers)</span><br><span class="line">    <span class="built_in">print</span>(token_resp)</span><br><span class="line">    token_data = token_resp.json()</span><br><span class="line">    <span class="built_in">print</span>(token_data)</span><br><span class="line"></span><br><span class="line">    access_token = token_data.get(<span class="string">&#x27;access_token&#x27;</span>)</span><br><span class="line">    id_token = token_data.get(<span class="string">&#x27;id_token&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取用户信息</span></span><br><span class="line">    userinfo_resp = requests.get(USERINFO_ENDPOINT, headers=&#123;</span><br><span class="line">        <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">f&#x27;Bearer <span class="subst">&#123;access_token&#125;</span>&#x27;</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">&#x27;Access Token&#x27;</span>: access_token,</span><br><span class="line">        <span class="string">&#x27;ID Token&#x27;</span>: id_token,</span><br><span class="line">        <span class="string">&#x27;UserInfo&#x27;</span>: userinfo_resp.json()</span><br><span class="line">    &#125;</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">    app.run(debug=<span class="literal">True</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果使用 authlib 是这样子的.通过 Authlib 把应用接入 OIDC：</p><p>启动时先从环境变量读取并校验客户端 ID、密钥及各端点；</p><p>随后注册 OIDC 客户端并自动启用 PKCE。</p><p>用户访问 <code>/login</code> 时，服务端生成 nonce 并调用 <code>authorize_redirect()</code> 将浏览器跳转到身份提供方登录，同时在会话里保存随机值；身份提供方完成认证后回调到 <code>/callback</code>，<code>authorize_access_token()</code> 会携带先前的 code 和 code verifier 去换取 access token &#x2F; ID token，并用保存的 nonce 校验 ID Token 防止重放。</p><p>成功后解析得到的声明（用户信息）渲染或写入 Session，即可认为用户已登录。如此利用现成库把 PKCE、状态验证、ID Token 验签等安全细节都交给框架处理，只需少量代码就实现了安全的单点登录闭环。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">import os, secrets</span><br><span class="line">from flask import Flask, request, render_template</span><br><span class="line">from authlib.integrations.flask_client import OAuth</span><br><span class="line"></span><br><span class="line">LAZYCAT_BOX_DOMAIN = os.environ.get(&#x27;LAZYCAT_BOX_DOMAIN&#x27;)</span><br><span class="line">LAZYCAT_APP_DOMAIN = os.environ.get(&#x27;LAZYCAT_APP_DOMAIN&#x27;)</span><br><span class="line"></span><br><span class="line">CLIENT_ID      = os.getenv(&quot;LAZYCAT_AUTH_OIDC_CLIENT_ID&quot;)</span><br><span class="line">CLIENT_SECRET  = os.getenv(&quot;LAZYCAT_AUTH_OIDC_CLIENT_SECRET&quot;)</span><br><span class="line">AUTH_URI       = os.getenv(&quot;LAZYCAT_AUTH_OIDC_AUTH_URI&quot;)</span><br><span class="line">TOKEN_URI      = os.getenv(&quot;LAZYCAT_AUTH_OIDC_TOKEN_URI&quot;)</span><br><span class="line">USERINFO_URI   = os.getenv(&quot;LAZYCAT_AUTH_OIDC_USERINFO_URI&quot;)</span><br><span class="line">JWKS_URI       = os.getenv(&quot;OIDC_JWKS_URI&quot;, f&quot;https://&#123;LAZYCAT_BOX_DOMAIN&#125;/sys/oauth/keys&quot;)</span><br><span class="line">REDIRECT_URI   = os.getenv(&quot;OIDC_REDIRECT_URI&quot;, f&quot;https://&#123;LAZYCAT_APP_DOMAIN&#125;/callback&quot;)</span><br><span class="line"></span><br><span class="line">required = [CLIENT_ID, CLIENT_SECRET, AUTH_URI, TOKEN_URI, USERINFO_URI, JWKS_URI, REDIRECT_URI]</span><br><span class="line">if not all(required):</span><br><span class="line">    missing = [k for k, v in zip(</span><br><span class="line">        [&quot;OIDC_CLIENT_ID&quot;,&quot;OIDC_CLIENT_SECRET&quot;,&quot;OIDC_AUTH_URI&quot;,</span><br><span class="line">         &quot;OIDC_TOKEN_URI&quot;,&quot;OIDC_USERINFO_URI&quot;,&quot;OIDC_JWKS_URI&quot;,</span><br><span class="line">         &quot;OIDC_REDIRECT_URI&quot;], required) if not v]</span><br><span class="line">    raise RuntimeError(f&quot;缺少环境变量: &#123;&#x27;, &#x27;.join(missing)&#125;&quot;)</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.secret_key = &quot;a-very-secret-key&quot;</span><br><span class="line"></span><br><span class="line">oauth = OAuth(app)</span><br><span class="line">oidc = oauth.register(</span><br><span class="line">    name=&quot;casdoor&quot;,</span><br><span class="line">    client_id=CLIENT_ID,</span><br><span class="line">    client_secret=CLIENT_SECRET,</span><br><span class="line">    authorize_url=AUTH_URI,</span><br><span class="line">    access_token_url=TOKEN_URI,</span><br><span class="line">    userinfo_endpoint=USERINFO_URI,</span><br><span class="line">    jwks_uri=JWKS_URI,</span><br><span class="line">    client_kwargs=&#123;&quot;scope&quot;: &quot;openid profile email&quot;&#125;,</span><br><span class="line">    redirect_uri=REDIRECT_URI,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">@app.route(&quot;/&quot;)</span><br><span class="line">def index():</span><br><span class="line">    return render_template(&quot;index.html&quot;)</span><br><span class="line"></span><br><span class="line">@app.route(&quot;/login&quot;)</span><br><span class="line">def login():</span><br><span class="line">    nonce = secrets.token_urlsafe(16)</span><br><span class="line">    resp  = oidc.authorize_redirect(redirect_uri=REDIRECT_URI, nonce=nonce)</span><br><span class="line">    resp.set_cookie(&quot;oidc_nonce&quot;, nonce, max_age=300, httponly=True)</span><br><span class="line">    return resp</span><br><span class="line"></span><br><span class="line">@app.route(&quot;/callback&quot;)</span><br><span class="line">def callback():</span><br><span class="line">    token = oidc.authorize_access_token()</span><br><span class="line">    nonce = request.cookies.get(&quot;oidc_nonce&quot;)</span><br><span class="line">    claims = oidc.parse_id_token(token, nonce=nonce)</span><br><span class="line">    env_vars = sorted(os.environ.items())</span><br><span class="line"></span><br><span class="line">    return render_template(&quot;callback.html&quot;,</span><br><span class="line">                           access_token=token.get(&quot;access_token&quot;),</span><br><span class="line">                           id_token=token.get(&quot;id_token&quot;),</span><br><span class="line">                           user_info=claims,</span><br><span class="line">                           env_vars=env_vars)</span><br><span class="line"></span><br><span class="line">@app.route(&quot;/env&quot;, endpoint=&quot;show_env_html&quot;)</span><br><span class="line">def show_env_html():</span><br><span class="line">    env_vars = sorted(os.environ.items())</span><br><span class="line">    return render_template(&quot;env.html&quot;, env_vars=env_vars)</span><br><span class="line"></span><br><span class="line">if __name__ == &quot;__main__&quot;:</span><br><span class="line">    app.run(debug=True, port=5005)</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">懒猫微服 OIDC 单点登录开发教程（下篇），实战代码与调试。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="单点登录" scheme="https://blog.no-claw.com/tags/%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（六）:用 Openresty 做反向代理来解决跨域问题</title>
    <link href="https://blog.no-claw.com/posts/300338c6/"/>
    <id>https://blog.no-claw.com/posts/300338c6/</id>
    <published>2025-07-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>对于 Web 开发而言， 经常会遇到跨域问题。我们先来看一下什么是跨域问题：</p><p><strong>跨域问题（Cross-Origin）</strong>本质上是浏览器的<strong>同源策略（Same-Origin Policy, SOP）</strong>在发挥作用：</p><blockquote><p><strong>同源</strong>指“协议 + 域名（或 IP）+ 端口”三要素完全一致。只要三者有任何一个不同，就被视为<strong>跨域</strong>。</p></blockquote><h4 id="为什么浏览器要限制跨域？"><a href="#为什么浏览器要限制跨域？" class="headerlink" title="为什么浏览器要限制跨域？"></a>为什么浏览器要限制跨域？</h4><ul><li><strong>安全</strong>：阻止一个站点随意读取或修改另一个站点的敏感资源（如 Cookie、LocalStorage、DOM），避免 XSS、CSRF 等攻击链被无限放大。</li><li><strong>隔离</strong>：让不同网站在沙盒里各自运行，互不干扰。</li></ul><blockquote><p>同源策略只在<strong>浏览器环境</strong>生效；后端服务之间（如服务器 A 请求服务器 B）并没有 SOP 的限制。</p></blockquote> <span id="more"></span><table><thead><tr><th>场景</th><th>描述</th><th>是否受限</th></tr></thead><tbody><tr><td><code>fetch(&#39;https://api.foo.com&#39;)</code> 从 <code>https://www.bar.com</code> 发出</td><td>协议、域名不同</td><td><strong>受限</strong></td></tr><tr><td><code>http://example.com:3000</code> 调用 <code>http://example.com:4000</code></td><td>端口不同</td><td><strong>受限</strong></td></tr></tbody></table><blockquote><p>⚠️ <strong>用 Nginx&#x2F;OpenResty 并不会“自动”解决 CORS</strong>。</p><ul><li>你可以把前端请求代理到后端 API，使浏览器认为请求仍在同一域名下，达到“变同源”的效果。</li><li>或者直接在后端&#x2F;代理层加 CORS 响应头，两种方式都可以。</li></ul></blockquote><p>懒猫微服的上使用的是 OpenResty，这是一个功能齐全的 Web 应用服务器，它集成了标准的 nginx core、大量第三方 nginx 模块以及它们的大部分外部依赖项。所以和 Nginx 的配置文件是通用的。</p><p>以我之前比赛做的项目为例，这个是 Nginx 作为网关，监听 80 端口，然后反向代理到 Next.js 和 Flask。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">nginx:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">Dockerfile</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">next-app</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">backend</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">next-app:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">smart-shopping-app</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">next-frontend</span></span><br><span class="line">    <span class="attr">expose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;3000&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">NODE_ENV=production</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">NEXT_TELEMETRY_DISABLED=1</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">backend:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">shoppingassistant-backend</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">backend-app</span></span><br><span class="line">    <span class="attr">expose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5005&quot;</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><p>而 Nginx 的配置文件如写，做七成的转发，把根路径转发到前端，&#x2F;api 转发到后端。所以前端的 axios 请求等于访问的&#x2F;api 这个端点，所以可以规避跨域的问题。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name localhost;</span><br><span class="line"></span><br><span class="line">    location / &#123;</span><br><span class="line">        proxy_pass http://next-app:3000;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location /api &#123;</span><br><span class="line">        proxy_pass http://backend:5005;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其实对于懒猫微服的 OpenResty 的也是一样的，好处是不用自己再找 base image 了，直接把配置文件写进去就能用了。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">APP</span> <span class="string">Proxy</span> <span class="string">Test</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.app-proxy-test</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">app-proxy-test</span> <span class="comment">#</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">setup_script:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      cat &lt;&lt;&#x27;EOF&#x27; &gt; /etc/nginx/conf.d/default.conf</span></span><br><span class="line"><span class="string">      server &#123;</span></span><br><span class="line"><span class="string">          listen 80;</span></span><br><span class="line"><span class="string">          server_name _;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">          <span class="comment"># 静态页面</span></span><br><span class="line">          <span class="string">location</span> <span class="string">/</span> &#123;</span><br><span class="line">              <span class="string">root</span>   <span class="string">/usr/local/openresty/nginx/html;</span></span><br><span class="line">              <span class="string">index</span>  <span class="string">index.html</span> <span class="string">index.htm;</span></span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          <span class="comment"># API 反向代理，保留 /api 前缀</span></span><br><span class="line">          <span class="string">location</span> <span class="string">/api/</span> &#123;</span><br><span class="line">              <span class="string">proxy_pass</span> <span class="string">http://flask:5000/;</span></span><br><span class="line">              <span class="string">proxy_set_header</span> <span class="string">Host</span> <span class="string">$host;</span></span><br><span class="line">              <span class="string">proxy_set_header</span> <span class="string">X-Real-IP</span> <span class="string">$remote_addr;</span></span><br><span class="line">              <span class="string">proxy_set_header</span> <span class="string">X-Forwarded-For</span> <span class="string">$proxy_add_x_forwarded_for;</span></span><br><span class="line">          &#125;</span><br><span class="line">      <span class="string">&#125;</span></span><br><span class="line">      <span class="string">EOF</span></span><br><span class="line">  <span class="attr">flask:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c</span></span><br></pre></td></tr></table></figure><p>flask 的 image 是我之前做的一个镜像仓库<code>docker run -p 5005:5000 cloudsmithy/flask-demo:latest</code></p><p>然后通过<code>lzc-cli appstore copy-image cloudsmithy/flask-demo</code> 把镜像换成懒猫的镜像，<code>registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c</code></p><p>通过<code>setup_script</code>传入和 Nginx 类似的配置文件，原理是替换 docker image 本身的 entrypoint&#x2F;command 参数。</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"># 打包成 LPK</span></span><br><span class="line">lzc-cli project build -o release.lpk</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在线安装 LPK</span></span><br><span class="line">lzc-cli app install release.lpk</span><br></pre></td></tr></table></figure><p>我们可以看到这个是 OpenResty 的主页，然后访问<code>https://app-proxy-test.micro.heiyu.space/api/</code> 也能返回 Flask 容器“Hello from multi-arch Flask Docker in production mode!”。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250704120859972.png" alt="image-20250704120859972"></p><p>如果你想把根路由直接代理到容器，也可以使用这个办法。这个一般是用来做反向代理来访问内网的服务，即使是 http 也没有关系。这个环境变量应该是懒猫魔改的快捷方式。不要和配置文件混用。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">APP</span> <span class="string">Proxy</span> <span class="string">Test</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.app-proxy-test</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">app-proxy-test</span> <span class="comment">#</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">UPSTREAM=&quot;http://flask:5000&quot;</span></span><br><span class="line">  <span class="attr">flask:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c</span></span><br></pre></td></tr></table></figure><p>有时候还会加上<code>BASIC_AUTH_HEADER</code>这个字段来让 nginx&#x2F;Openresty 自动填写密码，除了你的容器以外，代理外边服务也行。</p><p>其实用<code>echo -n &quot;user:password&quot; | base64</code>,的数据来填充<code>BASIC_AUTH_HEADER</code>“Basic <base64> “</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">APP</span> <span class="string">Proxy</span> <span class="string">Test</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.app-proxy-test</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">app-proxy-test</span> <span class="comment">#</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">UPSTREAM=&quot;https://xxx:9200/&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">BASIC_AUTH_HEADER=&quot;Basic</span> <span class="string">YWRt46YzssdsfFlOk=&quot;</span></span><br><span class="line">  <span class="attr">flask:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c</span></span><br></pre></td></tr></table></figure><p>另外也支持多域名解析，这个在传统的线下机房比较常见，而云上基本上还是 7 层基于路由转发，比如第一种，我也更加熟悉第一种。</p><p>这个其实就是加了一个 secondary_domains 的字段，然后把后端单独暴露出来了。这样就子域名就可以访问后端。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="attr">lzc-sdk-version:</span> <span class="string">&quot;0.1&quot;</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">APP</span> <span class="string">Proxy</span> <span class="string">Test</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.app-proxy-test</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">app-proxy-test</span> <span class="comment"># 应用列表里默认打开的域名</span></span><br><span class="line">  <span class="attr">secondary_domains:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">flask</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app-proxy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/app-proxy:v0.1.0</span></span><br><span class="line">    <span class="attr">setup_script:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      cat &lt;&lt;&#x27;EOF&#x27; &gt; /etc/nginx/conf.d/default.conf</span></span><br><span class="line"><span class="string">      server &#123;</span></span><br><span class="line"><span class="string">         server_name  app-proxy-test.*;</span></span><br><span class="line"><span class="string">         location / &#123;</span></span><br><span class="line"><span class="string">            root   /usr/local/openresty/nginx/html;</span></span><br><span class="line"><span class="string">            index  index.html index.htm;</span></span><br><span class="line"><span class="string">         &#125;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="string">server</span> &#123;</span><br><span class="line">         <span class="string">server_name</span>  <span class="string">flask.*;</span></span><br><span class="line"></span><br><span class="line">         <span class="string">location</span> <span class="string">/</span> &#123;</span><br><span class="line">            <span class="string">proxy_pass</span> <span class="string">http://flask:5000;</span></span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="string">EOF</span></span><br><span class="line">  <span class="attr">flask:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">介绍如何在懒猫微服中使用 Openresty 反向代理解决前后端跨域问题</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="代理" scheme="https://blog.no-claw.com/tags/%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>使用 Zilliz Cloud 快速体验 Milvus</title>
    <link href="https://blog.no-claw.com/posts/938aef4d/"/>
    <id>https://blog.no-claw.com/posts/938aef4d/</id>
    <published>2025-07-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在学习 Milvus 向量数据库时，除了本地 Milvus Lite、单机版 Milvus Standalone 或 Milvus on K8s 之外，还可以选择 <strong>Zilliz Cloud</strong> —— 一种无需部署服务器、零成本上手的托管方案。下面将演示如何申请 Zilliz Cloud 中国区免费套餐并运行官方示例代码。</p><h3 id="注册并创建免费集群"><a href="#注册并创建免费集群" class="headerlink" title="注册并创建免费集群"></a>注册并创建免费集群</h3><ol><li><p>打开官网</p><ul><li>国内站点：<a href="https://zilliz.com.cn/">https://zilliz.com.cn/</a></li><li>海外站点：<a href="https://zilliz.com/">https://zilliz.com/</a></li></ul></li></ol><p>我们本次实验使用的是国内站点，部署在阿里云，目前可以免费使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a508309b90c710ffd1d9e998626c47f9.png" alt="a508309b90c710ffd1d9e998626c47f9"></p><span id="more"></span><ol start="2"><li><p>选择 <strong>手机号码</strong> 或 <strong>邮箱</strong> 登录&#x2F;注册。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626212403361.png" alt="image-20250626212403361"></p></li><li><p>进入控制台首页后，点击 <strong>Create Cluster</strong> 按钮。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d712d4c4fd8f2546dab4426c68bf806f.png" alt="起始页面"></p></li><li><p>在弹窗中选择 <strong>Free Tier</strong>（免费套餐），数据中心默认为 <strong>阿里云 · 杭州</strong>。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/25626960ddcd05d12117aff485eb2487.png" alt="选择免费集群"></p></li><li><p>等待几分钟，集群创建完成后会显示 <strong>Endpoint URI、API Token、Cluster ID</strong> 等信息，请妥善保存。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/3d606a5a0a797d7332bbb3efd86fd8c4.png" alt="集群信息"></p></li><li><p>运行中</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/395f581c7dbbeb8b4940afa0bcab025a.png" alt="395f581c7dbbeb8b4940afa0bcab025a"></p><hr><h3 id="二、连接"><a href="#二、连接" class="headerlink" title="二、连接"></a>二、连接</h3><p>安装 milvus-cli：</p><figure class="highlight plaintext"><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">pip install uv</span><br><span class="line">uv pip install milvus-cli</span><br></pre></td></tr></table></figure><p>终端执行 milvus_cli，进入交互式 CLI</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">milvus_cli</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"> | |  | | | |\ V /| |_| \__ \ | |___| |___ | |</span><br><span class="line"> |_|  |_|_|_| \_/  \__,_|___/  \____|_____|___|</span><br><span class="line"></span><br><span class="line">Milvus cli version: 1.0.2</span><br><span class="line">Pymilvus version: 2.5.3</span><br><span class="line"></span><br><span class="line">Learn more: https://github.com/zilliztech/milvus_cli.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">milvus_cli &gt; connect -uri https://in03-d7b5690fee7bcbf.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn -t 88b738ee492b2ad88d69c166ee587825d546b049dab3a5d8767733a636efec52a62e96b283ab90c24146d5a311696dacd9499fc1</span><br><span class="line">Connect Milvus successfully.</span><br><span class="line">+---------+---------+</span><br><span class="line">| Address |         |</span><br><span class="line">|  Alias  | default |</span><br><span class="line">+---------+---------+</span><br><span class="line">milvus_cli &gt; list databases</span><br><span class="line">+--------------------+</span><br><span class="line">|      db_name       |</span><br><span class="line">+--------------------+</span><br><span class="line">| db_d7b5690fee7bcbf |</span><br><span class="line">+--------------------+</span><br></pre></td></tr></table></figure><h3 id="创建虚拟环境（缺少-3-12-时-uv-会自动下载）"><a href="#创建虚拟环境（缺少-3-12-时-uv-会自动下载）" class="headerlink" title="创建虚拟环境（缺少 3.12 时 uv 会自动下载）"></a>创建虚拟环境（缺少 3.12 时 uv 会自动下载）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">uv venv milvus-py --python 3.12</span><br><span class="line"></span><br><span class="line"># 激活环境</span><br><span class="line">source milvus-py/bin/activate      # macOS / Linux</span><br><span class="line"># .\milvus-py\Scripts\activate      # Windows PowerShell</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果你使用的是 conda 也可以：</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">conda create -n milvus-py python==3.12 -y</span><br><span class="line">conda activate milvus-py</span><br></pre></td></tr></table></figure><ol><li><strong>克隆仓库</strong></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/zilliztech/cloud-vectordb-examples.git</span><br></pre></td></tr></table></figure><ol><li><strong>安装 PyMilvus</strong></li></ol><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">pip3 install pymilvus==2.5.3</span><br></pre></td></tr></table></figure><ol><li><strong>进入 Python 示例目录</strong></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> cloud-vectordb-examples/python</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>需要注意的是，在开源版本的 Milvus 中，端口号是 9530 &#x2F; 9091 ，而在 Zilliz cloud 上，端口上是 443.</p><hr><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> configparser</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pymilvus <span class="keyword">import</span> MilvusClient</span><br><span class="line"><span class="keyword">from</span> pymilvus <span class="keyword">import</span> DataType</span><br><span class="line"></span><br><span class="line">cfp = configparser.RawConfigParser()</span><br><span class="line">cfp.read(<span class="string">&#x27;config.ini&#x27;</span>)</span><br><span class="line">milvus_uri = cfp.get(<span class="string">&#x27;example&#x27;</span>, <span class="string">&#x27;uri&#x27;</span>)</span><br><span class="line">token = cfp.get(<span class="string">&#x27;example&#x27;</span>, <span class="string">&#x27;token&#x27;</span>)</span><br><span class="line"></span><br><span class="line">milvus_client = MilvusClient(uri=milvus_uri, token=token)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Connected to DB: <span class="subst">&#123;milvus_uri&#125;</span> successfully&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Check if the collection exists</span></span><br><span class="line">collection_name = <span class="string">&quot;book&quot;</span></span><br><span class="line">check_collection = milvus_client.has_collection(collection_name)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> check_collection:</span><br><span class="line">    milvus_client.drop_collection(collection_name)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Dropped the existing collection <span class="subst">&#123;collection_name&#125;</span> successfully&quot;</span>)</span><br><span class="line"></span><br><span class="line">dim = <span class="number">64</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Start to create the collection schema&quot;</span>)</span><br><span class="line">schema = milvus_client.create_schema()</span><br><span class="line">schema.add_field(<span class="string">&quot;book_id&quot;</span>, DataType.INT64, is_primary=<span class="literal">True</span>, description=<span class="string">&quot;customized primary id&quot;</span>)</span><br><span class="line">schema.add_field(<span class="string">&quot;word_count&quot;</span>, DataType.INT64, description=<span class="string">&quot;word count&quot;</span>)</span><br><span class="line">schema.add_field(<span class="string">&quot;book_intro&quot;</span>, DataType.FLOAT_VECTOR, dim=dim, description=<span class="string">&quot;book introduction&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Start to prepare index parameters with default AUTOINDEX&quot;</span>)</span><br><span class="line">index_params = milvus_client.prepare_index_params()</span><br><span class="line">index_params.add_index(<span class="string">&quot;book_intro&quot;</span>, metric_type=<span class="string">&quot;L2&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Start to create example collection: <span class="subst">&#123;collection_name&#125;</span>&quot;</span>)</span><br><span class="line"><span class="comment"># create collection with the above schema and index parameters, and then load automatically</span></span><br><span class="line">milvus_client.create_collection(collection_name, schema=schema, index_params=index_params)</span><br><span class="line">collection_property = milvus_client.describe_collection(collection_name)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Collection details: %s&quot;</span> % collection_property)</span><br><span class="line"></span><br><span class="line"><span class="comment"># insert data with customized ids</span></span><br><span class="line">nb = <span class="number">1000</span></span><br><span class="line">insert_rounds = <span class="number">2</span></span><br><span class="line">start = <span class="number">0</span>           <span class="comment"># first primary key id</span></span><br><span class="line">total_rt = <span class="number">0</span>        <span class="comment"># total response time for inert</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Start to insert <span class="subst">&#123;nb*insert_rounds&#125;</span> entities into example collection: <span class="subst">&#123;collection_name&#125;</span>&quot;</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(insert_rounds):</span><br><span class="line">    vector = [random.random() <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(dim)]</span><br><span class="line">    rows = [&#123;<span class="string">&quot;book_id&quot;</span>: i, <span class="string">&quot;word_count&quot;</span>: random.randint(<span class="number">1</span>, <span class="number">100</span>), <span class="string">&quot;book_intro&quot;</span>: vector&#125; <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(start, start+nb)]</span><br><span class="line">    t0 = time.time()</span><br><span class="line">    milvus_client.insert(collection_name, rows)</span><br><span class="line">    ins_rt = time.time() - t0</span><br><span class="line">    start += nb</span><br><span class="line">    total_rt += ins_rt</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Insert completed in <span class="subst">&#123;<span class="built_in">round</span>(total_rt,<span class="number">4</span>)&#125;</span> seconds&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Start to flush&quot;</span>)</span><br><span class="line">start_flush = time.time()</span><br><span class="line">milvus_client.flush(collection_name)</span><br><span class="line">end_flush = time.time()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Flush completed in <span class="subst">&#123;<span class="built_in">round</span>(end_flush - start_flush, <span class="number">4</span>)&#125;</span> seconds&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># search</span></span><br><span class="line">nq = <span class="number">3</span></span><br><span class="line">search_params = &#123;<span class="string">&quot;metric_type&quot;</span>: <span class="string">&quot;L2&quot;</span>,  <span class="string">&quot;params&quot;</span>: &#123;<span class="string">&quot;level&quot;</span>: <span class="number">2</span>&#125;&#125;</span><br><span class="line">limit = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line">   search_vectors = [[random.random() <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(dim)] <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(nq)]</span><br><span class="line">   t0 = time.time()</span><br><span class="line">   results = milvus_client.search(collection_name,</span><br><span class="line">                                  data=search_vectors,</span><br><span class="line">                                  limit=limit,</span><br><span class="line">                                  search_params=search_params,</span><br><span class="line">                                  anns_field=<span class="string">&quot;book_intro&quot;</span>)</span><br><span class="line">   t1 = time.time()</span><br><span class="line">   <span class="keyword">assert</span> <span class="built_in">len</span>(results) == nq</span><br><span class="line">   <span class="keyword">assert</span> <span class="built_in">len</span>(results[<span class="number">0</span>]) == limit</span><br><span class="line">   <span class="built_in">print</span>(<span class="string">f&quot;Search <span class="subst">&#123;i&#125;</span> results: <span class="subst">&#123;results&#125;</span>&quot;</span>)</span><br><span class="line">   <span class="built_in">print</span>(<span class="string">f&quot;Search <span class="subst">&#123;i&#125;</span> latency: <span class="subst">&#123;<span class="built_in">round</span>(t1-t0, <span class="number">4</span>)&#125;</span> seconds&quot;</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="配置连接信息"><a href="#配置连接信息" class="headerlink" title="配置连接信息"></a>配置连接信息</h3><p>在 <code>config.ini</code> 中填入你的集群信息（务必保持格式），⚠️ 切勿把 API Key 提交到公开仓库。</p><figure class="highlight ini"><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="attr">uri</span> = https://&lt;your-endpoint&gt;</span><br><span class="line"><span class="attr">token</span> = &lt;your-api-key&gt;</span><br></pre></td></tr></table></figure><hr><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">python3 hello_zilliz_vectordb.py</span><br></pre></td></tr></table></figure><p>运行后可见类似输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">Connected to DB: https://in03-d7b5690fee7bcbf.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn successfully</span><br><span class="line">Start to create the collection schema</span><br><span class="line">Start to prepare index parameters with default AUTOINDEX</span><br><span class="line">Start to create example collection: book</span><br><span class="line">Collection details: &#123;&#x27;collection_name&#x27;: &#x27;book&#x27;, &#x27;auto_id&#x27;: False, &#x27;num_shards&#x27;: 1, &#x27;description&#x27;: &#x27;&#x27;, &#x27;fields&#x27;: [&#123;&#x27;field_id&#x27;: 100, &#x27;name&#x27;: &#x27;book_id&#x27;, &#x27;description&#x27;: &#x27;customized primary id&#x27;, &#x27;type&#x27;: &lt;DataType.INT64: 5&gt;, &#x27;params&#x27;: &#123;&#125;, &#x27;is_primary&#x27;: True&#125;, &#123;&#x27;field_id&#x27;: 101, &#x27;name&#x27;: &#x27;word_count&#x27;, &#x27;description&#x27;: &#x27;word count&#x27;, &#x27;type&#x27;: &lt;DataType.INT64: 5&gt;, &#x27;params&#x27;: &#123;&#125;&#125;, &#123;&#x27;field_id&#x27;: 102, &#x27;name&#x27;: &#x27;book_intro&#x27;, &#x27;description&#x27;: &#x27;book introduction&#x27;, &#x27;type&#x27;: &lt;DataType.FLOAT_VECTOR: 101&gt;, &#x27;params&#x27;: &#123;&#x27;dim&#x27;: 64&#125;&#125;], &#x27;functions&#x27;: [], &#x27;aliases&#x27;: [], &#x27;collection_id&#x27;: 457861707686138665, &#x27;consistency_level&#x27;: 2, &#x27;properties&#x27;: &#123;&#125;, &#x27;num_partitions&#x27;: 1, &#x27;enable_dynamic_field&#x27;: False&#125;</span><br><span class="line">Start to insert 2000 entities into example collection: book</span><br><span class="line">Insert completed in 0.692 seconds</span><br><span class="line">Start to flush</span><br><span class="line">Flush completed in 3.0984 seconds</span><br><span class="line">Search 0 results: data: [&quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 10.547525405883789, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 10.547525405883789, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 8.913854598999023, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 8.913854598999023, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 1000, &#x27;distance&#x27;: 9.11572551727295, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1001, &#x27;distance&#x27;: 9.11572551727295, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;] , extra_info: &#123;&#x27;cost&#x27;: 6&#125;</span><br><span class="line">Search 0 latency: 3.4933 seconds</span><br><span class="line">Search 1 results: data: [&quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 8.898500442504883, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 8.898500442504883, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 9.7216157913208, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 9.7216157913208, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 1000, &#x27;distance&#x27;: 8.997819900512695, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1001, &#x27;distance&#x27;: 8.997819900512695, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;] , extra_info: &#123;&#x27;cost&#x27;: 6&#125;</span><br><span class="line">Search 1 latency: 0.099 seconds</span><br><span class="line">Search 2 results: data: [&quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 7.597465515136719, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 7.597465515136719, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 9.255533218383789, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 9.255533218383789, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 9.471370697021484, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 9.471370697021484, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;] , extra_info: &#123;&#x27;cost&#x27;: 6&#125;</span><br><span class="line">Search 2 latency: 0.0677 seconds</span><br><span class="line">Search 3 results: data: [&quot;[&#123;&#x27;id&#x27;: 1000, &#x27;distance&#x27;: 8.828998565673828, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1001, &#x27;distance&#x27;: 8.828998565673828, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 1000, &#x27;distance&#x27;: 8.66336441040039, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1001, &#x27;distance&#x27;: 8.66336441040039, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 9.222965240478516, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 9.222965240478516, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;] , extra_info: &#123;&#x27;cost&#x27;: 6&#125;</span><br><span class="line">Search 3 latency: 0.0722 seconds</span><br><span class="line">Search 4 results: data: [&quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 9.342487335205078, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 9.342487335205078, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 6.45243501663208, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 6.45243501663208, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;, &quot;[&#123;&#x27;id&#x27;: 0, &#x27;distance&#x27;: 8.369773864746094, &#x27;entity&#x27;: &#123;&#125;&#125;, &#123;&#x27;id&#x27;: 1, &#x27;distance&#x27;: 8.369773864746094, &#x27;entity&#x27;: &#123;&#125;&#125;]&quot;] , extra_info: &#123;&#x27;cost&#x27;: 6&#125;</span><br><span class="line">Search 4 latency: 0.0687 seconds</span><br></pre></td></tr></table></figure><p>如果控制台显示如上日志，即表明已成功连接集群、创建 collection 并完成简单的向量检索。</p><hr><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626211850476.png" alt="image-20250626211850476"></p><hr><p>然后我们就可以通过控制台来查看这个新建的索引和数据了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702113618025.png" alt="image-20250702113618025"></p><p>除此之外，zilliz 还提供了 restapi ，这样我们就可以通过请求 HTTP 来完成数据检索了。</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">curl --request POST \</span><br><span class="line">  --url https://in03-d7b5690fee7bcbf.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn/v2/vectordb/collections/list \</span><br><span class="line">  --header <span class="string">&#x27;accept: application/json&#x27;</span> \</span><br><span class="line">  --header <span class="string">&#x27;authorization: Bearer &lt;api-key&gt;&#x27;</span> \</span><br><span class="line">  --data <span class="string">&#x27;&#123;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>Python 版本的如下，需要我们把 api-key 作为 bear token 传到请求头里。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;https://in03-d7b5690fee7bcbf.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn/v2/vectordb/collections/list&quot;</span></span><br><span class="line"></span><br><span class="line">payload = <span class="string">&quot;&#123;&#125;&quot;</span></span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">&#x27;Bearer &lt;api-key&gt;&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;POST&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br></pre></td></tr></table></figure><p>同样我们再 Postman 上也可以进行测试，需要注意的是，即使请求体是空的，那么也需要使用 {} 来占位。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702112141165.png" alt="image-20250702112141165"></p><p>在左侧的 api-playground 中，我们可以看到更多的 API 操作，同时还可以直接在浏览器上发送请求。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250702113040640.png" alt="image-20250702113040640"></p><p>通过 Zilliz Cloud，我们可以在几分钟内获得一套托管版 Milvus 服务，免去本地运维与资源成本，非常适合作为学习、原型开发或小型应用的向量数据库后端。祝大家玩得开心！</p>]]></content>
    
    
    <summary type="html">零部署体验 Milvus 向量数据库，注册 Zilliz Cloud 免费套餐并运行官方示例代码。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="向量数据库" scheme="https://blog.no-claw.com/tags/%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>记年初的武汉游记</title>
    <link href="https://blog.no-claw.com/posts/cc35fe6/"/>
    <id>https://blog.no-claw.com/posts/cc35fe6/</id>
    <published>2025-07-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>头一天在重庆机场过的夜，到达江汉路步行街已经快中午了。在附近的来菜吃的藕汤，在北京就很爱吃清水亭，有藕的 N 多种做法，那么来到湖北不可不吃。</p><p>主要还是特意找问当地的小伙伴要了攻略：</p><blockquote><p>推荐的专门吃东西的几条街是玫瑰街、粮道街、万松园、吉庆街这些。早餐小吃比较丰富，武汉的早餐文化特别浓厚，早餐种类特别丰富，很多碳水炸弹。</p><p>正餐可以考虑 来菜，是湖北菜，藕汤是湖北菜最具特色的。文章中下面的信息还是可靠的。</p><p>午餐：这个季节湖北的藕汤已经上市，基本上随便一家都很好喝，一定要选筒子骨藕汤，9 孔粉藕炖的那种。怕踩坑可以选一些连锁的，出品有保障，如刘胖子、老村长、艳阳天，不怕排队也可以去夏氏砂锅。</p><blockquote><p>武汉魅族魅友家：<a href="https://weibo.com/2709494027/4973440665388967">https://weibo.com/2709494027/4973440665388967</a> 这是小伙伴自己总结的</p></blockquote><p>武汉推崇藕汤一定要用粉藕，有炒菜的那种，也可以点干煸藕丝，那就是不同的味道了</p></blockquote><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/201b49619f36a1d8c8e0a2270f06ab55.jpg" alt="201b49619f36a1d8c8e0a2270f06ab55"></p><p>江汉路的风景不错，虽然是老城区，住宿条件差强人意。但总归风景和视野还是不错的，可以远眺长江，夜景也很美，楼下就是江汉路步行街。</p><blockquote><p>携程上酒店骂声一片，都是吐槽住宿添加太差的。如果介意的话可以选择住对面的武昌。去的时候爆满，其他地区送早餐给升级房间的桔子水晶都没有增值服务了。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7516977ab7e5461589524d578417cda4.jpg" alt="7516977ab7e5461589524d578417cda4"></p><p>早上的江汉路是没什么人的，不过晚上到 12 点都很热闹。每天都是拖着走废的腿一瘸一拐的走回去的，青城山崴的脚，再经历重庆摧残，最后在武汉彻底残了。</p><p>武汉没有真正的市中心的概念，基本都是一个片区一个片区这样的。武汉本来就是三个城市合并来的，武昌重政治、汉口重经济、汉阳重工业。所以景区或者玩的地方相对也都比较分散。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b8568bf78a3965faacbfa10417023350.jpg" alt="b8568bf78a3965faacbfa10417023350"></p><p>早餐可以选严老幺的三鲜豆皮和黑色麻将的热干面。那个新出的三鲜面就不要点了，毕竟不是苏州，三鲜面很难吃！！！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/130d84f81d31f7cc57fc535cdfcecb27.jpg" alt="130d84f81d31f7cc57fc535cdfcecb27"></p><p>现做的三鲜豆皮，很多人排队，一定要现做现吃才好，这样外壳才是脆的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/21157cd9bac75924716d70921cf13324.jpg" alt="21157cd9bac75924716d70921cf13324"></p><p>山海关路来过个不早的早，每个人拿一个小板凳，5 块钱的热干面，三块钱的藕粉汤，基本早上就吃饱了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/918caab9550fd67c77a23bcba9fe92db.jpg" alt="918caab9550fd67c77a23bcba9fe92db"></p><p>第二天还是骑车到山海关路继续喝了藕汤，这家叫做金三角吊子煨汤。20 块钱一碗，有排骨有莲藕，总体来说比来菜吃的爽一点吧。算是在武汉吃到的第一个拉丝的藕。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0e81da7c263f7121c2edf8a7edab12ec.jpg" alt="0e81da7c263f7121c2edf8a7edab12ec"></p><p>山海关路也吃了三鲜豆皮，和毛氏汽水包。不过是在吃不下了，据说这家牛肉面也不错。嗯，来自襄阳。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cf93a9047c3a84453fa6bb337358b546.jpg" alt="cf93a9047c3a84453fa6bb337358b546"></p><p>这个是小伙伴的旅游攻略：</p><blockquote><p>1、黄鹤楼，现在应该在准备春晚分会场，闭园了<br>2、东湖有空可以看看，东湖绿道、或者磨山风景区也可以锻炼一下，都在东湖片区，哈哈<br>3、湖北省省博物馆离东湖也很近。越王勾践剑、曾侯乙编钟是镇馆之宝。<br>4、武汉大学凌波门不用去了，日出日落这个季节都不太合适。武大校园里面还行，建筑风格也很不错。<br>5、宝通寺、古德寺、归元寺是寺庙，长春观是道观。<br>6、黎黄陂路、昙华林是文艺青年打卡地。黎黄陂路有很多近代风格的建筑，适合拍照。昙华林有一些文创小礼品吧。<br>7、江汉路、楚河汉街、武商梦时代是商圈。江汉路建筑风格是近代租界风格，距离江滩近。楚河汉街有新开的 SKP，奢侈品应该相对多一些。武商梦时代是亚洲最大的单体商场。</p></blockquote><blockquote><p>武汉魅族魅友家：<a href="https://weibo.com/2709494027/4973440665388967">https://weibo.com/2709494027/4973440665388967</a></p></blockquote><p>武汉博物馆真的很大，需要留一天的时间来看。越王勾践剑需要排队很久。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/DJI_20250112122305_0393_D.JPG" alt="DJI_20250112122305_0393_D"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a0a512bd9715878984048cbd60b1fa55.jpg" alt="a0a512bd9715878984048cbd60b1fa55"></p><p>沿着东湖骑车，慢慢就天黑了，然后发现自己在湖中央凌乱。武大不让进，所以一路都在怀疑自己，一个人在外地，大晚上在人少的地方骑车干嘛。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9baa45370097bb1d3d5b63ff546b6551.jpg" alt="9baa45370097bb1d3d5b63ff546b6551"></p><p>武汉玩的地方，可以考虑下湖北省博物馆，越王勾践剑和曾侯乙编钟（有时候会有编钟表演）。</p><p>东湖也还不错，比较大，春天有樱园，也可以在那边露营。这个季节不是特别推荐，风应该会比较大</p><p>江汉路步行街是商圈，仅临武汉江滩。江滩晚上可以看对岸的楼宇和大桥上的灯光表演，比较类似上次在杭州游船的风景</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5c5e187b188dc8a92f05a31f52e406b4.jpg" alt="5c5e187b188dc8a92f05a31f52e406b4"></p><p>从东湖离开已经是这个点了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/DJI_20250113180602_0522_D.JPG" alt="DJI_20250113180602_0522_D"></p><p>在楚河汉街，基本是本老武汉强行拉倒店里。说没有预制菜，藕汤保拉丝。于是大众点评 L6 送了一个价值 60+的藕汤，然后点了一个干煸藕丝。算是在武汉吃的性价比最高的一顿。果然，大众点评高等级在哪里都吃香。不过后来朋友去，据说是是不再送了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6fe23d79915042942958b4151d46c151.jpg"></p><p>干煸藕丝很酥脆，虽然说干炸的牛肉不多，但是味道简直无可挑剔。不过一个人吃俩大菜是在是太撑了，还是得找个女朋友一起才好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5c2c9e7a750fd7c700a8e266ffbbf9d4.jpg" alt="5c2c9e7a750fd7c700a8e266ffbbf9d4"></p><p>第二天又去了梨园。下午的东湖，美的不可方物。（没坐船，来回要七八十，而且要跟着船马上回来）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1fd49b438fdb3566e4d4ed8e26025101.jpg" alt="1fd49b438fdb3566e4d4ed8e26025101"></p><p>美的像一幅画。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c2ca64c6ed0be78720ab8bfc8b943847.jpg" alt="c2ca64c6ed0be78720ab8bfc8b943847"></p><p>晚上去的夏氏砂锅，因为谈季加上人少，所以基本没排队。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f3012d41985b79df5be270c5c8e4beb2.jpg" alt="f3012d41985b79df5be270c5c8e4beb2"></p><p>冬天点着炉火，喝着热气腾腾的藕汤，从上暖到下，还有这个藕是真拉丝呀～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7c816a7675cc82050067ed38f75c2244.jpg" alt="7c816a7675cc82050067ed38f75c2244"></p><p>徒步横跨长江大桥，属于又菜又还玩，危险指数 4 颗星，车在走桥在晃，小哥自行车在眼前飘过。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7c2788dfd6818c79eabb230318e27ef7.jpg" alt="7c2788dfd6818c79eabb230318e27ef7"></p><p>用 Apple Watch 记录下这个过程。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2dcf4cb81e2e41f2092d161693ee66e6.jpg" alt="2dcf4cb81e2e41f2092d161693ee66e6"></p><p>春晚彩排的原因，黄鹤楼进不去。只能在外边听见 1234567 的敲鼓声音，不过很多人也说黄鹤楼在外边看看就好了。</p><blockquote><p>文人的名楼情节，大概是拜崔颢的诗词所赐了。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/DJI_20250113200746_0529_D.JPG" alt="DJI_20250113200746_0529_D"></p><p>最后一天来参观国民政府办公厅，现在的南阳大楼被改成了酒店还是饭店一类的场所，只有三楼对外开放。电视剧里的政府办公厅和这个很像。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/89dd0edfb62f8bba4248bbe5c4fec999.jpg" alt="89dd0edfb62f8bba4248bbe5c4fec999"></p><p>请看大图 VCR：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2fd07bb791a4ff087c1958946045b86d.jpg" alt="2fd07bb791a4ff087c1958946045b86d"></p><p>还有江汉关博物馆的讲诉曾经的故事，近代的条约，租借等等。。。。</p><p>江汉路以及黎黄坡路的租借都改成了经典或者银行，但是我们仍然可以看到过去的岁月。</p><p>那，南京得什么样啊？</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/42b26aad3399283cd52922e826d9607d.jpg" alt="42b26aad3399283cd52922e826d9607d"></p><p>临走之前吃了，王记牛杂，生滚牛肉热干面，真心不错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5f9ab15dd51edde9e139fd1b1ea077e6.jpg" alt="5f9ab15dd51edde9e139fd1b1ea077e6"></p><p>回去写了三天四藕小记：</p><blockquote><p>如果用藕来比作爱情，来菜是初相识，幻想最初的美好，该是粉糯入口即化如亲吻红唇，而非迎合大众变节故作搔首，又道是重口难调。再三思虑下，你终究不是我的头牌。出来乍到幻想要轰轰烈烈的爱情，一次次的修改已不愿再等。</p><p>老武汉是路边的艳遇，你听我诉着苦， 又抚着上次的伤口，于是急切的来一场试探性的邀约，再经历试探，喧闹，繁华过后，你把新唤作老。清炖，油炸样样拉丝。本已无可挑剔，可惜你在灯红酒绿，不能常常如愿。</p><p>小吃街是日常，简单不精致，朴华粗糙，藕形状不一，大口啃着才舒服，细小的渣碎弃之不要。调侃着来往的路人以及同桌的游客，看着提高嗓门吆喝的店家。同时还能大快朵颐享受着放松而自在的瞬间，虽无钟鸣鼎食，处处诉说着平平淡淡。</p><p>夏氏的砂锅是醇厚的，就着炉火，半暗的灯光下，乘上一晚热气腾腾的汤，软糯的藕配着油花花的汤，可以散去一整天的疲惫，这家拉丝也是最多的，点着炉火，就好像无论多晚多累总归有人懂你，等你。汤一定要微烫入口，否则要重新加热到沸腾才行，锅气才是真实的人间烟火。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/609cab7b0092c2f91ee18062e5f1d10f.jpg" alt="609cab7b0092c2f91ee18062e5f1d10f"></p>]]></content>
    
    
    <summary type="html">年初武汉之行，江汉路步行街、藕汤美食攻略，附当地朋友推荐的湖北菜指南。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="游记" scheme="https://blog.no-claw.com/tags/%E6%B8%B8%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Gemini-cli 踩坑机：国内环境 , 用过 Google Cloud 如何设置？</title>
    <link href="https://blog.no-claw.com/posts/8d2ea5f5/"/>
    <id>https://blog.no-claw.com/posts/8d2ea5f5/</id>
    <published>2025-07-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Gemini 也发布了类似的 AI 编程产品 Gemini Cli - 开源命令行 AI 工具</p><p>而且个人谷歌账号登录就能免费用！</p><p>免费额度为每分钟 60 次请求、每天 1000 次请求，是业内最高的免费额度，几乎不会遇到限制。</p><ul><li>支持 Google 搜索实时联网，为模型提供外部上下文。</li><li>支持 MCP 和扩展，便于功能拓展。</li><li>可自定义提示词和指令，适应个人或团队工作流。</li><li>可在脚本中非交互式调用，实现自动化和集成。</li></ul><span id="more"></span><h3 id="安装-gemini-cli"><a href="#安装-gemini-cli" class="headerlink" title="安装 gemini-cli"></a>安装 gemini-cli</h3><p>先来安装 gemini-cli，其实就是一个 NPM 包。</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">npm install -g @google/gemini-cli</span><br><span class="line">gemini</span><br></pre></td></tr></table></figure><p>如果不出意外的话，执行之后会闪退。网上说需要设置 TUN 代理，甚至连命令行 export 环境变量也不行。</p><hr><h3 id="登录-Google-SSO-后仍无法使用"><a href="#登录-Google-SSO-后仍无法使用" class="headerlink" title="登录 Google SSO 后仍无法使用"></a>登录 Google SSO 后仍无法使用</h3><p>然后登录 Google SSO 验证，页面会显示 Gemini Code Assist 已获得访问您账号的授权。但是其实还是不行。我们继续看。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/de7757ea56e0bf2d668093ee788b240a.png" alt="de7757ea56e0bf2d668093ee788b240a"></p><p>命令行还是会得到这个报错：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626075949247.png" alt="image-20250626075949247"></p><hr><h3 id="设置-GOOGLE-CLOUD-PROJECT-环境变量"><a href="#设置-GOOGLE-CLOUD-PROJECT-环境变量" class="headerlink" title="设置 GOOGLE_CLOUD_PROJECT 环境变量"></a>设置 GOOGLE_CLOUD_PROJECT 环境变量</h3><p>网上基本有这个教程：</p><blockquote><p>用过谷歌云或者 ai studio 的，使用 gemini cli 登陆时可能会有些麻烦，可能要打开 console.cloud.google.com，找到你的 project id，然后设置 GOOGLE_CLOUD_PROJECT 环境变量，使用这种方式打开 gemini cli，就可以用了</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626080352544.png" alt="image-20250626080352544"></p><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">export GOOGLE_CLOUD_PROJECT=&quot;your-project-id&quot;</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">echo &#x27;export GOOGLE_CLOUD_PROJECT=&quot;your-project-id&quot;&#x27; &gt;&gt; ~/.zshrc</span><br></pre></td></tr></table></figure><p>然后 source ～&#x2F;.zshrc 就可以了。</p><hr><h3 id="报错：API-未启用"><a href="#报错：API-未启用" class="headerlink" title="报错：API 未启用"></a>报错：API 未启用</h3><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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span>API Error<span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">     <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">403</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;Gemini for Google Cloud API has not been used in project xxxxx before or it is disabled. Enable it by visiting</span></span><br><span class="line"><span class="string"> https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our</span></span><br><span class="line"><span class="string"> systems and retry.&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;errors&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;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Gemini for Google Cloud API has not been used in project xxxx before or it is disabled. Enable it by visiting</span></span><br><span class="line"><span class="string"> https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our</span></span><br><span class="line"><span class="string"> systems and retry.&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;domain&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usageLimits&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;accessNotConfigured&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;extendedHelp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://console.developers.google.com&quot;</span></span><br><span class="line">       <span class="punctuation">&#125;</span></span><br><span class="line">     <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PERMISSION_DENIED&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;details&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;@type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;type.googleapis.com/google.rpc.ErrorInfo&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SERVICE_DISABLED&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;domain&quot;</span><span class="punctuation">:</span> <span class="string">&quot;googleapis.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;metadata&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;activationUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;containerInfo&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;consumer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;projects/xxxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;service&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cloudaicompanion.googleapis.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;serviceTitle&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Gemini for Google Cloud API&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="punctuation">&#123;</span></span><br><span class="line">         <span class="attr">&quot;@type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;type.googleapis.com/google.rpc.LocalizedMessage&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;locale&quot;</span><span class="punctuation">:</span> <span class="string">&quot;en-US&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;Gemini for Google Cloud API has not been used in project xxxx before or it is disabled. Enable it by visiting</span></span><br><span class="line"><span class="string"> https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our</span></span><br><span class="line"><span class="string"> systems and retry.&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;@type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;type.googleapis.com/google.rpc.Help&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;links&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;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google developers console API activation&quot;</span><span class="punctuation">,</span></span><br><span class="line">             <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxx&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><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><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>打开报错了的网页 <a href="https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxxxx%EF%BC%8C%E6%AF%94%E5%A6%82%E8%BF%99%E4%B8%AA%EF%BC%8C%E8%BF%99%E4%B8%AA%E7%BD%91%E9%A1%B5%E6%98%AF%E5%92%8C%E4%BD%A0%E7%9A%84">https://console.developers.google.com/apis/api/cloudaicompanion.googleapis.com/overview?project=xxxxxx，比如这个，这个网页是和你的</a> ID 相关的，然后点击启用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d94297cbdd8e54f2d75d126f07f09bcb.png" alt="d94297cbdd8e54f2d75d126f07f09bcb"></p><hr><h3 id="成功运行！"><a href="#成功运行！" class="headerlink" title="成功运行！"></a>成功运行！</h3><p>终于可以用了不容易。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626075518890.png" alt="image-20250626075518890"></p>]]></content>
    
    
    <summary type="html">国内环境使用 Gemini CLI 的踩坑记录，Google Cloud 账号配置与免费额度说明。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="LLM" scheme="https://blog.no-claw.com/tags/LLM/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（一）：懒猫微服全栈上架指南，一步打包，一键发布</title>
    <link href="https://blog.no-claw.com/posts/ec579c86/"/>
    <id>https://blog.no-claw.com/posts/ec579c86/</id>
    <published>2025-07-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫应用离不开社区的力量，有了各位社区贡献者的支持让懒猫商店的应用越来越丰富。下面示范如何把自己的<strong>全栈应用</strong>上架到懒猫微服。</p><p>官网给出的示例里只有 <strong>3 个必备文件</strong>：<code>lzc-build.yml</code>、<code>lzc-icon.png</code>、<code>lzc-manifest.yml</code>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250630173852407.png" alt="示例目录结构"></p><ul><li>**<code>lzc-icon.png</code>**：应用图标，必须为  PNG。</li><li>**<code>lzc-build.yml</code>**：定义打包脚本、输出路径与图标路径。</li><li>**<code>lzc-manifest.yml</code>**：应用清单，描述路由规则等。<span id="more"></span></li></ul><h3 id="lzc-build-yml-示例"><a href="#lzc-build-yml-示例" class="headerlink" title="lzc-build.yml 示例"></a><code>lzc-build.yml</code> 示例</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="comment"># 打包预处理，例子里是复制目录，打包前端文件</span></span><br><span class="line"><span class="comment"># 见build.sh这个文件</span></span><br><span class="line"><span class="comment"># rm -rf ./dist</span></span><br><span class="line"><span class="comment"># mkdir -p dist</span></span><br><span class="line"><span class="comment"># 构建后端二进制文件，因为后面写了contentdir是 dist 文件夹，</span></span><br><span class="line"><span class="comment"># 所以dist是打包的上下文</span></span><br><span class="line"><span class="comment"># cp -r backend dist/</span></span><br><span class="line"><span class="comment"># 构建前端，这里就是普通的前端打包命令，只是指定了输出文件夹</span></span><br><span class="line"><span class="comment"># cd ui &amp;&amp; npx vite build --emptyOutDir --outDir ../dist/web</span></span><br><span class="line"><span class="attr">buildscript:</span> <span class="string">sh</span> <span class="string">build.sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># manifest: 指定 lpk 包的 manifest.yml ，一般是这个名字不改</span></span><br><span class="line"><span class="attr">manifest:</span> <span class="string">./lzc-manifest.yml</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># contentdir: 前面把前后端打包到这个目录还是。</span></span><br><span class="line"><span class="attr">contentdir:</span> <span class="string">./dist</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pkgout: lpk 包的输出路径</span></span><br><span class="line"><span class="attr">pkgout:</span> <span class="string">./</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># icon 指定 lpk 包 icon 的路径路径，如果不指定将会警告</span></span><br><span class="line"><span class="comment"># icon 仅仅允许 png 后缀的文件</span></span><br><span class="line"><span class="attr">icon:</span> <span class="string">./lzc-icon.png</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># dvshell 指定开发依赖的情况，这个我们后面专门来讲讲</span></span><br><span class="line"><span class="comment"># 这种情况下，选用 alpine:latest 作为基础镜像，在 dependencies 中添加所需要的开发依赖即可</span></span><br><span class="line"><span class="comment"># 如果 dependencies 和 build 同时存在，将会优先使用 dependencies</span></span><br><span class="line"><span class="attr">devshell:</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://127.0.0.1:5173</span></span><br><span class="line">  <span class="attr">dependencies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">nodejs</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">npm</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">python3</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">py3-pip</span></span><br><span class="line">  <span class="attr">setupscript:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    export npm_config_registry=https://registry.npmmirror.com</span></span><br><span class="line"><span class="string">    export PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple</span></span><br></pre></td></tr></table></figure><p><code>build.sh</code> 执行完后目录结构大致如下：</p><ul><li><strong>dist&#x2F;backend</strong> →  后端（可执行&#x2F;脚本）</li><li><strong>dist&#x2F;web</strong> →  前端（静态文件）</li></ul><hr><h3 id="lzc-manifest-yml-示例"><a href="#lzc-manifest-yml-示例" class="headerlink" title="lzc-manifest.yml 示例"></a><code>lzc-manifest.yml</code> 示例</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="attr">lzc-sdk-version:</span> <span class="number">0.1</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">代办清单Py</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">cloud.lazycat.app.todolistpy</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">description:</span></span><br><span class="line"><span class="attr">license:</span> <span class="string">https://choosealicense.com/licenses/mit/</span></span><br><span class="line"><span class="attr">homepage:</span></span><br><span class="line"><span class="attr">author:</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">todolistpy</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=file:///lzcapp/pkg/content/web</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/api/=exec://3000,./lzcapp/pkg/content/backend/run.sh</span></span><br></pre></td></tr></table></figure><p>routes 这里有三种写法：</p><ol><li><strong>file</strong> 代表文件，一般是纯静态文件，比如打包后的前端文件，也就是在 build.sh 里面做的打包前端的操作，npm build 之类的，我们在脚本里指定了 content 的路径就是机器上的&#x2F;lzcapp&#x2F;pkg&#x2F;content&#x2F;，所以&#x2F;lzcapp&#x2F;pkg&#x2F;content&#x2F;web 也就是刚才的 dist&#x2F;web。这个的意思就是说把根路由转发这个静态目录，其实就是类似 Nginx 托管静态文件这个样子，只是不需要手动打包，写好命令之后，打包工具帮忙做了这一套。</li><li><code>http(s)://$hostname/$path</code>， 这个是我们印象里的网关代理后端服务，比如&#x2F;api&#x2F;&#x3D;http(s):&#x2F;&#x2F;$hostname&#x2F;$path，其实就类似 Nginx 的 proxy_pass 将&#x2F;api 转发到 http(s):&#x2F;&#x2F;$hostname&#x2F;$path&#x2F;。</li><li><strong>exec</strong>：这个和 http(s)很像，后面多加了一个 run.sh，相当于在转发到 http(s)路由之前，先执行这个脚本。一般是用来预置环境，比如 pip install 什么的，但是由于每个人的环境不一样，还是要使用多个镜像源才保险，我上架的应用就遇到用户通过清华源下载报错 HTTP403 以及 腾讯源下载签名不匹配的问题，或者干脆使用 Docker，这个我们后面再说。</li></ol><p>附上 pip 多源的例子：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 已有 —— 主索引 &amp; 前两级备用</span></span><br><span class="line">pip config <span class="built_in">set</span>  global.index-url        https://pypi.tuna.tsinghua.edu.cn/simple</span><br><span class="line">pip config --add global.extra-index-url https://pypi.mirrors.ustc.edu.cn/simple/</span><br><span class="line">pip config --add global.extra-index-url https://mirrors.bfsu.edu.cn/pypi/web/simple/</span><br><span class="line"></span><br><span class="line"><span class="comment"># ③ 阿里云（华东节点评测最稳）</span></span><br><span class="line">pip config --add global.extra-index-url https://mirrors.aliyun.com/pypi/simple/</span><br><span class="line"></span><br><span class="line"><span class="comment"># ④ 华为云（华南线路友好）</span></span><br><span class="line">pip config --add global.extra-index-url https://repo.huaweicloud.com/repository/pypi/simple/</span><br><span class="line"></span><br><span class="line"><span class="comment"># ⑤ 字节跳动开源镜像（火山引擎，带全站 CDN）</span></span><br><span class="line">pip config --add global.extra-index-url https://mirrors.byteimg.com/pypi/simple/</span><br><span class="line"></span><br><span class="line"><span class="comment"># ⑥ 南京大学镜像（NJU，教育网 &amp; 华东建议保留）</span></span><br><span class="line">pip config --add global.extra-index-url https://mirrors.nju.edu.cn/pypi/web/simple/</span><br></pre></td></tr></table></figure><hr><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 打包成 LPK</span></span><br><span class="line">lzc-cli project build -o release.lpk</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在线安装 LPK</span></span><br><span class="line">lzc-cli app install release.lpk</span><br></pre></td></tr></table></figure><p>然后是打包，如果缺少 lzc-build.yml，lzc-icon.png，lzc-manifest.yml 三者之一就会报错。</p><p>LPK 是懒猫微服应用商店 APP 的安装包格式，其实可以理解为一个配置文件的压缩包，安装之后其实就是在微服内部启动了一个 alpine 的 image，然后通过 build.sh 安装依赖。</p><p>通过 lzc-docker 来看，直接打包的就是这个 images registry.lazycat.cloud&#x2F;lzc&#x2F;lzcapp:3.20.3</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250630181419299.png" alt="image-20250630181419299"></p><p>命令如下<strong>lzc-docker history –no-trunc registry.lazycat.cloud&#x2F;lzc&#x2F;lzcapp:3.20.3</strong>，能够看到是 Alpine 作为 base image，然后更换中科大的源，以及安装 <strong>gcompat</strong> 以兼容 glibc 程序。</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">(base) lzcbox-029c588e ~ <span class="comment"># lzc-docker history --no-trunc registry.lazycat.cloud/lzc/lzcapp:3.20.3</span></span><br><span class="line">IMAGE                                                                     CREATED        CREATED BY                                                                                                SIZE      COMMENT</span><br><span class="line">sha256:ba7a533c869a26d89e83bdc5ddb978df5a3502ac91452422a649d0d3cf52190b   7 months ago   RUN /bin/sh -c apk add gcompat <span class="comment"># buildkit                                                                 2.48MB    buildkit.dockerfile.v0</span></span><br><span class="line">&lt;missing&gt;                                                                 7 months ago   RUN /bin/sh -c sed -i <span class="string">&#x27;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g&#x27;</span> /etc/apk/repositories <span class="comment"># buildkit   97B       buildkit.dockerfile.v0</span></span><br><span class="line">&lt;missing&gt;                                                                 9 months ago   CMD [<span class="string">&quot;/bin/sh&quot;</span>]                                                                                           0B        buildkit.dockerfile.v0</span><br><span class="line">&lt;missing&gt;                                                                 9 months ago   ADD alpine-minirootfs-3.20.3-x86_64.tar.gz / <span class="comment"># buildkit                                                   7.8MB     buildkit.dockerfile.v0</span></span><br><span class="line">(base) lzcbox-029c588e ~ <span class="comment">#</span></span><br></pre></td></tr></table></figure><p>甚至可以看到，这个 image 是连 bash 以及各种开发运行时都没有的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">(base) lzcbox-029c588e ~ # lzc-docker run -it registry.lazycat.cloud/lzc/lzcapp:3.20.3 bash</span><br><span class="line">docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: &quot;bash&quot;: executable file not found in $PATH: unknown.</span><br><span class="line">(base) lzcbox-029c588e ~ # lzc-docker run -it registry.lazycat.cloud/lzc/lzcapp:3.20.3 sh</span><br><span class="line">/ # go</span><br><span class="line">sh: go: not found</span><br><span class="line">/ # npm</span><br><span class="line">sh: npm: not found</span><br><span class="line">/ # pip</span><br><span class="line">sh: pip: not found</span><br><span class="line">/ # python</span><br><span class="line">sh: python: not found</span><br><span class="line">/ #</span><br></pre></td></tr></table></figure><p>所以这个 backend 文件夹的 run.sh 是拿来安装 Python 依赖的。而前端是使用本地的 npm 打包的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">#!/bin/sh</span><br><span class="line"></span><br><span class="line"># 切换到当前目录</span><br><span class="line">cd &quot;$(dirname &quot;$0&quot;)&quot;</span><br><span class="line">sed -i &#x27;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g&#x27; /etc/apk/repositories</span><br><span class="line">apk update</span><br><span class="line">apk add python3 py3-pip</span><br><span class="line"># ❶ 设主索引，只能有一个</span><br><span class="line"># 已有 —— 主索引 &amp; 前两级备用</span><br><span class="line">pip config set  global.index-url        https://pypi.tuna.tsinghua.edu.cn/simple</span><br><span class="line">pip config --add global.extra-index-url https://pypi.mirrors.ustc.edu.cn/simple/</span><br><span class="line">pip config --add global.extra-index-url https://mirrors.bfsu.edu.cn/pypi/web/simple/</span><br><span class="line"></span><br><span class="line"># ③ 阿里云（华东节点评测最稳）</span><br><span class="line">pip config --add global.extra-index-url https://mirrors.aliyun.com/pypi/simple/</span><br><span class="line"></span><br><span class="line"># ④ 华为云（华南线路友好）</span><br><span class="line">pip config --add global.extra-index-url https://repo.huaweicloud.com/repository/pypi/simple/</span><br><span class="line"></span><br><span class="line"># ⑤ 字节跳动开源镜像（火山引擎，带全站 CDN）</span><br><span class="line">pip config --add global.extra-index-url https://mirrors.byteimg.com/pypi/simple/</span><br><span class="line"></span><br><span class="line"># ⑥ 南京大学镜像（NJU，教育网 &amp; 华东建议保留）</span><br><span class="line">pip config --add global.extra-index-url https://mirrors.nju.edu.cn/pypi/web/simple/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">pip install -r ./requirements.txt --break-system-packages</span><br><span class="line">python3 app.py</span><br></pre></td></tr></table></figure><p>安装之后的 app 可以通过 lzc-docker 查看，也可以使用 Dozze 查看日志，一般 debug 时候的时候会看这个。</p><p>DOZZL 需要安装开发者工具，然后使用<a href="https://dev.设备名.heiyu.space/dozzle/%E8%AE%BF%E9%97%AE%E3%80%82">https://dev.设备名.heiyu.space/dozzle/访问。</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250630190230526.png" alt="image-20250630190230526"></p><p>一般来说部署有两个 pod，一个是 App-1 结尾的，主要是涉及到转发，run.sh 自动安装依赖，以及健康检查。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">PATH:&quot;/&quot; is served by &quot;file&quot;://&quot;/lzcapp/pkg/content/dist&quot;</span><br><span class="line">PATH:&quot;/api/&quot; is served by &quot;http&quot;://&quot;host.lzcapp:53443&quot;</span><br><span class="line">health check finished</span><br></pre></td></tr></table></figure><p>应用名字-1 结尾的，这个才是应用的日志。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[2025-06-29 17:29:29 +0800] [1] [INFO] Starting gunicorn 23.0.0</span><br><span class="line">[2025-06-29 17:29:29 +0800] [1] [INFO] Listening at: http://0.0.0.0:9527 (1)</span><br><span class="line">[2025-06-29 17:29:29 +0800] [1] [INFO] Using worker: sync</span><br><span class="line">[2025-06-29 17:29:29 +0800] [9] [INFO] Booting worker with pid: 9</span><br></pre></td></tr></table></figure><p>希望大家都能够多多为懒猫微服贡献应用。</p>]]></content>
    
    
    <summary type="html">懒猫微服应用全栈上架完整指南，从打包到一键发布。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>通过 SMTP 使用 163 邮箱发送邮件</title>
    <link href="https://blog.no-claw.com/posts/5dfd4b8a/"/>
    <id>https://blog.no-claw.com/posts/5dfd4b8a/</id>
    <published>2025-06-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>这里的 <strong>授权码（Authorization Code）</strong> 是 163 邮箱（以及 QQ 邮箱等国内常见邮箱服务商）专门为 <strong>SMTP&#x2F;POP3&#x2F;IMAP</strong> 等邮件协议提供的独立密码，与邮箱的登录密码不同。</p><ul><li><p><strong>作用</strong>：用于通过第三方客户端（如 Python 的 <code>smtplib</code>）发送邮件，避免直接暴露邮箱登录密码。</p></li><li><p><strong>获取方式</strong>（以 163 邮箱为例）：</p><ol><li><p>登录 <a href="https://mail.163.com/">163 邮箱</a>。</p></li><li><p>进入 <strong>设置 → POP3&#x2F;SMTP&#x2F;IMAP</strong>。</p><span id="more"></span></li><li><p>开启 <strong>SMTP 服务</strong>，系统会提示你设置授权码（类似 <code>ABCDEFG123456</code>，不是你的登录密码）。</p></li><li><p>复制这个授权码，替换代码中的 <code>your_authorization_code</code>。</p></li></ol></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250701174551330.png" alt="image-20250701174551330"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> smtplib</span><br><span class="line"><span class="keyword">from</span> email.mime.multipart <span class="keyword">import</span> MIMEMultipart</span><br><span class="line"><span class="keyword">from</span> email.mime.text <span class="keyword">import</span> MIMEText</span><br><span class="line"><span class="keyword">from</span> email.mime.application <span class="keyword">import</span> MIMEApplication</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">send_163_email</span>():</span><br><span class="line">    <span class="comment"># 163邮箱SMTP配置</span></span><br><span class="line">    smtp_server = <span class="string">&quot;smtp.163.com&quot;</span></span><br><span class="line">    smtp_port = <span class="number">465</span>  <span class="comment"># SSL加密端口</span></span><br><span class="line">    sender = <span class="string">&quot;your_username@163.com&quot;</span>  <span class="comment"># 你的163邮箱</span></span><br><span class="line">    password = <span class="string">&quot;ABCDEFG123456&quot;</span>  <span class="comment"># 替换为你的SMTP授权码（不是登录密码！）</span></span><br><span class="line">    receiver = <span class="string">&quot;recipient@example.com&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">    msg = MIMEMultipart()</span><br><span class="line">    msg[<span class="string">&#x27;From&#x27;</span>] = sender</span><br><span class="line">    msg[<span class="string">&#x27;To&#x27;</span>] = receiver</span><br><span class="line">    msg[<span class="string">&#x27;Subject&#x27;</span>] = <span class="string">&quot;测试邮件（带Markdown附件）&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 邮件正文</span></span><br><span class="line">    msg.attach(MIMEText(<span class="string">&quot;这是邮件正文，附件是Markdown文件。&quot;</span>, <span class="string">&#x27;plain&#x27;</span>, <span class="string">&#x27;utf-8&#x27;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 添加Markdown附件</span></span><br><span class="line">    markdown_content = <span class="string">&quot;# CSDN文章汇总\n| 标题 | 链接 |\n|------|------|\n| [Python教程] | https://example.com |&quot;</span></span><br><span class="line">    attachment = MIMEApplication(markdown_content.encode(<span class="string">&#x27;utf-8&#x27;</span>), Name=<span class="string">&quot;articles.md&quot;</span>)</span><br><span class="line">    attachment[<span class="string">&#x27;Content-Disposition&#x27;</span>] = <span class="string">&#x27;attachment; filename=&quot;articles.md&quot;&#x27;</span></span><br><span class="line">    msg.attach(attachment)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 发送邮件</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> smtplib.SMTP_SSL(smtp_server, smtp_port) <span class="keyword">as</span> server:</span><br><span class="line">            server.login(sender, password)</span><br><span class="line">            server.sendmail(sender, receiver, msg.as_string())</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;邮件发送成功！&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;发送失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    send_163_email()</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Python 通过 SMTP 协议使用 163 邮箱发送邮件，含授权码获取和 smtplib 代码示例。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="Python" scheme="https://blog.no-claw.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十七）：把 Steam 游戏存到懒猫网盘</title>
    <link href="https://blog.no-claw.com/posts/ffc71b6/"/>
    <id>https://blog.no-claw.com/posts/ffc71b6/</id>
    <published>2025-06-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫微服的网盘基本可以替代一些公有的网盘，还有一个好处是可以自动挂载，只要打开懒猫微服的客户端，然后自动把网盘就能自动挂载到 Finder，十分方便。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627201448767.png" alt="image-20250627201448767"></p><p>网盘的挂载是支持了 WebDAV 和 Sambda。这是常见的两种远程访问的协议。</p><p><strong>WebDAV（Web Distributed Authoring and Versioning）</strong>主要用于 <strong>HTTP&#x2F;HTTPS 协议</strong> 的文件共享，适合 <strong>远程访问、云存储</strong>。基于 HTTP&#x2F;HTTPS<strong>，可在浏览器中直接访问（如 <code>http://server/webdav</code>）。</strong></p><p>SMB（Server Message Block）&#x2F; Samba 主要用于 <strong>局域网文件共享</strong>（如 Windows 共享文件夹、NAS、企业内网存储）。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627211645046.png" alt="image-20250627211645046"></p><span id="more"></span><p>通过 mount 命令可以看到，懒猫微服客户端默认使用的是 SMB 协议挂载：</p><figure class="highlight plaintext"><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">//xu-automount@file.micro.heiyu.space/xu-automount on /Users/xu/lazycat_automount/micro (smbfs, nodev, nosuid, mounted by xu)</span><br><span class="line">//xu-automount@file.micro.heiyu.space/xu on /Volumes/xu (smbfs, nodev, nosuid, mounted by xu)</span><br></pre></td></tr></table></figure><p>默认有两个文件夹，其实都是指向网盘根目录的软连接。两者内容完全一样。</p><blockquote><p>&lt;用户名&gt;-automount：这个应该不是自动挂载到文件管理器的目录</p><p>&lt;用户名&gt;: 这个是网盘多租户的目录</p></blockquote><p>所以在 Steam 里我们直接添加驱动器就好：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a1a7bbcb4236bc63c91151d9dc3a53b6.png" alt="a1a7bbcb4236bc63c91151d9dc3a53b6"></p><p>然后可以选择这两个文件目录，就像前面介绍的，这两个选择哪个都行：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ff20f0dc7b36ba7e27ad18ce45b2c378.png" alt="ff20f0dc7b36ba7e27ad18ce45b2c378"></p><p>然后这个时候会弹出来这个提示。我们选择允许。这样 Steam 才能有写入懒猫网盘的权限。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b90bea03eed9f716a64f52ae16a2526f.png" alt="b90bea03eed9f716a64f52ae16a2526f"></p><p>然后选择下载之后，我们发现网盘里多了一个 SteamLibrary 的目录。所有的游戏都存在这里。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/7733bdd613320011b0de6ad55032cc19.png" alt="7733bdd613320011b0de6ad55032cc19"></p><p>如果你比较习惯网盘的页面，也可以在网盘里找到 SteamLibrary 的目录</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250627202118461.png" alt="image-20250627202118461"></p><p>如果哪天不再需要写入懒猫网盘，用这个办法删除。网上吐槽挺多的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/788d94c0cb77ec7c66aa3de73f414462.png" alt="788d94c0cb77ec7c66aa3de73f414462"></p>]]></content>
    
    
    <summary type="html">把 Steam 游戏库存到懒猫网盘，客户端自动挂载，节省本地硬盘空间。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十六）：把懒猫微服当作 24 × 7 在线开发机</title>
    <link href="https://blog.no-claw.com/posts/25ccd3a3/"/>
    <id>https://blog.no-claw.com/posts/25ccd3a3/</id>
    <published>2025-06-28T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近和极限科技沟通，将 <strong>INFINI Console</strong> 上架到懒猫微服。可以当作随时可用、不关机的 <strong>远程开发机</strong>。比如连接自己部署的 ES，中间件，数据库什么的。</p><h2 id="1-应用商店一键安装-INFINI-Console"><a href="#1-应用商店一键安装-INFINI-Console" class="headerlink" title="1. 应用商店一键安装 INFINI Console"></a>1. 应用商店一键安装 INFINI Console</h2><p>从懒猫微服应用商店搜索 <strong>Console</strong>，点击“安装”即可：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a8de38c1a8d6cb8bf34ae697256ad230.png" alt="应用商店界面"></p><h2 id="2-初始化与登录"><a href="#2-初始化与登录" class="headerlink" title="2. 初始化与登录"></a>2. 初始化与登录</h2><p>初始化之后登录：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/Snipaste_2025-06-24_13-16-34.png" alt="登录页面"></p><span id="more"></span><p>首次登录后台，左侧侧边栏包含 <strong>Dashboard、Agents、Settings</strong> 等模块：</p><ul><li>Dashboard 默认展示 CPU &#x2F; 内存 &#x2F; 磁盘实时曲线。</li><li>顶栏可切换“明暗主题”并显示当前工作区 ID。</li><li>右下角有“检查更新”按钮，提示有新版时可一键升级。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/Snipaste_2025-06-24_13-16-57.png" alt="控制台主界面"></p><h2 id="3-用-Dockge-安装-Easysearch"><a href="#3-用-Dockge-安装-Easysearch" class="headerlink" title="3. 用 Dockge 安装 Easysearch"></a>3. 用 Dockge 安装 Easysearch</h2><p>ES 的话，我是直接用 Dockge 安装的，如果你需要啥中间价，数据库都可以用这个安装。</p><p>前提需要用 lzc-cli appstore copy-image 来获取国内的镜像源：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将官方镜像复制到懒猫内网仓库</span></span><br><span class="line">lzc-cli appstore copy-image infinilabs/easysearch:1.13.0-2159</span><br></pre></td></tr></table></figure><p>然后把 <code>docker run</code> 或 <code>docker-compose.yml</code> 中的镜像地址替换成上一步生成的私有 registry 地址即可。全部容器由 <strong>Dockge</strong> 图形化管理：<br>（截图信息要点）</p><ul><li>Dockge 左侧列出所有 Stack，右侧显示 Easysearch 服务状态为 <code>Running</code>。</li><li>端口 9200 已自动映射，重启、查看日志，都能一键完成。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624164746305.png" alt="Dockge 管理界面"></p><h2 id="4-为什么说它适合做开发机？"><a href="#4-为什么说它适合做开发机？" class="headerlink" title="4. 为什么说它适合做开发机？"></a>4. 为什么说它适合做开发机？</h2><table><thead><tr><th>需求</th><th>懒猫微服能力</th><th>体验亮点</th></tr></thead><tbody><tr><td><strong>24 × 7 在线</strong></td><td>独立云主机，自动重启、监控告警</td><td>关掉本地电脑，服务仍在运行</td></tr><tr><td><strong>x86 架构</strong></td><td>后端统一使用 x86 节点</td><td>对 <strong>Mac M 系列（ARM）用户</strong>，可避免本地编译兼容性问题</td></tr><tr><td><strong>多端远程开发</strong></td><td>内置 Web Shell、端口映射、域名分配</td><td>VS Code Remote &#x2F; JetBrains Gateway 秒连接</td></tr><tr><td><strong>镜像同步</strong></td><td><code>lzc-cli appstore copy-image</code></td><td>国内网络下拉镜像不超时</td></tr><tr><td><strong>中间件生态</strong></td><td>Dockge + Compose</td><td>RabbitMQ、Redis、Postgres 都能一键启动</td></tr><tr><td><strong>环境变量管理</strong></td><td>UI + <code>.env</code> 托管</td><td>私密信息集中维护，避免泄漏</td></tr></tbody></table><p>总结下来，把懒猫微服当作一个可远程访问的轻量开发机还是挺合适的：</p><ol><li>不用担心公网 IP 和端口映射</li><li>应用商店部署方便快捷</li><li>支持命令行部署、私有镜像同步</li><li>用 Dockge 管理一套中间件生态完全没问题</li></ol><p>适合：<br>👉 想要随时随地调试项目的开发者<br>👉 不想在本地装一堆环境的轻量用户<br>👉 有多端共享、协作需求的远程开发场景</p><p>整套流程走下来，你只需一台浏览器，就能获得 <strong>24 × 7 不关机的云端开发环境</strong>。如果你也是 Mac M-芯片用户、经常出差或需要多端协作，不妨试试用懒猫微服托管自己的 DevBox。</p>]]></content>
    
    
    <summary type="html">把懒猫微服当作 24 小时在线的远程开发机，随时连接中间件和数据库。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 实战指南：修改索引主分片的三种方式（split  shrink  reindex</title>
    <link href="https://blog.no-claw.com/posts/cba11cdd/"/>
    <id>https://blog.no-claw.com/posts/cba11cdd/</id>
    <published>2025-06-27T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Easysearch（兼容 Elasticsearch）的架构中，索引的主分片数（<code>index.number_of_shards</code>）一旦创建就无法直接修改。这给实际使用带来挑战：</p><ul><li>设得太少，查询&#x2F;写入瓶颈出现；</li><li>设得太多，资源浪费、集群不稳；</li><li>想变更结构，却发现配置是“写死”的。</li></ul><p>本文将带你深入了解三种常见但本质不同的索引重构方式：<code>split</code>、<code>shrink</code>、<code>reindex</code>，教你如何选择合适方案、安全操作，并解释为什么<strong>split + shrink 无法取代 reindex</strong>。</p><hr><h2 id="📌-一张图概览三种方式"><a href="#📌-一张图概览三种方式" class="headerlink" title="📌 一张图概览三种方式"></a>📌 一张图概览三种方式</h2><table><thead><tr><th>方法</th><th>是否重建索引</th><th>可否原名使用</th><th>改分片数限制</th><th>是否保留数据</th><th>是否改结构（mapping&#x2F;settings）</th><th>常见用途</th></tr></thead><tbody><tr><td><code>split</code></td><td>✅ 新建索引</td><td>❌ 不支持</td><td>只能 × 倍数（如 1→2→4）</td><td>✅ 是</td><td>❌ 否</td><td>提升写入并发&#x2F;读性能</td></tr><tr><td><code>shrink</code></td><td>✅ 新建索引</td><td>❌ 不支持</td><td>只能 ÷ 因数（如 4→2→1）</td><td>✅ 是</td><td>❌ 否</td><td>合并历史数据分片</td></tr><tr><td><code>reindex</code></td><td>✅ 新建索引</td><td>✅ 支持（先删）</td><td>任意</td><td>✅ 是</td><td>✅ 支持</td><td>自定义结构&#x2F;分片&#x2F;升级</td></tr></tbody></table><hr><h2 id="🔧-一、split：将分片数量倍增（如-1-→-2-→-4）"><a href="#🔧-一、split：将分片数量倍增（如-1-→-2-→-4）" class="headerlink" title="🔧 一、split：将分片数量倍增（如 1 → 2 → 4）"></a>🔧 一、split：将分片数量倍增（如 1 → 2 → 4）</h2><blockquote><p><strong>适用于：</strong> 提升并发能力、增加查询&#x2F;写入并行度。</p></blockquote><span id="more"></span><h3 id="✅-条件要求："><a href="#✅-条件要求：" class="headerlink" title="✅ 条件要求："></a>✅ 条件要求：</h3><ul><li>原始索引必须设置 <code>index.blocks.write: true</code>（只读）；主要是防止写入继续增长。</li><li>新分片数必须是原主分片的 <strong>倍数</strong>；</li><li>不能使用原名，目标索引必须另起新名。</li></ul><h3 id="🛠-操作示例："><a href="#🛠-操作示例：" class="headerlink" title="🛠 操作示例："></a>🛠 操作示例：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="comment"># 设置只读</span></span><br><span class="line">PUT /abc/_settings</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;index.blocks.write&quot;</span>: <span class="literal">true</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">```json</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;error&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;root_cause&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;illegal_state_exception&quot;</span>,</span><br><span class="line">        <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;index abc must be read-only to resize index. use \&quot;index.blocks.write=true\&quot;&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;illegal_state_exception&quot;</span>,</span><br><span class="line">    <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;index abc must be read-only to resize index. use \&quot;index.blocks.write=true\&quot;&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;status&quot;</span>: 500</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="拆分索引（1-→-4）"><a href="#拆分索引（1-→-4）" class="headerlink" title="拆分索引（1 → 4）"></a>拆分索引（1 → 4）</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">POST /abc/_split/abc_split_2shards</span><br><span class="line">&#123;</span><br><span class="line">  &quot;settings&quot;: &#123;</span><br><span class="line">    &quot;index.number_of_shards&quot;: 2</span><br><span class="line">  &#125;</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><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">&#123;</span><br><span class="line">  &quot;acknowledged&quot;: true,</span><br><span class="line">  &quot;shards_acknowledged&quot;: true,</span><br><span class="line">  &quot;index&quot;: &quot;abc_split_2shards&quot;</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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_argument_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;the number of source shards [13] must be a factor of [25]&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_argument_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;the number of source shards [13] must be a factor of [25]&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;status&quot;</span><span class="punctuation">:</span> <span class="number">400</span></span><br><span class="line"><span class="punctuation">&#125;</span></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">GET /abc_split_2shards/_settings?flat_settings=true</span><br></pre></td></tr></table></figure><blockquote><p>&#x2F;_settings：Elasticsearch 提供的 API 端点，用于查看索引设置。<br>?flat_settings&#x3D;true：查询参数，使返回结果以扁平化的键值对形式展示（而非嵌套结构）。</p></blockquote><p>可以看到目标的索引也是只读的，这在 Easysearch 里是 ElasticSearch 不一样的地方。</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;abc_split_2shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;index.blocks.write&quot;</span><span class="punctuation">:</span> <span class="string">&quot;true&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.creation_date&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1750747232004&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.number_of_replicas&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.number_of_shards&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.provided_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc_split_2shards&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.resize.source.name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.resize.source.uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3NY_W5B_TzimoEGdoA74cg&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.routing.allocation.initial_recovery._id&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.routing_partition_size&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;e2BQiTRKTlaTS5OE8kmiXw&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.version.created&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1130099&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index.version.upgraded&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1130099&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>然后使用这个来解锁 write block。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PUT /abc_split_2shards/_settings</span><br><span class="line">&#123;</span><br><span class="line">  &quot;settings&quot;: &#123;</span><br><span class="line">    &quot;index.blocks.write&quot;: false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你不想让目标索引变成只读。也可以在_split 的时候加上 “index.blocks.write”: false。</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">POST /abc_split_2sharxds/_split/qwe</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;index.blocks.write&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="attr">&quot;index.number_of_shards&quot;</span><span class="punctuation">:</span> <span class="number">26</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="🔧-二、shrink：将分片数量整除压缩（如-8-→-4-→-1）"><a href="#🔧-二、shrink：将分片数量整除压缩（如-8-→-4-→-1）" class="headerlink" title="🔧 二、shrink：将分片数量整除压缩（如 8 → 4 → 1）"></a>🔧 二、shrink：将分片数量整除压缩（如 8 → 4 → 1）</h2><blockquote><p><strong>适用于：</strong> 历史归档数据压缩、节省内存、提升查询效率。</p></blockquote><h3 id="✅-条件要求：-1"><a href="#✅-条件要求：-1" class="headerlink" title="✅ 条件要求："></a>✅ 条件要求：</h3><ul><li>所有主分片必须集中在同一节点；</li><li>原索引必须只读；</li><li>新分片数必须是旧分片数的 <strong>因数</strong>；</li><li>同样不能保留原名，需新建索引名。</li></ul><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><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"># 强制所有主分片调度到 node-1</span></span><br><span class="line">PUT /source_index/_settings</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;index.blocks.write&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="string">&quot;index.routing.allocation.require._name&quot;</span>: <span class="string">&quot;node-1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;index.number_of_replicas&quot;</span>: 0</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">```json</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;error&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;root_cause&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;illegal_state_exception&quot;</span>,</span><br><span class="line">        <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;index test1 must be read-only to resize index. use \&quot;index.blocks.write=true\&quot;&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;illegal_state_exception&quot;</span>,</span><br><span class="line">    <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;index test1 must be read-only to resize index. use \&quot;index.blocks.write=true\&quot;&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;status&quot;</span>: 500</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 plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">POST /source_index/_shrink/source_index_1</span><br><span class="line">&#123;</span><br><span class="line">  &quot;settings&quot;: &#123;</span><br><span class="line">      &quot;index.blocks.write&quot;: false,</span><br><span class="line">    &quot;index.number_of_shards&quot;: 1</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 plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PUT /source_index_1/_settings</span><br><span class="line">&#123;</span><br><span class="line">  &quot;settings&quot;: &#123;</span><br><span class="line">    &quot;index.blocks.write&quot;: false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🔧-三、reindex：拷贝数据-新建结构-替换旧索引"><a href="#🔧-三、reindex：拷贝数据-新建结构-替换旧索引" class="headerlink" title="🔧 三、reindex：拷贝数据 + 新建结构 + 替换旧索引"></a>🔧 三、reindex：拷贝数据 + 新建结构 + 替换旧索引</h2><blockquote><p><strong>适用于：</strong> 任意修改分片数、字段结构、settings，或实现“看起来改了原索引”的效果。</p></blockquote><h3 id="✅-优势："><a href="#✅-优势：" class="headerlink" title="✅ 优势："></a>✅ 优势：</h3><ul><li>唯一支持<strong>任意分片数修改</strong>；</li><li>可自由重构 mapping、settings；</li><li>可支持<strong>保留原名</strong>（删除旧索引 + 重新创建）；</li><li>可带条件、分页、脚本拷贝数据；</li><li>是唯一可模拟“修改原索引分片”的方式。</li></ul><h3 id="🛠-操作步骤（保留原名但改变结构）："><a href="#🛠-操作步骤（保留原名但改变结构）：" class="headerlink" title="🛠 操作步骤（保留原名但改变结构）："></a>🛠 操作步骤（保留原名但改变结构）：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 创建临时索引结构（你想要的新结构）</span></span><br><span class="line">PUT /my_index_v2</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;index.number_of_shards&quot;</span>: 5,</span><br><span class="line">    <span class="string">&quot;index.number_of_replicas&quot;</span>: 1</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;user&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;message&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&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="comment"># 2. 拷贝数据</span></span><br><span class="line">POST /_reindex</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;my_index&quot;</span> &#125;,</span><br><span class="line">  <span class="string">&quot;dest&quot;</span>:   &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;my_index_v2&quot;</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 删除旧索引（谨慎）</span></span><br><span class="line">DELETE /my_index</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 创建同名索引（新结构）</span></span><br><span class="line">PUT /my_index</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;index.number_of_shards&quot;</span>: 3,</span><br><span class="line">    <span class="string">&quot;index.number_of_replicas&quot;</span>: 1</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;user&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;keyword&quot;</span> &#125;,</span><br><span class="line">      <span class="string">&quot;message&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&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="comment"># 5. 再次拷贝数据（回填）</span></span><br><span class="line">POST /_reindex</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;my_index_v2&quot;</span> &#125;,</span><br><span class="line">  <span class="string">&quot;dest&quot;</span>:   &#123; <span class="string">&quot;index&quot;</span>: <span class="string">&quot;my_index&quot;</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">6.  查看索引，并且删除目标索引</span><br><span class="line">GET _cat/indices/my_index*?v</span><br><span class="line"></span><br><span class="line">![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/64f99ab7b3ba4b4a8e18d53d4e32fab5.png)</span><br><span class="line">最后删除my_index_v2 即可</span><br><span class="line"></span><br><span class="line">DELETE /my_index_v2</span><br></pre></td></tr></table></figure><hr><h2 id="⚠️-为什么-shrink-split-不能替代-reindex？"><a href="#⚠️-为什么-shrink-split-不能替代-reindex？" class="headerlink" title="⚠️ 为什么 shrink + split 不能替代 reindex？"></a>⚠️ 为什么 shrink + split 不能替代 reindex？</h2><p>很多用户会问：能不能 <code>shrink → split</code> 或 <code>split → shrink</code> 拼接出任意分片数？</p><p>答案是：<strong>数学上不成立 + 实战限制太多。</strong></p><table><thead><tr><th>操作</th><th>说明</th></tr></thead><tbody><tr><td>split 只能倍增</td><td>例如：1 → 2 → 4 → 8 ✅，但不能变成 3、5、6 ❌</td></tr><tr><td>shrink 只能整除</td><td>例如：8 → 4 → 2 → 1 ✅，但不能变成 3、5 ❌</td></tr><tr><td>二者组合</td><td>受限于倍数 × 因数关系，<strong>不是万能变换</strong>（大多数目标分片数根本到不了）</td></tr></tbody></table><h3 id="✅-唯一万能方式：reindex"><a href="#✅-唯一万能方式：reindex" class="headerlink" title="✅ 唯一万能方式：reindex"></a>✅ 唯一万能方式：<code>reindex</code></h3><p>可以任意：</p><ul><li>调整分片数 ✅</li><li>修改字段结构 ✅</li><li>改 settings ✅</li><li>保留索引名 ✅</li></ul><hr><h2 id="✅-最佳实践总结"><a href="#✅-最佳实践总结" class="headerlink" title="✅ 最佳实践总结"></a>✅ 最佳实践总结</h2><table><thead><tr><th>场景</th><th>推荐方式</th><th>理由</th></tr></thead><tbody><tr><td>写入并发不足（1 → 4）</td><td>split</td><td>快速、低风险</td></tr><tr><td>存储&#x2F;查询优化（8 → 1）</td><td>shrink</td><td>节省资源、适合归档</td></tr><tr><td>修改索引结构、字段、settings</td><td>reindex</td><td>最灵活、唯一支持任意结构</td></tr><tr><td>想保留原名但改分片数</td><td>reindex（配合 delete&#x2F;recreate）</td><td>只有它能实现</td></tr><tr><td>不想中断服务</td><td>reindex + alias 切换</td><td>alias 实现无缝替换</td></tr></tbody></table><hr><h2 id="🚀-附加建议"><a href="#🚀-附加建议" class="headerlink" title="🚀 附加建议"></a>🚀 附加建议</h2><p>S* split&#x2F;shrink 一般用于 <strong>线上小范围结构调整</strong>；</p><ul><li>reindex 用于 <strong>升级、清洗、结构优化</strong>等更大粒度的改造；</li><li>如果你不想中断服务，强烈建议使用 <strong>alias + reindex</strong> 做平滑切换；</li><li>不建议用 shrink + split 拼接方案，实际运维性差、数学关系苛刻。E</li></ul>]]></content>
    
    
    <summary type="html">详解 Easysearch 中通过 split、shrink、reindex 修改索引主分片数的方法</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（十）：本地开发，如何接入懒猫微服的 OpenID Connect (OIDC)</title>
    <link href="https://blog.no-claw.com/posts/4daab20a/"/>
    <id>https://blog.no-claw.com/posts/4daab20a/</id>
    <published>2025-06-26T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们知道懒猫的 OpenID Connect (OIDC) 无需在后台申请，商店里的应用在运行的时候会自动申请，但是本地测试的时候就不太方便。</p><p>一般是需要用其他的 IDP 作为测试环境，因为 OIDC 的协议是通用的，不像 OAuth 这么百花齐放。</p><p>以我的“家庭任务通知”APP 为例，讲解下在开发模式下接入懒猫微服的 OpenID Connect (OIDC)。</p><hr><h3 id="添加-OIDC-登录逻辑"><a href="#添加-OIDC-登录逻辑" class="headerlink" title="添加 OIDC 登录逻辑"></a>添加 OIDC 登录逻辑</h3><p>首先前端需要有一个 OIDC 的登录按钮，然后做好 OIDC 的逻辑：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626104429835.png" alt="image-20250626104429835"></p><hr><h3 id="使用懒猫-ENV-查看器导出本地配置"><a href="#使用懒猫-ENV-查看器导出本地配置" class="headerlink" title="使用懒猫 ENV 查看器导出本地配置"></a>使用懒猫 ENV 查看器导出本地配置</h3><p>从应用商店安装我写的“懒猫 ENV 查看器”，导出 <code>env.example</code> 文件，导出项目之后重命名为 <code>.env</code>。这样就可以把商店里的 ENV 复制到本地的开发环境。</p><p>不过需要注意的是：<strong>应用名字和回调函数还是原来的，不要轻易去改。遇到问题再手动调试。</strong></p><p><a href="https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env">https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env</a></p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/Snipaste_2025-06-26_13-41-30.png" alt="Snipaste_2025-06-26_13-41-30"></p><h3 id="登录后出现回调-URL-报错"><a href="#登录后出现回调-URL-报错" class="headerlink" title="登录后出现回调 URL 报错"></a>登录后出现回调 URL 报错</h3><p>登录之后我们就看到了这个页面：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626104445501.png" alt="image-20250626104445501"></p><p>点击“授予权限”，会报错。这个是由于回调 URL 不匹配的问题，还是会访问 ENV 查看器的 URL：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626104502680.png" alt="image-20250626104502680"></p><hr><h3 id="手动修改回调-URL"><a href="#手动修改回调-URL" class="headerlink" title="手动修改回调 URL"></a>手动修改回调 URL</h3><p>然后我们手动把上边的 URL 改成我们自己的回调路由就可以了，如果想自动化，你也可以写一个油猴脚本。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250626104618044.png" alt="image-20250626104618044"></p><hr><p>这样就可以完成本地的 OIDC 授权流程啦。开发的时候不用搭 IDP，也能走懒猫的登录流程。是不是很方便？</p>]]></content>
    
    
    <summary type="html">本地开发环境接入懒猫微服 OIDC 单点登录的配置方法。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="单点登录" scheme="https://blog.no-claw.com/tags/%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>Lighting 原生不支持OTG！</title>
    <link href="https://blog.no-claw.com/posts/d969a069/"/>
    <id>https://blog.no-claw.com/posts/d969a069/</id>
    <published>2025-06-25T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我还留着 Lighting 的设备，由于大疆 pocket3 最近更新了直连手机文件管理器的功能，所以试试。</p><p>用了 Apple 的原装线连接 pocket3 和 iPhone 结果没反应，但是在 IPAD 上就能够成正常连接。</p><p>然后换了移动硬盘连接 IPhone 同样也不行，和 IPhone 的售后 battle 了好久对方也说不明白。最后还是 Apple 论坛上找找到一个帖子，</p><p>Lighting 原生不支持 OTG，还得买转接头，这算盘打的真响。</p><span id="more"></span><p><a href="https://discussionschinese.apple.com/thread/252659031?=undefined&previousThread=255169304321&sortBy=rank">https://discussionschinese.apple.com/thread/252659031?=undefined&amp;previousThread=255169304321&amp;sortBy=rank</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624115019794.png" alt="image-20250624115019794"></p><p>最后感谢欧盟，要求 Apple 更换 Typec 接口，解决了 USB2.0 的问题，也可以和安卓设备一样用高速传输了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;我还留着 Lighting 的设备，由于大疆 pocket3 最近更新了直连手机文件管理器的功能，所以试试。&lt;/p&gt;
&lt;p&gt;用了 Apple 的原装线连接 pocket3 和 iPhone 结果没反应，但是在 IPAD 上就能够成正常连接。&lt;/p&gt;
&lt;p&gt;然后换了移动硬盘连接 IPhone 同样也不行，和 IPhone 的售后 battle 了好久对方也说不明白。最后还是 Apple 论坛上找找到一个帖子，&lt;/p&gt;
&lt;p&gt;Lighting 原生不支持 OTG，还得买转接头，这算盘打的真响。&lt;/p&gt;</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十五）：大疆Pocket3 素材导入懒猫网盘</title>
    <link href="https://blog.no-claw.com/posts/50f8f5d2/"/>
    <id>https://blog.no-claw.com/posts/50f8f5d2/</id>
    <published>2025-06-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>日常使用 Pocket3 拍摄视频，就是共享比较麻烦，不然就得每个手机安装一个 app，然后再导出。突发奇想可以把素材传到懒猫网盘中，这样就解决了这个痛点。</p><p>大疆的机器每次连接都要走这个流程，不得不说真的很麻烦。这个 Wi-Fi 的记忆功能比较鸡肋。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624122938093.png" alt="image-20250624122938093"></p><p>连接之后需要右上角先把视频下载到手机本地，然后点击分享。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123556578.png" alt="image-20250624123556578"></p><p>这里可以选各种软件，比如 airdrop，微信，邮件。我这里选懒猫微服，点击之后就会跳转到懒猫网盘。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123606895.png" alt="image-20250624123606895"></p><p>第一次传输我发现速度慢的离谱。询问了售后才发现 ios 会默认在 wifi 网络不好时走流量的。</p><p>因为大疆的 pocket3 传输需要连接相机的 Wi-Fi。所以手机是整个一断网额的状态。这流量也不快。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123055526.png" alt="image-20250624123055526"></p><p>偷偷跑流量是手机和运行商的传统了，那么在蜂窝网络里给他关掉。关掉无线局域网助理。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123117094.png" alt="image-20250624123117094"></p><p>关了之后，再重复上边的操作，就发现懒猫网盘打不开了，嗯 这就是预期的行为了，不会偷偷的用网了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123004993.png" alt="image-20250624123004993"></p><p>于是询问大疆额售后能不能让机器连接家里的网，得到的回答是不行。只能手动切换 Wi-Fi。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123022647.png" alt="image-20250624123022647"></p><p>换了网之后再传输，这个速度就舒服多了，虽然没跑满千兆，但是也能够看了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123032634.png" alt="image-20250624123032634"></p><p>然后把上一步的视频文件夹共享出来，以后把素材都发到这个文件夹里。再共享给其他的懒猫用户，就很方便了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624123207647.png" alt="image-20250624123207647"></p><blockquote><p>两个很想吐槽的地方</p><ol><li>大疆 pocket3 不能直接连家里 Wi-Fi，传文件到网盘必须换网</li><li>Apple 的 lighting 原生不支持 OTG，需要买转接头。</li></ol></blockquote>]]></content>
    
    
    <summary type="html">大疆 Pocket3 拍摄素材导入懒猫网盘，解决多设备共享视频的痛点。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服排查篇（二）：上架应用后，pip 安装报错 HASH 不一致</title>
    <link href="https://blog.no-claw.com/posts/cf7f5d3/"/>
    <id>https://blog.no-claw.com/posts/cf7f5d3/</id>
    <published>2025-06-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫 ENV 查看器第二版更新的时候，审核人员和我说遇到了这个错误。说来也奇怪，都用了 docker 了，也会遇到依赖的问题。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/9bf75c39-b39e-4d35-8031-45f96fae5e3a.png" alt="6c779ad7537d89eb4bd9e40c0d69b7d8.png" title="6c779ad7537d89eb4bd9e40c0d69b7d8.png"></p><p><a href="https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env">https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env</a></p><blockquote><p>ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE<br>Expected sha256 4ceb…<br>Got 5519987f…</p></blockquote><span id="more"></span><p>因为 pip 在校验阶段就失败，后面的 Flask 等依赖都没装上，于是程序启动时报 ModuleNotFoundError: No module named ‘flask’。</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换到当前目录</span></span><br><span class="line"><span class="built_in">cd</span> <span class="string">&quot;<span class="subst">$(dirname <span class="string">&quot;<span class="variable">$0</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">sed -i <span class="string">&#x27;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g&#x27;</span> /etc/apk/repositories</span><br><span class="line">apk update</span><br><span class="line">apk add python3 py3-pip</span><br><span class="line"><span class="comment"># 设一条主索引（可选）</span></span><br><span class="line">pip config <span class="built_in">set</span> global.index-url https://pypi.tuna.tsinghua.edu.cn/simple</span><br><span class="line"><span class="comment"># 给同一个键追加多条 extra-index-url</span></span><br><span class="line">pip config <span class="built_in">set</span> global.extra-index-url https://mirrors.aliyun.com/pypi/simple/</span><br><span class="line">pip config <span class="built_in">set</span> global.extra-index-url https://repo.huaweicloud.com/repository/pypi/simple/</span><br><span class="line">pip config <span class="built_in">set</span> global.extra-index-url https://mirrors.cloud.tencent.com/pypi/simple/</span><br><span class="line">pip install -r ./requirements.txt --break-system-packages</span><br><span class="line">python3 app.py</span><br></pre></td></tr></table></figure><p>网上还有几种办法，后面再遇到的时候可以再尝试：</p><ol><li>–no-cache-dir</li><li>pip cache purge</li><li>pip install –trusted-host&#x3D;pypi.org –trusted-host&#x3D;files.pythonhosted.org example_package</li><li>rm ~&#x2F;.cache&#x2F;pip -rf</li><li>关闭机器代理</li></ol><p>参考链接：</p><p><a href="https://stackoverflow.com/questions/71435874/pip-these-packages-do-not-match-the-hashes-from-the-requirements-file">https://stackoverflow.com/questions/71435874/pip-these-packages-do-not-match-the-hashes-from-the-requirements-file</a></p>]]></content>
    
    
    <summary type="html">排查懒猫微服上架应用后 pip 安装时 HASH 校验不一致的问题</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="排查" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%8E%92%E6%9F%A5/"/>
    
    
    <category term="Python" scheme="https://blog.no-claw.com/tags/Python/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 索引关闭与重开全攻略：open close 操作、批量处理及防误操作配置</title>
    <link href="https://blog.no-claw.com/posts/af48f3e/"/>
    <id>https://blog.no-claw.com/posts/af48f3e/</id>
    <published>2025-06-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Easysearch（兼容 Elasticsearch 的搜索引擎）中，索引是存储和查询的基本单元。默认情况下，索引是处于 <code>open</code> 状态的，可以正常写入和搜索。当你暂时不使用某些索引，但又不想删除它们时，可以通过 <code>close</code> 操作来关闭索引，从而释放部分内存资源。</p><hr><h2 id="📊-查看索引状态"><a href="#📊-查看索引状态" class="headerlink" title="📊 查看索引状态"></a>📊 查看索引状态</h2><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 _cat/indices?v</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">POST abc/_doc</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;a&quot;</span>: 1</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>此时你会看到索引 <code>abc</code> 已创建，并处于 <code>open</code> 状态：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/2aceb6d717390a7c8df6ac76c45b748d.png" alt="索引 open 状态"></p><p>默认每个索引有 1 个主分片、1 个副本分片，且为可读写状态。</p><hr><h2 id="🔒-关闭索引"><a href="#🔒-关闭索引" class="headerlink" title="🔒 关闭索引"></a>🔒 关闭索引</h2><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">POST abc/_close</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;acknowledged&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;shards_acknowledged&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;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;abc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;closed&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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="🚫-关闭后的行为限制"><a href="#🚫-关闭后的行为限制" class="headerlink" title="🚫 关闭后的行为限制"></a>🚫 关闭后的行为限制</h2><p>关闭索引后，不仅不能写入，<strong>连搜索都无法进行</strong>。</p><h3 id="🔍-搜索已关闭索引（403-错误）"><a href="#🔍-搜索已关闭索引（403-错误）" class="headerlink" title="🔍 搜索已关闭索引（403 错误）"></a>🔍 搜索已关闭索引（403 错误）</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">GET abc/_search</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cluster_block_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index [abc] blocked by: [FORBIDDEN/4/index closed];&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;status&quot;</span><span class="punctuation">:</span> <span class="number">403</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/f1edce36572eb74cd2a9e0fdb8a04223.png" alt="关闭后搜索报错"></p><hr><h3 id="📝-写入已关闭索引（400-错误）"><a href="#📝-写入已关闭索引（400-错误）" class="headerlink" title="📝 写入已关闭索引（400 错误）"></a>📝 写入已关闭索引（400 错误）</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></pre></td><td class="code"><pre><span class="line">POST abc/_doc</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;a&quot;</span>: 2</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><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;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index_closed_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;closed&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc&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;status&quot;</span><span class="punctuation">:</span> <span class="number">400</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/bbad6f804fbbdbc5b7849c2f3c34864d.png" alt="关闭后写入报错"></p><hr><h2 id="✳️-批量关闭索引（支持通配符）"><a href="#✳️-批量关闭索引（支持通配符）" class="headerlink" title="✳️ 批量关闭索引（支持通配符）"></a>✳️ 批量关闭索引（支持通配符）</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST ab*,<span class="built_in">test</span>/_close</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;acknowledged&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;shards_acknowledged&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;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;closed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;abd&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;closed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;abc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;closed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</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><p><img src="https://i-blog.csdnimg.cn/img_convert/4448625bea11f429679f4ca32e28c2e0.png" alt="批量关闭成功"></p><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 _cat/indices?v</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/8ce15dcd81a81351aab9dce010749dea.png" alt="索引已关闭"></p><hr><h2 id="🔓-重新打开索引"><a href="#🔓-重新打开索引" class="headerlink" title="🔓 重新打开索引"></a>🔓 重新打开索引</h2><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">POST */_open</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;acknowledged&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;shards_acknowledged&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><p><img src="https://i-blog.csdnimg.cn/img_convert/b9fe797a08c2fb1b6bf183b04633c8a8.png" alt="打开索引成功"></p><hr><h2 id="⚙️-禁止关闭索引的集群配置"><a href="#⚙️-禁止关闭索引的集群配置" class="headerlink" title="⚙️ 禁止关闭索引的集群配置"></a>⚙️ 禁止关闭索引的集群配置</h2><p>有些场景中（如运营平台防止误操作），管理员可能会<strong>禁止索引关闭操作</strong>。设置如下：</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></pre></td><td class="code"><pre><span class="line">PUT _cluster/settings</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cluster.indices.close.enable&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><br><span class="line"><span class="punctuation">&#125;</span></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><span class="line">13</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;acknowledged&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;persistent&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cluster&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;close&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="string">&quot;false&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</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="attr">&quot;transient&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/a180ec9eea33c1a3027762d56a92fea8.png" alt="设置禁止关闭索引"></p><hr><h2 id="🧯-禁止后关闭索引会报错"><a href="#🧯-禁止后关闭索引会报错" class="headerlink" title="🧯 禁止后关闭索引会报错"></a>🧯 禁止后关闭索引会报错</h2><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><span class="line">13</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;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_state_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;closing indices is disabled - set [cluster.indices.close.enable: true] to enable it. NOTE: closed indices still consume a significant amount of diskspace&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_state_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;closing indices is disabled - set [cluster.indices.close.enable: true] to enable it. NOTE: closed indices still consume a significant amount of diskspace&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;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="🔍-如何确认关闭被禁用？"><a href="#🔍-如何确认关闭被禁用？" class="headerlink" title="🔍 如何确认关闭被禁用？"></a>🔍 如何确认关闭被禁用？</h2><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 _cluster/settings</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cluster&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;close&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="string">&quot;false&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</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="attr">&quot;index_state_management&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;template_migration&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;control&quot;</span><span class="punctuation">:</span> <span class="string">&quot;-1&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="attr">&quot;rollup&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;search&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="string">&quot;true&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;hours_before&quot;</span><span class="punctuation">:</span> <span class="string">&quot;24&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="attr">&quot;transient&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="✅-总结"><a href="#✅-总结" class="headerlink" title="✅ 总结"></a>✅ 总结</h2><table><thead><tr><th>操作</th><th>是否支持</th><th>条件</th></tr></thead><tbody><tr><td><code>POST /&lt;index&gt;/_close</code></td><td>✅ 默认支持</td><td>除非设置 <code>cluster.indices.close.enable: false</code></td></tr><tr><td><code>POST /&lt;index&gt;/_open</code></td><td>✅ 总是支持</td><td>无需额外开启</td></tr><tr><td><code>POST ab*/_close</code></td><td>✅ 支持批量关闭</td><td>同上</td></tr><tr><td>查看关闭限制配置</td><td><code>GET _cluster/settings?include_defaults=true</code></td><td></td></tr></tbody></table><p>关闭索引适用于资源控制、调试排查等场景，但要注意：<strong>关闭索引仍会占用磁盘空间，不会释放存储</strong>，仅仅是节省内存和 CPU 资源。</p>]]></content>
    
    
    <summary type="html">Easysearch 索引 open/close 操作详解，含批量处理与防误操作配置</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服开发篇（零）：上架应用需要哪些知识</title>
    <link href="https://blog.no-claw.com/posts/c7acbc13/"/>
    <id>https://blog.no-claw.com/posts/c7acbc13/</id>
    <published>2025-06-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫微服的可玩性在于可以让开发人员放开手脚来做一些事情，等于是提供了一个可靠的基础设施。那么理所当然我们可以把开源的知识应用到上面，比如开发或者移植应用，或者干脆部署一些好玩的东西。这在传统 NAS 上实现起来很困难，甚至都没有包管理工具。</p><p>我们看一看开发懒猫应用，需要什么样的知识？</p><p>那么，开发懒猫微服的应用需要掌握哪些技能呢？</p><h3 id="NPM"><a href="#NPM" class="headerlink" title="NPM"></a>NPM</h3><p>懒猫微服的 CLI 本质上是一个通过 NPM 全局安装的工具包，因此掌握一些基本的 NPM 使用方法是必要的。</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">npm install -g @lazycatcloud/lzc-cli</span><br></pre></td></tr></table></figure><span id="more"></span><p>这个工具是用 JavaScript 编写的，但如果你只是为了使用而非开发，那么并不需要掌握这门语言。当然，你也可以选择使用 pnpm 或 yarn 作为包管理工具，或者通过 NVM 来创建 Node.js 虚拟环境。</p><p>如果在 macOS&#x2F;Linux 上遇到了权限不足的问题，其实不一定要使用 <code>sudo</code>。默认情况下，npm 的全局目录是 <code>/usr/local</code>，普通用户对其没有写权限。比如我们可以看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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"> ll /usr/</span><br><span class="line">total 0</span><br><span class="line">drwxr-xr-x  918 root  wheel    29K  6  5 14:05 bin/</span><br><span class="line">drwxr-xr-x   32 root  wheel   1.0K  6  5 14:05 lib/</span><br><span class="line">drwxr-xr-x  417 root  wheel    13K  6  5 14:05 libexec/</span><br><span class="line">drwxr-xr-x    8 root  wheel   256B  6 10 09:31 local/</span><br><span class="line">drwxr-xr-x  230 root  wheel   7.2K  6  5 14:05 sbin/</span><br><span class="line">drwxr-xr-x   43 root  wheel   1.3K  6  5 14:05 share/</span><br><span class="line">drwxr-xr-x    5 root  wheel   160B  6  5 14:05 standalone/</span><br><span class="line">lrwxr-xr-x    1 root  wheel    25B  6  5 14:05 X11@ -&gt; ../private/var/select/X11</span><br><span class="line">lrwxr-xr-x    1 root  wheel    25B  6  5 14:05 X11R6@ -&gt; ../private/var/select/X11</span><br></pre></td></tr></table></figure><p>因此我们可以通过设置 npm 的全局安装目录，规避权限问题。在当前用户目录中创建一个文件夹并添加到环境变量中即可：</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"> npm config get prefix</span><br><span class="line">/usr/local</span><br><span class="line"><span class="built_in">mkdir</span> ~/.npm_packages</span><br><span class="line">npm config <span class="built_in">set</span> prefix ~/.npm_packages/</span><br><span class="line">npm config get prefix</span><br><span class="line">/Users/home/.npm_packages</span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> PATH=~/.npm-global/bin:<span class="variable">$PATH</span></span><br></pre></td></tr></table></figure><p>开发的技能是可选的，如果你只是移植现有的应用的话，那么具备一些 Docker Compose 的知识就足够了，这个我们后面再说。</p><p>如果是开发原创 APP 的话，那么无论是 Vue，React，Go，Python 都有用武之地，只要是 Web 的应用能够本地运行或者打包成 Docker 就能上架商店。相信很多开发的小伙伴也会做一些 Devops 的事情，这部分的技能是可以完全迁移过来的。</p><h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><p>很多 NAS 是基于 FreeBSD 或者 Linux 改的，懒猫微服是基于 debian12， 虽然在设计之初是针对非专业玩家。但是后来也开放了 SSH，可以做和其他 Linux 一样的事情，给了 root 用户，所以可以底层操作文件，网络，查看分区，监控，以及系统负载。</p><p>所以不是只有树莓派或者自己笔电装机才能学 Linux，懒猫微服的系统重启之后会复原（除了 root 目录和网络设置），所以请随便折腾。</p><p>虽然有了一套很完善的图形客户端，但是相信很多专业的玩家还是更加喜欢用传统 Linux 的方式来看待这个微服，我管他叫做拆解系统设计。</p><p>举个例子：用 <code>htop</code> 查看负载、用 <code>nmtui</code> 配置网络、用 <code>lsblk</code> 查看磁盘分区、用 <code>systemctl</code> 设置服务自启。深度定制的系统，让我们可以完全无视内核，以及 grub 的这些东西。甚至连 sambda，webdav 这些 server 都不用自己安装。</p><h3 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h3><p>Docker 好像对 NAS 玩家是必须的，无论是群晖，威联通。与传统 NAS 不一样的是，懒猫微服集成了三套 docker，分别是系统组件，playground 和应用商店。</p><p>playground 就是我们刻板印象的 Docker， 这里叫做<code>pg-docker</code>，所以需要懂一些 Docker 的知识，比如下载，打包，上传，还有数据卷的贡献。甚至包括 Docker- compose 的使用。</p><p>应用商店也是基于 Docker 运行的，</p><p>上架软件时有两种方式：</p><ol><li><p><strong>直接打包</strong>：这个一般用于原创应用或者移植开源无 docker 版本的应用。调试的时候可以使用懒猫内置的 Docker Registry 的 image 进行测试，颇有 VS code remote 的风格。这个调试模式叫做 devshell。</p></li><li><p><strong>Docker 镜像迁移</strong>：一般用于已有的 docker image 的迁移，由于国内出海宽带不足，访问 Docker 经常失败。所以需要使用懒猫提供的 Docker Registry 来做一个国内版本的镜像。然后再做目录的映射。</p></li></ol><h3 id="OIDC"><a href="#OIDC" class="headerlink" title="OIDC"></a>OIDC</h3><p>这个稍稍有点跑题，前面的都是传统 Devops 需要的东西。这里的 OIDC 叫做 OpenID Connect，是单点登录实现的一种。传统的认证有基于 cookie 的，或者基于 JWT 的。OIDC 是后者，也是单点登录中最优雅的实现。除了 OIDC 之外，你可能听说过 SAML，Oauth，其实也都是 Single Sign-On 的不同实现，而 Oauth 是和 OpenID Connect 源同一脉，Oauth 的各家实现千差万别，而 OpenID Connect 既统一了规范，解决用户态的问题。换句话说 OAuth 2.0 只是用来授权，颁发的是<code>Access Token</code>，而对于访问者是谁还需要开发人员自己存数据库。OIDC 则是引入了<code>ID Token</code>，这通常是通常是 JWT，所以认证直接请求 IDP 解码就好了。大致是这个流程：</p><p>下图是 OIDC 的基本流程：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250623222439761.png" alt="image-20250623222439761"></p><h4 id="能够学到哪些知识："><a href="#能够学到哪些知识：" class="headerlink" title="能够学到哪些知识："></a>能够学到哪些知识：</h4><ol><li>微服内部的官网看起来是根据 OpenResty 改的（个人推断），所以可以来复习一下 nginx 或者 OpenResty 相关的知识</li><li>Docker 的使用，容器这几年还是挺火的，移植应用必备，甚至还支持 web VNC。</li><li>HTTP 知识：有些情况需要对 http 的请求做特殊的处理，比如加一些自定义 header 或者 cookies</li><li>单点登录：微服内置了 OIDC 的认证，应用能够自动帮助我们申请 CLIENT_ID 和 CLIENT_SECRET，简化了和 IDP 打交道的环节。</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>如果你熟悉 Web 开发、Docker 和基本的 Linux 操作，那么你已经可以快速上手懒猫微服的应用开发。无论是移植开源项目，还是开发原创 App，只要能够在本地运行或打包为 Docker 镜像，就可以顺利上架到应用商店。</p><p>懒猫微服不仅仅是一个面向普通用户的 NAS 系统，更是一块为开发者打造的自由试验田 —— 它就是一台稳定可靠的 Debian 云主机，你可以在上面尽情发挥创意与技术。</p>]]></content>
    
    
    <summary type="html">懒猫微服应用上架前需要掌握的知识清单，从 Docker 到应用打包。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="开发" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（九）：商店 App 如何接管 Docker 引擎？</title>
    <link href="https://blog.no-claw.com/posts/e8e61ce7/"/>
    <id>https://blog.no-claw.com/posts/e8e61ce7/</id>
    <published>2025-06-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的内容中，我们提到过懒猫微服采用三套独立的 Docker 环境来隔离系统组件、Playground Docker 与商店 App 的 Docker 实例。那么问题来了：<strong>如何让商店中上架的 App 操作 Playground 中的 Docker 引擎？</strong></p><p>答案是：<strong>通过挂载 <code>docker.sock</code> 文件来实现跨容器控制。</strong></p><p>所以我们可以在商店的 APP 中操作 playground docker，其实也就是 Docker 面板或者轻量 Docker 面板做的事情。</p><p>为什么不操作其他两个 Docker 引擎？</p><ul><li>系统组件 Docker 无需干预，重启之后可以复原。</li><li>应用商店有自己的生命周期，也无需干预。</li></ul><hr><h3 id="一、在-build-yml-中挂载-Playground-路径"><a href="#一、在-build-yml-中挂载-Playground-路径" class="headerlink" title="一、在 build.yml 中挂载 Playground 路径"></a>一、在 <code>build.yml</code> 中挂载 Playground 路径</h3><p>首先，在打包配置 <code>build.yml</code> 中新增 <code>services</code> 字段，用于将宿主机中的 <code>/data/playground</code> 挂载到容器内部：</p><span id="more"></span><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="attr">manifest:</span> <span class="string">./lzc-manifest.yml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">pkgout:</span> <span class="string">./</span></span><br><span class="line"><span class="attr">icon:</span> <span class="string">./logo.png</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">containly:</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">bind:</span></span><br><span class="line">          <span class="attr">create_host_path:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">/data/playground</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/lzcapp/run/playground</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">bind</span></span><br></pre></td></tr></table></figure><p>打包后会生成一个名为 <code>compose.override.yml</code> 的文件。<strong>请注意：即使你手动创建了 <code>compose.override.yml</code>，也可能无法直接生效，必须通过打包流程自动生成。</strong>（此结论基于初步测试）</p><p>生成后的 <code>compose.override.yml</code> 内容如下：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">containly:</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">bind:</span></span><br><span class="line">          <span class="attr">create_host_path:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">/data/playground</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/lzcapp/run/playground</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">bind</span></span><br></pre></td></tr></table></figure><hr><h3 id="二、修改-manifest-yml-实现-docker-sock-映射"><a href="#二、修改-manifest-yml-实现-docker-sock-映射" class="headerlink" title="二、修改 manifest.yml 实现 docker.sock 映射"></a>二、修改 <code>manifest.yml</code> 实现 <code>docker.sock</code> 映射</h3><p>为了让上架 App 操作 Docker，需要手动编辑 <code>manifest.yml</code> 文件，添加以下内容：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="attr">binds:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/lzcapp/run/playground/docker.sock:/var/run/docker.sock</span></span><br><span class="line"><span class="attr">environment:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOCKGE_STACKS_DIR=/lzcapp/var/stacks</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOCKER_HOST=unix:///lzcapp/run/playground/docker.sock</span></span><br></pre></td></tr></table></figure><p>这样，容器内的 Docker CLI 或管理面板就可以通过 <code>DOCKER_HOST</code> 环境变量，控制宿主机的 Docker 引擎。</p><hr><h3 id="三、完整的-manifest-yml-示例"><a href="#三、完整的-manifest-yml-示例" class="headerlink" title="三、完整的 manifest.yml 示例"></a>三、完整的 <code>manifest.yml</code> 示例</h3><p>以下是完整可运行的 <code>manifest.yml</code> 配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">lzc-sdk-version:</span> <span class="number">0.1</span></span><br><span class="line"><span class="attr">package:</span> <span class="string">xu.deploy.containly</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.0</span><span class="number">.2</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">Containly</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">&gt;-</span></span><br><span class="line"><span class="string">  A fancy, easy-to-use and reactive self-hosted docker compose.yaml</span></span><br><span class="line"><span class="string">  stack-oriented manager.</span></span><br><span class="line"><span class="string"></span><span class="attr">license:</span> <span class="string">https://choosealicense.com/licenses/mit/</span></span><br><span class="line"><span class="attr">homepage:</span> <span class="string">https://github.com/cloudsmithy/Containly</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">xu</span></span><br><span class="line"><span class="attr">usage:</span> <span class="string">&gt;-</span></span><br><span class="line"><span class="string">  安装完成后，请重启懒猫微服以启用 Docker。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">  <span class="string">此应用将接管懒猫微服的独立</span> <span class="string">Docker</span> <span class="string">守护进程，可能存在安全风险。在授予容器</span> <span class="string">privileged</span></span><br><span class="line">  <span class="string">等权限之前，请确保容器是安全的，且不会执行危险操作。为了避免潜在风险，请确保：</span></span><br><span class="line"></span><br><span class="line">  <span class="number">1</span><span class="string">.</span> <span class="string">您了解容器的行为，并确认它们来自可信的源。</span></span><br><span class="line">  <span class="number">2</span><span class="string">.</span> <span class="string">容器中没有运行高危命令，且没有暴露不必要的端口或服务。</span></span><br><span class="line"></span><br><span class="line">  <span class="string">建议先查阅懒猫微服开发者手册，了解相关特性和限制，并根据手册中的安全建议配置容器。</span></span><br><span class="line"><span class="attr">application:</span></span><br><span class="line">  <span class="attr">subdomain:</span> <span class="string">containly</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/=http://containly.xu.deploy.containly.lzcapp:5000/</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">containly:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/containly:896f4251373d0ebe</span></span><br><span class="line">    <span class="attr">binds:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/lzcapp/run/playground/docker.sock:/var/run/docker.sock</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DOCKGE_STACKS_DIR=/lzcapp/var/stacks</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DOCKER_HOST=unix:///lzcapp/run/playground/docker.sock</span></span><br></pre></td></tr></table></figure><hr><h3 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h3><p>通过挂载 <code>docker.sock</code> 文件和设置 <code>DOCKER_HOST</code>，我们可以让商店上架的 App 控制懒猫微服的 Playground Docker 实例。我用这个功能上架了自己写的 Docker 面板，一起来玩一玩嘛？</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250520104141112.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服商店应用如何接管和管理 Docker 引擎的机制解析。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>MacOS 26 beta 如何恢复启动台</title>
    <link href="https://blog.no-claw.com/posts/83eb1399/"/>
    <id>https://blog.no-claw.com/posts/83eb1399/</id>
    <published>2025-06-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>苹果最新版的系统使用了年份命名，主要是毛玻璃风格，虽然毛玻璃用了几天慢慢习惯了。</p><p>另一个槽点是 apple 把启动台去掉了，改成了 apps，用搜索栏统一搜索。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/afe4c7a5c5be827826a5052bef99b15d.png" alt="afe4c7a5c5be827826a5052bef99b15d"></p><p>除了检索应用，也能检索邮件和文件什么的，属于是一键搜索了。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f2ffc0aca969718696707c309990f94a.png" alt="f2ffc0aca969718696707c309990f94a"></p><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"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> -p /Library/Preferences/FeatureFlags/Domain</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> defaults write /Library/Preferences/FeatureFlags/Domain/SpotlightUI.plist SpotlightPlus -dict Enabled -bool <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>GPT 解释如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">#### 第 1 行：</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">sudo mkdir -p /Library/Preferences/FeatureFlags/Domain</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">* 创建一个目录用于配置 **macOS 内部功能标志（FeatureFlags）**。</span><br><span class="line">* `-p` 参数确保中间目录不存在时也一并创建。</span><br><span class="line"></span><br><span class="line">#### 第 2 行：</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">sudo defaults write /Library/Preferences/FeatureFlags/Domain/SpotlightUI.plist SpotlightPlus -dict Enabled -bool false</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">* 使用 macOS `defaults` 命令写入系统级别的配置文件。</span><br><span class="line">* `SpotlightUI.plist` 是控制 Spotlight（聚焦搜索）的功能标志文件。</span><br><span class="line">* `SpotlightPlus` 是 macOS 14 Sonoma 起的一个实验性 Spotlight 增强功能。</span><br><span class="line">* `Enabled -bool false` 意思是**禁用 Spotlight Plus（新版搜索界面）**。</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">| 💻 macOS Sonoma 用户 | 禁用默认启用的“Spotlight Plus”功能，恢复旧版 Spotlight |</span><br><span class="line">| 🧪 实验功能控制          | macOS 通过隐藏的 feature flag 启用或禁用某些 GUI 功能  |</span><br><span class="line">| ⚙️ 解决兼容问题          | 有些用户觉得新版 Spotlight 太慢或不兼容快捷键脚本，想禁用       |</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">1. 更改后可能需要 **重启系统或注销登录** 才能生效。</span><br><span class="line">2. 如果系统升级，Apple 可能会重置该设置。</span><br><span class="line">3. 这种方法是“非公开支持”的隐藏设置，未来 macOS 可能废弃此路径。</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">如果你想恢复 Spotlight Plus，只需执行：</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">sudo defaults delete /Library/Preferences/FeatureFlags/Domain/SpotlightUI.plist SpotlightPlus</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">或者将 `-bool true` 写回去。</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">如你还有其他想隐藏或禁用的 macOS 功能（比如 Siri、Stage Manager、控制中心某模块），我可以帮你查找对应的 `defaults write` 配置。需要吗？</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>重启之后我们的启动台就回来了：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/dbd2d68cadcb6d80d8df9cbfe240dace.png" alt="dbd2d68cadcb6d80d8df9cbfe240dace"></p>]]></content>
    
    
    <summary type="html">macOS 26 beta 升级后启动台异常的恢复方法。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十四）：不登录客户端，如何用 Samba 挂载懒猫微服网盘？</title>
    <link href="https://blog.no-claw.com/posts/86ecba8b/"/>
    <id>https://blog.no-claw.com/posts/86ecba8b/</id>
    <published>2025-06-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫微服网盘自带自动挂载 Samba 功能，但如果<strong>不登录客户端</strong>，也可以手动通过微服的私有地址来挂载，实现访问和高速传输。</p><p>由于懒猫网盘采用<strong>多租户架构</strong>，每位用户的数据是隔离的。因此，访问路径通常为：</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">smb://&lt;ip&gt;/&lt;用户名&gt;</span><br></pre></td></tr></table></figure><h2 id="🧩-步骤一：开启内网访问服务"><a href="#🧩-步骤一：开启内网访问服务" class="headerlink" title="🧩 步骤一：开启内网访问服务"></a>🧩 步骤一：开启内网访问服务</h2><p>在开始挂载前，<strong>请确保你已在懒猫微服后台开启了“内网访问服务”</strong>，否则 SMB 连接会被拒绝。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621230834943.png" alt="image-20250621230834943"></p><hr><span id="more"></span><h2 id="💡-回忆一波上机考试的方式"><a href="#💡-回忆一波上机考试的方式" class="headerlink" title="💡 回忆一波上机考试的方式"></a>💡 回忆一波上机考试的方式</h2><p>以前在上机考试时，老师会将题目放在服务器共享目录中，我们用 <code>Win + R</code> 输入 <code>\\IP地址</code> 来下载资料，当时还觉得这操作很高端。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621224004995.png" alt="image-20250621224004995"></p><p><strong>解释：</strong><br>这是通过 <code>Win + R</code> 快捷键打开“运行”窗口，输入 <code>\\&lt;IP&gt;</code> 快速访问局域网 SMB 共享目录。通常用于临时打开文件夹，不做映射。</p><hr><h2 id="🐧-macOS-Linux-挂载方式（CLI）"><a href="#🐧-macOS-Linux-挂载方式（CLI）" class="headerlink" title="🐧 macOS&#x2F;Linux 挂载方式（CLI）"></a>🐧 macOS&#x2F;Linux 挂载方式（CLI）</h2><p>在 Linux 或 macOS 上，可以直接用以下地址挂载：</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">smb://&lt;ip&gt;/Download</span><br></pre></td></tr></table></figure><p>或者通过 <code>mount.cifs</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> mount -t cifs //192.168.1.100/your-username /mnt/share -o user=your-username,password=your-password</span><br></pre></td></tr></table></figure><hr><h2 id="🪟-Windows-上手动挂载-SMB-网盘"><a href="#🪟-Windows-上手动挂载-SMB-网盘" class="headerlink" title="🪟 Windows 上手动挂载 SMB 网盘"></a>🪟 Windows 上手动挂载 SMB 网盘</h2><p>在 Windows 中，需要通过图形界面手动挂载为本地磁盘，操作如下：</p><h3 id="第一步：右键“此电脑”-→-映射网络驱动器"><a href="#第一步：右键“此电脑”-→-映射网络驱动器" class="headerlink" title="第一步：右键“此电脑” → 映射网络驱动器"></a>第一步：右键“此电脑” → <strong>映射网络驱动器</strong></h3><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621224142450.png" alt="image-20250621224142450"></p><p><strong>解释：</strong><br>在这里你可以：</p><ul><li>选择盘符（建议使用末尾的 <code>Z:</code>, <code>Y:</code>, <code>X:</code> 等）；</li><li>输入共享路径（例如 <code>\\192.168.1.100\your-username</code>）；</li><li>可勾选“使用其他凭据连接”等选项。</li></ul><hr><h3 id="第二步：确认挂载路径并验证身份"><a href="#第二步：确认挂载路径并验证身份" class="headerlink" title="第二步：确认挂载路径并验证身份"></a>第二步：确认挂载路径并验证身份</h3><p>系统将提示输入用户名和密码：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621224120769.png" alt="image-20250621224120769"></p><hr><h3 id="第三步：挂载成功，查看-Z-盘内容"><a href="#第三步：挂载成功，查看-Z-盘内容" class="headerlink" title="第三步：挂载成功，查看 Z 盘内容"></a>第三步：挂载成功，查看 Z 盘内容</h3><p>成功后，可以在“此电脑”中看到挂载好的 SMB 网络盘：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621230132415.png" alt="image-20250621230132415"></p><p>你可以像操作本地硬盘一样打开、拖拽、复制文件。</p><hr><h3 id="打开挂载目录后的界面如下："><a href="#打开挂载目录后的界面如下：" class="headerlink" title="打开挂载目录后的界面如下："></a>打开挂载目录后的界面如下：</h3><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250621230747150.png" alt="image-20250621230747150"></p><hr><h2 id="🚀-实测传输速度：约-500MB-s"><a href="#🚀-实测传输速度：约-500MB-s" class="headerlink" title="🚀 实测传输速度：约 500MB&#x2F;s"></a>🚀 实测传输速度：约 500MB&#x2F;s</h2><p>我测试了一下，将文件从 SMB 网盘拖入 PVE 虚拟机，传输速度稳定在 <strong>500MB&#x2F;s</strong>，表现不错。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bed30da7272c40a1203b34b834269b13.png" alt="bed30da7272c40a1203b34b834269b13"></p>]]></content>
    
    
    <summary type="html">不登录懒猫客户端，通过 Samba 协议直接挂载懒猫微服网盘。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="Samba" scheme="https://blog.no-claw.com/tags/Samba/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服排查篇（一）：403 Forbidden！记应用商店 pip 使用清华源的报错</title>
    <link href="https://blog.no-claw.com/posts/c20c194c/"/>
    <id>https://blog.no-claw.com/posts/c20c194c/</id>
    <published>2025-06-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>由于自身调试 OIDC 需要，写了一个懒猫 ENV 查看器，然后发到商店里给大家使用&#x2F;</p><p>打包的时候偷了个懒，直接打包的没有用容器，然后其中一位用户就遇到这个问题：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/bcd8d32b6f8193da16e2e456be0efb63.png" alt="bcd8d32b6f8193da16e2e456be0efb63"></p><p>要了下日志，结果是发现访问清华源有问题，被清华源直接返回了 403，这个问题还比较稀奇，毕竟在我的印象里清华源一直都很稳。</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">-------------logs:-------------</span><br><span class="line">app-1  | 2025-06-23T14:28:36.813573986Z Start Child Process: [sh -c ./lzcapp/pkg/content/run.sh]</span><br><span class="line">app-1  | 2025-06-23T14:28:36.813587080Z PATH:&quot;/&quot; is served by &quot;exec&quot;://&quot;5005,./lzcapp/pkg/content/run.sh&quot;</span><br><span class="line">app-1  | 2025-06-23T14:28:36.818585835Z fetch https://mirrors.ustc.edu.cn/alpine/v3.20/main/x86_64/APKINDEX.tar.gz</span><br><span class="line">app-1  | 2025-06-23T14:28:36.997192042Z fetch https://mirrors.ustc.edu.cn/alpine/v3.20/community/x86_64/APKINDEX.tar.gz</span><br><span class="line">app-1  | 2025-06-23T14:28:37.251288967Z 2025/06/23 22:28:37 http: proxy error: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:37.251446934Z 2025/06/23 22:28:37 http: proxy error: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:37.444530406Z v3.20.6-201-gd57c9d7d1c9 [https://mirrors.ustc.edu.cn/alpine/v3.20/main]</span><br><span class="line">app-1  | 2025-06-23T14:28:37.444552892Z v3.20.6-203-g66ce3a1d0dc [https://mirrors.ustc.edu.cn/alpine/v3.20/community]</span><br><span class="line">app-1  | 2025-06-23T14:28:37.444560548Z OK: 24177 distinct packages available</span><br><span class="line">app-1  | 2025-06-23T14:28:37.732800630Z (1/25) Installing libbz2 (1.0.8-r6)</span><br><span class="line">app-1  | 2025-06-23T14:28:37.834399822Z (2/25) Installing libexpat (2.7.0-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:37.978333761Z (3/25) Installing libffi (3.4.6-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:37.996681317Z (4/25) Installing gdbm (1.23-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.023808099Z (5/25) Installing xz-libs (5.6.2-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.059202322Z (6/25) Installing libgcc (13.2.1_git20240309-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.120270779Z (7/25) Installing libstdc++ (13.2.1_git20240309-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.219766620Z (8/25) Installing mpdecimal (4.0.0-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.244870690Z (9/25) Installing ncurses-terminfo-base (6.4_p20240420-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.266752323Z (10/25) Installing libncursesw (6.4_p20240420-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.301269438Z (11/25) Installing libpanelw (6.4_p20240420-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.318329841Z (12/25) Installing readline (8.2.10-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.474049984Z (13/25) Installing sqlite-libs (3.45.3-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:38.628199528Z (14/25) Installing python3 (3.12.11-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:39.316803498Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:39.367766399Z (15/25) Installing python3-pycache-pyc0 (3.12.11-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:39.753346082Z (16/25) Installing pyc (3.12.11-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:39.770701744Z (17/25) Installing py3-setuptools-pyc (70.3.0-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:39.929343816Z (18/25) Installing py3-pip-pyc (24.0-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.207250136Z (19/25) Installing py3-parsing (3.1.2-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.304655931Z (20/25) Installing py3-parsing-pyc (3.1.2-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.317188019Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:40.529573664Z (21/25) Installing py3-packaging-pyc (24.0-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.618998893Z (22/25) Installing python3-pyc (3.12.11-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.653772503Z (23/25) Installing py3-packaging (24.0-r1)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.830130888Z (24/25) Installing py3-setuptools (70.3.0-r0)</span><br><span class="line">app-1  | 2025-06-23T14:28:40.995056226Z (25/25) Installing py3-pip (24.0-r2)</span><br><span class="line">app-1  | 2025-06-23T14:28:41.318293072Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:41.764687778Z Executing busybox-1.36.1-r29.trigger</span><br><span class="line">app-1  | 2025-06-23T14:28:41.768553945Z OK: 75 MiB in 42 packages</span><br><span class="line">app-1  | 2025-06-23T14:28:42.318842615Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:42.994899875Z Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple</span><br><span class="line">app-1  | 2025-06-23T14:28:43.192401924Z Collecting Flask (from -r ./requirements.txt (line 1))</span><br><span class="line">app-1  | 2025-06-23T14:28:43.219653783Z   ERROR: HTTP error 403 while getting https://pypi.tuna.tsinghua.edu.cn/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl (from https://pypi.tuna.tsinghua.edu.cn/simple/flask/) (requires-python:&gt;=3.9)</span><br><span class="line">app-1  | 2025-06-23T14:28:43.219966823Z ERROR: Could not install requirement Flask from https://pypi.tuna.tsinghua.edu.cn/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl (from -r ./requirements.txt (line 1)) because of HTTP error 403 Client Error: Forbidden for url: https://pypi.tuna.tsinghua.edu.cn/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl for URL https://pypi.tuna.tsinghua.edu.cn/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl (from https://pypi.tuna.tsinghua.edu.cn/simple/flask/) (requires-python:&gt;=3.9)</span><br><span class="line">app-1  | 2025-06-23T14:28:43.319551942Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:43.578023168Z Traceback (most recent call last):</span><br><span class="line">app-1  | 2025-06-23T14:28:43.578037493Z   File &quot;/lzcapp/pkg/content/app.py&quot;, line 2, in &lt;module&gt;</span><br><span class="line">app-1  | 2025-06-23T14:28:43.578038656Z     from flask import Flask, request, render_template</span><br><span class="line">app-1  | 2025-06-23T14:28:43.578039625Z ModuleNotFoundError: No module named &#x27;flask&#x27;</span><br><span class="line">app-1  | 2025-06-23T14:28:43.581234895Z ChildProcess exit: exit status 1</span><br><span class="line">app-1  | 2025-06-23T14:28:44.320074235Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:45.320738420Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:46.321124378Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:47.322143472Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:48.322911993Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:49.323128356Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:50.323783719Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:51.323982245Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:52.324934294Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:53.325225164Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:54.325889637Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:55.326592221Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:56.326903845Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:57.327197993Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br><span class="line">app-1  | 2025-06-23T14:28:58.327666585Z WAIT ./lzcapp/pkg/content/run.sh 127.0.0.1:5005 ERR: dial tcp 127.0.0.1:5005: connect: connection refused</span><br></pre></td></tr></table></figure><p>GPT 查询了下，可能就是 IP 给限制了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250624094657148.png" alt="image-20250624094657148"></p><p>打开清华的 Pypi 的页面，看到这位的 IP 确实被清华拦截了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b189989aae74fe2bfbe23c812c1cd3ca.png" alt="b189989aae74fe2bfbe23c812c1cd3ca"></p><p>为了防止这个情况，有两个办法：</p><ol><li>直接使用 Docker 做好镜像，这样就无关软件源</li><li>可以同时设置其他 pypi 源：</li></ol><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">pip config <span class="built_in">set</span> global.extra-index-url <span class="string">&quot;https://mirrors.aliyun.com/pypi/simple/ https://repo.huaweicloud.com/repository/pypi/simple/ https://mirrors.cloud.tencent.com/pypi/simple/&quot;</span> </span><br></pre></td></tr></table></figure><p>然后可以使用 pip config list 查看,能看到我这个是走了腾讯云的：</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></pre></td><td class="code"><pre><span class="line">pip install pandas</span><br><span class="line">Looking <span class="keyword">in</span> indexes: https://pypi.org/simple, https://mirrors.aliyun.com/pypi/simple/, https://repo.huaweicloud.com/repository/pypi/simple/, https://mirrors.cloud.tencent.com/pypi/simple/</span><br><span class="line">Collecting pandas</span><br><span class="line">  Downloading https://mirrors.cloud.tencent.com/pypi/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl (10.7 MB)</span><br><span class="line">     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.7/10.7 MB 3.6 MB/s eta 0:00:00</span><br><span class="line">Requirement already satisfied: numpy&gt;=1.26.0 <span class="keyword">in</span> /opt/miniconda3/lib/python3.12/site-packages (from pandas) (1.26.4)</span><br><span class="line">Requirement already satisfied: python-dateutil&gt;=2.8.2 <span class="keyword">in</span> /opt/miniconda3/lib/python3.12/site-packages (from pandas) (2.9.0.post0)</span><br><span class="line">Requirement already satisfied: pytz&gt;=2020.1 <span class="keyword">in</span> /opt/miniconda3/lib/python3.12/site-packages (from pandas) (2025.1)</span><br><span class="line">Requirement already satisfied: tzdata&gt;=2022.7 <span class="keyword">in</span> /opt/miniconda3/lib/python3.12/site-packages (from pandas) (2025.1)</span><br><span class="line">Requirement already satisfied: six&gt;=1.5 <span class="keyword">in</span> /opt/miniconda3/lib/python3.12/site-packages (from python-dateutil&gt;=2.8.2-&gt;pandas) (1.17.0)</span><br><span class="line">Installing collected packages: pandas</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">排查懒猫微服应用商店中 pip 使用清华源时出现 403 报错的解决过程</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="排查" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%8E%92%E6%9F%A5/"/>
    
    
    <category term="Python" scheme="https://blog.no-claw.com/tags/Python/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>Amazon OpenSearch Service 现在支持 JSON Web Token（JWT）身份验证和授权</title>
    <link href="https://blog.no-claw.com/posts/330e574/"/>
    <id>https://blog.no-claw.com/posts/330e574/</id>
    <published>2025-06-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近，Amazon OpenSearch 推出了一个新功能，支持 JWT 认证和授权。虽然这个功能在开源的 OpenSearch 中早已存在，但在托管的 Amazon OpenSearch 中的实现一直不够理想。</p><span id="more"></span><h3 id="此前的授权方式"><a href="#此前的授权方式" class="headerlink" title="此前的授权方式"></a>此前的授权方式</h3><h4 id="控制台登录"><a href="#控制台登录" class="headerlink" title="控制台登录"></a>控制台登录</h4><ol><li><strong>内部数据库</strong>：使用基本的用户名和密码进行 HTTP 验证。如果切换到其他认证方式（如 IAM 或 SAML），此验证方式将被禁用。</li><li><strong>IAM 主用户</strong>：实际上是通过 Cognito 进行验证。由于中国区没有用户池，设置为 IAM 作为主用户故无法使用。</li><li><strong>SAML 单点登录</strong>：与 SAML 身份提供商（如 Azure AD、Auth0）集成。SAML 身份验证仅能在浏览器中访问 OpenSearch Dashboard，开启 SAML 后会禁用基本的 HTTP 验证。</li></ol><h4 id="编程方式"><a href="#编程方式" class="headerlink" title="编程方式"></a>编程方式</h4><p>对于 SDK 而言，可以通过在 HTTP 请求中携带用户名和密码，或使用 SignV4 携带 IAM 身份信息进行认证。</p><p>常见的解决方案包括：</p><ol><li>控制台和 SDK 都使用内部数据库的主用户进行基本 HTTP 验证。</li><li>控制台使用内部数据库或 SAML 凭证登录 OpenSearch Dashboard，然后在权限认证中给 IAM 身份单独授权访问索引，这样编程方式就可以使用 SignV4 的签名算法访问集群资源。</li></ol><h3 id="JWT-与-OIDC"><a href="#JWT-与-OIDC" class="headerlink" title="JWT 与 OIDC"></a>JWT 与 OIDC</h3><h4 id="JWT-验证流程"><a href="#JWT-验证流程" class="headerlink" title="JWT 验证流程"></a>JWT 验证流程</h4><ol><li>客户端请求：客户端向服务器发出登录请求，提供用户凭证（例如用户名和密码）。</li><li>服务器验证凭证：服务器验证用户凭证的有效性。</li><li>生成 JWT：如果凭证有效，服务器生成一个包含用户身份信息和其他声明的 JWT，并使用服务器的私钥签名。</li><li>返回 JWT：服务器将签名的 JWT 返回给客户端。</li><li>客户端存储 JWT：客户端收到 JWT 后，将其存储在本地存储或 cookie 中，以便在后续请求中使用。</li><li>携带 JWT 的请求：客户端在每次请求时将 JWT 包含在 HTTP 请求头中（通常是 Authorization: Bearer <JWT>）。</li><li>服务器验证 JWT：服务器接收到请求后，提取并解析 JWT，验证其签名、有效期和其他声明的合法性。</li><li>处理请求：如果 JWT 验证通过，服务器处理请求并返回响应；如果验证失败，返回 401 或 403 错误。</li></ol><p><img src="https://i-blog.csdnimg.cn/blog_migrate/0ca0441fec14f7ca2d2790908c8b751a.png" alt="在这里插入图片描述"></p><h4 id="OIDC-验证流程"><a href="#OIDC-验证流程" class="headerlink" title="OIDC 验证流程"></a>OIDC 验证流程</h4><p>OpenID Connect（OIDC）是在 OAuth 2.0 协议之上构建的一个身份层，用于实现单点登录（SSO）和身份验证。以下是 OIDC 的详细验证流程：</p><ol><li>客户端请求身份认证：客户端向身份提供者（IdP）发送身份认证请求，包含 client_id、redirect_uri、scope、response_type 和 state 参数。</li><li>用户身份验证：身份提供者显示登录界面，用户输入凭证进行身份验证。</li><li>同意授权：用户登录成功后，身份提供者可能会显示同意授权页面。</li><li>返回授权码：用户同意授权后，身份提供者重定向客户端到 redirect_uri，并附带一个授权码。</li><li>交换授权码：客户端使用授权码向身份提供者的 Token 端点发送请求，以交换 access token 和 ID token。</li><li>返回令牌：身份提供者验证授权码后，返回 access token 和 ID token。ID token 是一个包含用户身份信息的 JWT。</li><li>验证 ID token：客户端接收到 ID token 后，验证其签名、声明合法性和过期时间。</li><li>使用令牌：客户端使用 access token 访问受保护资源，并解码 ID token 中的用户身份信息。</li></ol><p><img src="https://i-blog.csdnimg.cn/blog_migrate/2370bdde2b917cfdfdac4c5f0e7acf84.png" alt="在这里插入图片描述"></p><h3 id="OpenSearch-的-JWT-认证授权"><a href="#OpenSearch-的-JWT-认证授权" class="headerlink" title="OpenSearch 的 JWT 认证授权"></a>OpenSearch 的 JWT 认证授权</h3><p>2024 年 6 月 19 日 Amazon OpenSearch 在全球区上线了 JWT 认证授权功能，中国区的北京和宁夏区域的此功能在 2024 年 6 月 23 日上线控制台增加了 JWT authentication and authorization 功能，启用此功能需要开启精细访问控制，并导入验证 JWT 有效性的证书。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/9717422ccac90a4b758b96f11de01fa9.png" alt="在这里插入图片描述"><br><img src="https://i-blog.csdnimg.cn/blog_migrate/2e8ca062f82f779a4612b5b5aa635572.png" alt="在这里插入图片描述"></p><h4 id="配置-Auth0"><a href="#配置-Auth0" class="headerlink" title="配置 Auth0"></a>配置 Auth0</h4><p>配置 JWT 认证授权的步骤包括在 IDP 中创建 API，并使用 API 获取 JWT。以下是使用 Auth0 生成 JWT 的示例代码：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line">AUTH0_DOMAIN = <span class="string">&quot;&lt;domain&gt;.auth0.com&quot;</span></span><br><span class="line">CLIENT_ID = <span class="string">&quot;&lt;CLIENT_ID&gt;&quot;</span></span><br><span class="line">CLIENT_SECRET = <span class="string">&quot;&lt;CLIENT_SECRET&gt;&quot;</span></span><br><span class="line">AUDIENCE = <span class="string">&quot;https://auth0-jwt-authorize&quot;</span></span><br><span class="line">GRANT_TYPE = <span class="string">&quot;client_credentials&quot;</span></span><br><span class="line">TOKEN_URL = <span class="string">f&quot;https://<span class="subst">&#123;AUTH0_DOMAIN&#125;</span>/oauth/token&quot;</span></span><br><span class="line">OUTPUT_FILE_PATH = <span class="string">&#x27;jwt_token.json&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 请求负载和头部</span></span><br><span class="line">payload = &#123;</span><br><span class="line">    <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">    <span class="string">&quot;client_secret&quot;</span>: CLIENT_SECRET,</span><br><span class="line">    <span class="string">&quot;audience&quot;</span>: AUDIENCE,</span><br><span class="line">    <span class="string">&quot;grant_type&quot;</span>: GRANT_TYPE</span><br><span class="line">&#125;</span><br><span class="line">headers = &#123;</span><br><span class="line">    <span class="string">&quot;content-type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发送 POST 请求获取 JWT</span></span><br><span class="line">response = requests.post(TOKEN_URL, json=payload, headers=headers)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理响应</span></span><br><span class="line"><span class="keyword">if</span> response.status_code == <span class="number">200</span>:</span><br><span class="line">    data = response.json()</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(OUTPUT_FILE_PATH, <span class="string">&#x27;w&#x27;</span>) <span class="keyword">as</span> file:</span><br><span class="line">        json.dump(data, file, indent=<span class="number">4</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;JWT 已保存到文件: <span class="subst">&#123;OUTPUT_FILE_PATH&#125;</span>&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;请求失败，状态码：<span class="subst">&#123;response.status_code&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(response.text)</span><br></pre></td></tr></table></figure><p>我们在 Auth0 中新建一个 API，然后会帮我们生成一个 Application。后续我们会使用这个 Application 的 Client ID 和 Secret ID 以及 Domain 的信息来登录。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/2f652bc95370df9c93021c19d6b2c75e.png" alt="在这里插入图片描述"><br>也就是说这三个信息确定了一个身份池，然后符合规则的用户可以通过这个身份池来换取 JWT。可以在 Applications-Applications 中看到。</p><p>配置好之后，可以通过 Auth0 的 API 来拿到登录后的 JWT，以下是一个官方给的教程可以用来测试功能，当然也可以集成到 APP 中。</p><p>auth0 也提供了示例代码供我们测试：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/a5dec4ae3ac0d8c568560643c7cbe9e7.png" alt="在这里插入图片描述"><br>官方提供代码样例可读性不是很高，让我们用用 requests 来改写一下，这个代码会把生成的 JWT 存在一个 Json 文件里面，这样我们就能容易的复制出来。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line">AUTH0_DOMAIN = <span class="string">&quot;&lt;domain&gt;.auth0.com&quot;</span></span><br><span class="line">CLIENT_ID = <span class="string">&quot;&lt;CLIENT_ID&gt;&quot;</span></span><br><span class="line">CLIENT_SECRET = <span class="string">&quot;&lt;CLIENT_SECRET&gt;&quot;</span></span><br><span class="line">AUDIENCE = <span class="string">&quot;https://auth0-jwt-authorize&quot;</span></span><br><span class="line">GRANT_TYPE = <span class="string">&quot;client_credentials&quot;</span></span><br><span class="line">TOKEN_URL = <span class="string">f&quot;https://<span class="subst">&#123;AUTH0_DOMAIN&#125;</span>/oauth/token&quot;</span></span><br><span class="line">OUTPUT_FILE_PATH = <span class="string">&#x27;jwt_token.json&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 请求负载和头部</span></span><br><span class="line">payload = &#123;</span><br><span class="line">    <span class="string">&quot;client_id&quot;</span>: CLIENT_ID,</span><br><span class="line">    <span class="string">&quot;client_secret&quot;</span>: CLIENT_SECRET,</span><br><span class="line">    <span class="string">&quot;audience&quot;</span>: AUDIENCE,</span><br><span class="line">    <span class="string">&quot;grant_type&quot;</span>: GRANT_TYPE</span><br><span class="line">&#125;</span><br><span class="line">headers = &#123;</span><br><span class="line">    <span class="string">&quot;content-type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发送POST请求获取JWT</span></span><br><span class="line">response = requests.post(TOKEN_URL, json=payload, headers=headers)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理响应</span></span><br><span class="line"><span class="keyword">if</span> response.status_code == <span class="number">200</span>:</span><br><span class="line">    data = response.json()</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(OUTPUT_FILE_PATH, <span class="string">&#x27;w&#x27;</span>) <span class="keyword">as</span> file:</span><br><span class="line">        json.dump(data, file, indent=<span class="number">4</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;JWT已保存到文件: <span class="subst">&#123;OUTPUT_FILE_PATH&#125;</span>&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;请求失败，状态码：<span class="subst">&#123;response.status_code&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(response.text)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们可以看到控制台多出了一个 JWT authentication and authorization 新功能，使用这个功能需要先开启精细访问控制，我们需要在这里需要导入验证 JWT 有效性的证书。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/0197167461b3194268825beee866e6ee.png" alt="在这里插入图片描述"></p><p>服务端需要填写验证 JWT 的 PEM 证书，那么我们要从 Auth0 的 API 中拿到这个信息。使用如下代码从.well-known&#x2F;jwks.json 中解析出来需要的证书，然后填写到 OpenSearch 中。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> jwcrypto <span class="keyword">import</span> jwk</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line">JWKS_URI = <span class="string">&#x27;https://&lt;domain&gt;/.well-known/jwks.json&#x27;</span>  <span class="comment"># 替换为你的Auth0域名</span></span><br><span class="line">OUTPUT_DIR = <span class="string">&#x27;./pem_keys&#x27;</span>  <span class="comment"># 你希望保存PEM公钥的目录</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建输出目录</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(OUTPUT_DIR):</span><br><span class="line">    os.makedirs(OUTPUT_DIR)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取JWKS</span></span><br><span class="line">response = requests.get(JWKS_URI)</span><br><span class="line">jwks = response.json()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理每一个JWK</span></span><br><span class="line"><span class="keyword">for</span> index, jwk_key <span class="keyword">in</span> <span class="built_in">enumerate</span>(jwks[<span class="string">&#x27;keys&#x27;</span>]):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        key = jwk.JWK(**jwk_key)</span><br><span class="line">        pem = key.export_to_pem(private_key=<span class="literal">False</span>, password=<span class="literal">None</span>).decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">        kid = jwk_key[<span class="string">&#x27;kid&#x27;</span>]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 将PEM格式公钥写入文件</span></span><br><span class="line">        pem_file_path = os.path.join(OUTPUT_DIR, <span class="string">f&#x27;<span class="subst">&#123;kid&#125;</span>.pem&#x27;</span>)</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(pem_file_path, <span class="string">&#x27;w&#x27;</span>) <span class="keyword">as</span> pem_file:</span><br><span class="line">            pem_file.write(pem)</span><br><span class="line"></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&#x27;PEM格式公钥已保存到文件: <span class="subst">&#123;pem_file_path&#125;</span>&#x27;</span>)</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&#x27;处理公钥时出错: <span class="subst">&#123;e&#125;</span>&#x27;</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>配置好后，可以在 OpenSearch 的安全配置中看到 “View JWT details” 信息，验证 JWT 的有效性。通过标准的 JWT 流程使用 Postman 验证时，将 JWT 输入到 Bearer token 中，即可进行验证。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/11581d01b998d4e844e32c16c8019d9f.png" alt="在这里插入图片描述"></p><h4 id="postman-测试-JWT"><a href="#postman-测试-JWT" class="headerlink" title="postman 测试 JWT"></a>postman 测试 JWT</h4><p>然后我们按照标准的 JWT 流程进行验证，这里使用 Postman，验证方式使用 Bear token，我们把通过应用程序模拟的 JWT 输入进去。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/fef0ead3467fe57326c4baf4141e2fa9.png" alt="在这里插入图片描述"></p><h4 id="编程方式访问-Python"><a href="#编程方式访问-Python" class="headerlink" title="编程方式访问(Python)"></a>编程方式访问(Python)</h4><p>我们也可以使用编程方式来进行访问，其实就是上加上一个’Authorization’: ‘Bearer <jwt token>‘的请求头。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;OpenSearch URL&quot;</span></span><br><span class="line"></span><br><span class="line">payload = &#123;&#125;</span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">&#x27;Bearer &lt;jwt token&gt;&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;GET&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br></pre></td></tr></table></figure><p>几个月前，我参与了一些内部功能的审核工作。当时认为，既然系统支持 JWT，那么这应该也意味着支持 OIDC IDP，并且能够解决中国区 Cognito 无法集成的问题。然而，通过测试发现，JWT 仅能在编程方式中使用，控制台仍然需要使用原来的认证方式。也就是说，预想的从控制台跳转到 OIDC IDP 的方式仍然无法实现。期待未来能够实现从控制台无缝跳转到 OIDC IDP 的目标，为用户提供更便捷和安全的使用体验。</p><p>参考文档<br>【1】<a href="https://aws.amazon.com/cn/about-aws/whats-new/2024/06/amazon-opensearch-service-jwt-authentication-authorization/">https://aws.amazon.com/cn/about-aws/whats-new/2024/06/amazon-opensearch-service-jwt-authentication-authorization/</a></p><p>【2】<a href="https://www.amazonaws.cn/new/2024/amazon-opensearch-service-supports-json-web-token-authentication-and-authorization/">https://www.amazonaws.cn/new/2024/amazon-opensearch-service-supports-json-web-token-authentication-and-authorization/</a></p><p>【3】<a href="https://docs.aws.amazon.com/zh_cn/opensearch-service/latest/developerguide/JSON-Web-tokens.html">https://docs.aws.amazon.com/zh_cn/opensearch-service/latest/developerguide/JSON-Web-tokens.html</a></p>]]></content>
    
    
    <summary type="html">介绍 Amazon OpenSearch Service 新增的 JWT 身份验证与授权功能</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>半小时学会 Amazon Transit Gateway 跨 VPC 网络互联配置</title>
    <link href="https://blog.no-claw.com/posts/b9af2b64/"/>
    <id>https://blog.no-claw.com/posts/b9af2b64/</id>
    <published>2025-06-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Amazon Transit Gateway (TGW) 是一个强大的网络连接服务，用于在不同的 VPC（虚拟私有云）之间实现高效互联。本文将指导您如何创建和配置 TGW，以便实现跨账户和跨区域的 VPC 互联。</p><h3 id="VPC-Peering-的局限性"><a href="#VPC-Peering-的局限性" class="headerlink" title="VPC Peering 的局限性"></a>VPC Peering 的局限性</h3><p><strong>点对点连接</strong>：VPC Peering 是一个点对点的连接，每次只能连接两个 VPC。如果需要连接多个 VPC，需要为每对 VPC 单独设置 Peering 连接，也就是我们常说的不能进行路由的传递，需要打通的 VPC 很多的时候会非常的麻烦。</p><span id="more"></span><p><strong>手动路由配置</strong>：每个 VPC Peering 连接都需要手动配置路由表，这在大规模环境下非常繁琐。</p><h4 id="TGW-的优势"><a href="#TGW-的优势" class="headerlink" title="TGW 的优势"></a>TGW 的优势</h4><p><strong>集中式管理</strong>：TGW 作为一个中央枢纽，允许多个 VPC 和本地网络通过单个网关相互连接，简化了网络架构和管理。</p><p><strong>自动路由传播</strong>：TGW 支持自动路由传播，简化了路由配置，减少了人为错误的风险。</p><p><strong>跨账户和跨区域支持</strong>：TGW 支持跨多个亚马逊云科技账户和跨区域的连接，提供更大的灵活性和扩展性。</p><p>总结下来说，TGW 就是是一个中转网关，使用时候需要在需要打通的 VPC 内创建一个挂载点，TGW 会管理一张路由表来决定流量的转发到对应的挂载点上。本质上是 EC2 的请求路由到 TGW，然后在查询 TGW 的路由表来再来决定下一跳，所以需要同时修改 VPC 内子网的路由表和 TGW 的路由表。</p><p>TGW 的网络拓扑图如下：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/725dc106ded8fa6428a48cc6bf20d1d1.png" alt="在这里插入图片描述"></p><h3 id="1-创建-TGW"><a href="#1-创建-TGW" class="headerlink" title="1. 创建 TGW"></a>1. 创建 TGW</h3><p>登录到亚马逊云科技管理控制台，导航到”VPC”服务。<br>在左侧菜单中选择”Transit Gateways”，点击”Create Transit Gateway”。<br>填写 TGW 名称和描述，配置 DNS 支持等选项。</p><p>根据要求创建 TGW，如果不需要和本地网络打通，这里填写名称和描述就好。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/526162c3c12656cb9b59fcd755b0a080.png"></p><p>建议开启以下三个选项：</p><p>DNS support：开启打通 VPC 的 DNS 支持，这个 DNS support 无法解析对端的私有 R53 记录，还需要使用 Resolver 才行 [^1]</p><p>Default route table association：自动创建一个路由表并且关联这个 TGW</p><p>Default route table propagation：自动路由表自动传播，这样每次更新的时候就不用手动管理路由。</p><h3 id="2-在每个-VPC-新建挂载点"><a href="#2-在每个-VPC-新建挂载点" class="headerlink" title="2. 在每个 VPC 新建挂载点"></a>2. 在每个 VPC 新建挂载点</h3><p>在 TGW 创建完成后，导航到”Transit Gateway Attachments”。<br>点击”Create Transit Gateway Attachment”，选择目标 VPC 并配置相关选项。</p><p>创建挂载点需要选择关联的 TGW 以及挂载点的 Type，除了 VPC 之外还有 peering，DX 类型的可供选择。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/f6f20f7a98607b975d5e48590acec9f2.png"></p><p>同样这里也要开启对 DNS 的支持，另外关于 Appliance Mode support，如果这个功能开启的话，流量只能在相同的可用区进行转发，这个功能开启需要慎重考虑。</p><h3 id="3-设置-TGW-路由"><a href="#3-设置-TGW-路由" class="headerlink" title="3. 设置 TGW 路由"></a>3. 设置 TGW 路由</h3><p>手动新建 TGW 的路由表并且关联到一个 TGW，如果前面开启了 Default route table association 和 Default route table propagation 不再需要此步骤。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/fe0bfdcccd14c2694e46dbd5cab24921.png"></p><p>需要在 Routes 部分手动添加路由规则</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/98506c132b5f22087bdb79d3ee7e034c.png"></p><h3 id="4-设置子网路由"><a href="#4-设置子网路由" class="headerlink" title="4. 设置子网路由"></a>4. 设置子网路由</h3><p>为每个 VPC 配置路由表，添加到 TGW 的路由。确保启用路由传播，使 VPC 可以通过 TGW 相互通信。</p><p>和 peering 一一样，需要把对应的流量指到对端，这里 10.1.0.0&#x2F;16 的流量到 TGW。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/189191718ded45bfd296fb4b345bd12f.png"></p><h3 id="5-跨账户-RAM-分享，对端-RAM-接收"><a href="#5-跨账户-RAM-分享，对端-RAM-接收" class="headerlink" title="5. 跨账户 RAM 分享，对端 RAM 接收"></a>5. 跨账户 RAM 分享，对端 RAM 接收</h3><p>如果需要跨账户打通网络，那么需要用到 TGW 的 share 功能，其实就是使用 RAM 进行资源共享。</p><p>如果需要跨账户共享 TGW，使用 AWS Resource Access Manager (RAM)。</p><p>在 RAM 控制台中创建资源共享并邀请其他 AWS 账户。</p><p>对方也是需要在 RAM 里进行确认，并且接收方不能二次 share 此 TGW。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/f228631cba10ecb1ed96ef3c60d7f6a4.png"></p><p>拓展阅读：</p><ol><li>Building a global network using Amazon Transit Gateway Inter-Region peering</li></ol><p><a href="https://aws.amazon.com/cn/blogs/networking-and-content-delivery/building-a-global-network-using-aws-transit-gateway-inter-region-peering/">https://aws.amazon.com/cn/blogs/networking-and-content-delivery/building-a-global-network-using-aws-transit-gateway-inter-region-peering/</a></p><ol start="2"><li>Amazon Transit Gateway now supports Inter-Region Peering</li></ol><p><a href="https://aws.amazon.com/about-aws/whats-new/2019/12/aws-transit-gateway-supports-inter-region-peering/">https://aws.amazon.com/about-aws/whats-new/2019/12/aws-transit-gateway-supports-inter-region-peering/</a></p><ol start="3"><li>Transit Gateway inter-Region peering</li></ol><p><a href="https://docs.aws.amazon.com/solutions/latest/network-orchestration-aws-transit-gateway/transit-gateway-inter-region-peering.html">https://docs.aws.amazon.com/solutions/latest/network-orchestration-aws-transit-gateway/transit-gateway-inter-region-peering.html</a></p><ol start="4"><li>Amazon Transit Gateway - Amazon Virtual Private Cloud Connectivity Options</li></ol><p><a href="https://docs.aws.amazon.com/whitepapers/latest/aws-vpc-connectivity-options/aws-transit-gateway.html">https://docs.aws.amazon.com/whitepapers/latest/aws-vpc-connectivity-options/aws-transit-gateway.html</a></p><ol start="5"><li>Centralized DNS management of hybrid cloud with Amazon Route 53 and AWS Transit Gateway</li></ol><p><a href="https://aws.amazon.com/cn/blogs/networking-and-content-delivery/centralized-dns-management-of-hybrid-cloud-with-amazon-route-53-and-aws-transit-gateway/">https://aws.amazon.com/cn/blogs/networking-and-content-delivery/centralized-dns-management-of-hybrid-cloud-with-amazon-route-53-and-aws-transit-gateway/</a></p><p>通过这些文档，可以全面了解 TGW 在跨区域连接中的显著优势，确保在大规模和复杂网络环境中的高效、安全和可扩展性。</p>]]></content>
    
    
    <summary type="html">半小时学会 Amazon Transit Gateway 跨 VPC 网络互联配置</summary>
    
    
    
    <category term="网络" scheme="https://blog.no-claw.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E7%BD%91%E7%BB%9C/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>基于 AWS IAM Identity Center 的 SAML 配置，实现单点登录阿里云</title>
    <link href="https://blog.no-claw.com/posts/511c83d9/"/>
    <id>https://blog.no-claw.com/posts/511c83d9/</id>
    <published>2025-06-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在企业多云环境日益普及的今天，很多组织不仅使用 <strong>AWS（亚马逊云科技）</strong> 作为主要的计算与存储平台，同时也会使用 <strong>阿里云</strong> 来满足本地合规、地域性需求或价格优势。如何在多个云平台之间实现 <strong>统一身份认证</strong>，避免用户维护多个账号与密码，已经成为企业安全与运维中的核心问题。</p><p><strong>AWS IAM Identity Center</strong>（身份中心，原 AWS SSO）作为 AWS 官方提供的集中式身份认证与访问管理服务，可以作为企业的 <strong>主身份提供商（IdP）</strong>。通过 <strong>SAML 2.0 协议</strong>，它能够将认证结果传递给其他云服务商（如阿里云），让用户在 AWS 完成一次身份验证后，直接进入阿里云控制台，而无需再次登录。这就是所谓的 <strong>跨云单点登录（Single Sign-On, SSO）</strong>。</p><span id="more"></span><p>本文将结合详细步骤与截图，完整演示如何配置 <strong>AWS IAM Identity Center 与阿里云 RAM 的 SAML 对接</strong>。</p><h3 id="在-AWS-IAM-Identity-Center-新建用户"><a href="#在-AWS-IAM-Identity-Center-新建用户" class="headerlink" title="在 AWS IAM Identity Center 新建用户"></a>在 AWS IAM Identity Center 新建用户</h3><p>在正式配置 SAML 对接之前，我们需要先在 AWS IAM Identity Center 中创建用户。</p><ol><li>登录 AWS 控制台，进入 <strong>IAM Identity Center</strong> 页面。</li><li>选择 <strong>Users → Add user</strong>。</li><li>填写 <strong>用户名、密码、姓名和邮箱</strong>。<ul><li>用户名：用于登录 AWS SSO 的唯一标识。</li><li>姓名：通常填写用户的真实姓名，方便后续在阿里云匹配用户。</li><li>邮箱：AWS 会将登录邀请邮件发送到该邮箱。</li><li>密码：可以由管理员生成初始密码，也可以让用户自行设置。</li></ul></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20251003112401516.png" alt="image.png"></p><h3 id="创建应用并选择-SAML"><a href="#创建应用并选择-SAML" class="headerlink" title="创建应用并选择 SAML"></a>创建应用并选择 SAML</h3><p>在用户创建完成后，需要在 AWS IAM Identity Center 中配置一个 <strong>应用（Application）</strong>，它代表与阿里云之间的对接关系。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%201.png" alt="image.png"></p><p>操作步骤：</p><ol><li>打开 <strong>Applications → Add application</strong>。</li><li>在应用类型中选择 <strong>SAML 2.0</strong>。</li><li>将此应用与前面创建的用户或用户组进行绑定。</li></ol><p>📌 <strong>说明</strong>：</p><ul><li>应用相当于一个桥梁，AWS 通过它来生成 SAML 断言。</li><li>如果未来还要接入其他云服务（如 Salesforce、Office 365），也需要新建对应应用。</li><li>建议统一命名规范，例如：<code>AlibabaCloud-SAML</code>。</li></ul><h3 id="OAuth-与-SAML-的区别"><a href="#OAuth-与-SAML-的区别" class="headerlink" title="OAuth 与 SAML 的区别"></a>OAuth 与 SAML 的区别</h3><p>在配置过程中，你可能注意到 AWS 提供了多种协议选项，其中最常见的就是 <strong>OAuth 2.0</strong> 与 <strong>SAML 2.0</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%202.png" alt="image.png"></p><p>📌 <strong>区别解析</strong>：</p><table><thead><tr><th>对比项</th><th>SAML</th><th>OAuth</th></tr></thead><tbody><tr><td>核心用途</td><td>单点登录（SSO），跨云、跨域身份认证</td><td>应用授权，第三方应用获取资源</td></tr><tr><td>数据格式</td><td>XML</td><td>JSON</td></tr><tr><td>常见场景</td><td>企业员工访问云平台、内部系统</td><td>微信&#x2F;Google 登录第三方应用</td></tr><tr><td>安全特性</td><td>强调身份认证 + 授权</td><td>强调令牌授权，不负责身份本身</td></tr><tr><td>适合对象</td><td>企业 IT 管理，多云环境</td><td>C 端互联网应用</td></tr></tbody></table><h3 id="下载-AWS-IAM-Identity-Center-的-SAML-元数据-XML"><a href="#下载-AWS-IAM-Identity-Center-的-SAML-元数据-XML" class="headerlink" title="下载 AWS IAM Identity Center 的 SAML 元数据 XML"></a>下载 AWS IAM Identity Center 的 SAML 元数据 XML</h3><p>在创建好应用后，AWS 会为我们生成一个 <strong>元数据 XML 文件</strong>，其中包含：</p><ul><li>AWS IAM Identity Center 的 <strong>端点 URL</strong>；</li><li>公钥证书信息；</li><li>支持的协议与绑定方式。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%203.png" alt="image.png"></p><p>📌 <strong>操作方法</strong>：</p><ol><li>在应用详情页找到 <strong>IAM SSO URL</strong>。</li><li>点击 <strong>Download metadata XML</strong>。</li><li>保存到本地，稍后需要上传到阿里云。</li></ol><p>⚠️ <strong>注意事项</strong>：</p><ul><li>下载后请勿修改 XML 文件内容，否则会导致签名校验失败。</li><li>建议妥善保存，并在企业内部文档中记录文件版本。</li><li>如果更换证书，需要重新下载并更新到阿里云。</li></ul><h3 id="在阿里云导入身份提供商"><a href="#在阿里云导入身份提供商" class="headerlink" title="在阿里云导入身份提供商"></a>在阿里云导入身份提供商</h3><p>接下来，切换到 <strong>阿里云 RAM 控制台</strong>，新建一个 <strong>身份提供商（Identity Provider）</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%207.png" alt="image.png"><br>📌 <strong>步骤</strong>：</p><ol><li>进入 <strong>访问控制（RAM） → 身份提供商</strong>。</li><li>选择 <strong>SAML 类型</strong>。</li><li>上传刚才下载的 AWS XML 文件。</li></ol><p>在阿里云中，也提供了一个默认的 SAML 元数据地址：<br>👉 <a href="https://signin.aliyun.com/saml-role/sp-metadata.xml">https://signin.aliyun.com/saml-role/sp-metadata.xml</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%204.png" alt="image.png"></p><p>但在本场景下，我们需要导入 <strong>AWS 提供的 XML</strong>，因为 AWS 是 IdP，阿里云是 SP。</p><h3 id="为应用分配用户或用户组"><a href="#为应用分配用户或用户组" class="headerlink" title="为应用分配用户或用户组"></a>为应用分配用户或用户组</h3><p>在 AWS IAM Identity Center 应用配置页面，需要把实际用户分配到该应用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%205.png" alt="image.png"></p><p>📌 <strong>操作细节</strong>：</p><ol><li>在应用右下角点击 <strong>Add user or group</strong>。</li><li>搜索用户，建议使用 <strong>姓名搜索</strong> 而不是用户名。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%206.png" alt="image.png"></p><p>⚠️ <strong>注意事项</strong>：</p><ul><li>用户名可能无法正确匹配，使用姓名更稳定。</li><li>如果分配的是用户组，那么组内所有用户都能通过 SSO 登录阿里云。</li><li>建议按 <strong>部门 &#x2F; 职能</strong> 建立用户组，例如：<code>DevOps-Team</code>、<code>Finance-Team</code>。</li></ul><p>这个应用程序的属性映射如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2011.png" alt="image.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName](https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName)  awssso(可以自定义）</span><br><span class="line"></span><br><span class="line">[https://www.aliyun.com/SAML-Role/Attributes/Role](https://www.aliyun.com/SAML-Role/Attributes/Role) acs:ram::1647543622349991:role/iamssorole,acs:ram::1647543622349991:saml-provider/iamsso（role, provider的arn）</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2012.png" alt="image.png"></p><h3 id="在阿里云创建角色并建立信任关系"><a href="#在阿里云创建角色并建立信任关系" class="headerlink" title="在阿里云创建角色并建立信任关系"></a>在阿里云创建角色并建立信任关系</h3><p>阿里云侧还需要配置一个 <strong>角色（Role）</strong>，与身份提供商进行绑定。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%208.png"></p><p>角色配置完成后， <strong>信任策略</strong>如下，例如：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%209.png" alt="image.png"></p><p>在 AWS 上可以看到我们新建的应用程序。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2010.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2011.png" alt="image.png"></p><p>📌 <strong>说明</strong>：</p><ul><li><code>role/iamssorole</code> 表示阿里云角色。</li><li><code>saml-provider/iamsso</code> 表示 AWS IAM Identity Center 提供的身份。</li><li>两者形成绑定关系后，用户通过 AWS SSO 登录时即可扮演此角色。</li></ul><p>👉 可配置的属性：</p><ul><li><code>RoleSessionName</code>：会话名称，通常可设置为 <code>awssso</code> 或自定义值。</li><li><code>Role</code>：指定的阿里云角色 ARN。</li></ul><p><a href="https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName">https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName</a><br><a href="https://www.aliyun.com/SAML-Role/Attributes/Role">https://www.aliyun.com/SAML-Role/Attributes/Role</a></p><p>在配置过程中，阿里云官方也提供了一些参考文档，例如：</p><p><a href="https://help.aliyun.com/zh/ram/user-guide/implement-role-based-sso-from-ad-fs?spm=a2c4g.11186623.help-menu-28625.d_2_4_3_0_5.2f7f4cc2kQ1KvN">https://help.aliyun.com/zh/ram/user-guide/implement-role-based-sso-from-ad-fs</a></p><p>虽然文档以 AD FS 为例，但本质上对接 AWS IAM Identity Center 的原理是一样的。</p><h3 id="验证-IAM-SSO-登录流程"><a href="#验证-IAM-SSO-登录流程" class="headerlink" title="验证 IAM SSO 登录流程"></a>验证 IAM SSO 登录流程</h3><p>配置完成后，可以在 AWS IAM Identity Center 的 SSO 页面进行验证。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2013.png" alt="image.png"></p><p>在 <strong>Dashboard</strong> 中，点击分配好的 “Alibaba Cloud” 应用：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2014.png" alt="image.png"></p><p>此时用户会自动跳转到阿里云控制台，无需再次输入用户名和密码：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image%2015.png" alt="image.png"></p><h3 id="最佳实践与经验总结"><a href="#最佳实践与经验总结" class="headerlink" title="最佳实践与经验总结"></a>最佳实践与经验总结</h3><ol><li><strong>最小权限原则</strong><ul><li>在阿里云角色策略中，只授予用户所需的最低权限。</li></ul></li><li><strong>分组管理</strong><ul><li>在 AWS IAM Identity Center 中按部门建组，再映射到阿里云角色。</li></ul></li><li><strong>MFA 多因子认证</strong><ul><li>在 AWS 侧启用 MFA，提高整体安全性。</li></ul></li><li><strong>跨云审计</strong><ul><li>结合 AWS CloudTrail 与阿里云 ActionTrail，实现跨平台日志追踪。</li></ul></li><li><strong>会话管理</strong><ul><li>设置合理的会话过期时间，防止用户长时间保持登录状态。</li></ul></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过本文的配置流程，我们实现了 <strong>AWS IAM Identity Center（IdP） → 阿里云 RAM（SP）</strong> 的单点登录：</p><ul><li>用户在 AWS 中完成身份认证；</li><li>AWS 生成 SAML 断言并传递给阿里云；</li><li>阿里云验证后授予对应角色权限；</li><li>用户一键跳转进入阿里云控制台。</li></ul>]]></content>
    
    
    <summary type="html">配置 AWS IAM Identity Center SAML 实现单点登录阿里云的教程</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>组队参加 Amazon Q Idea 1000，我们的作品上了 AWS 峰会</title>
    <link href="https://blog.no-claw.com/posts/ac23bcf7/"/>
    <id>https://blog.no-claw.com/posts/ac23bcf7/</id>
    <published>2025-06-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近生成式 AI 的比赛很多，也报名了 AWS Idea1000 的比赛，作品登录上了 AWS 的峰会。</p><p>我们的产品名称是“拍立懂”，团队名称是 “凌晨三点的夜”。主要是<strong>拍照识别</strong>商品成分、品牌资质，分析价格合理性，为老年人提供购买决策建议；通过<strong>实时语音聊天交互</strong>，<strong>亲人语音陪伴</strong>老年人购物，满足空巢老人的情感空缺。</p><p>我们的项目团队汇聚了文化创意创业者、资深互联网产品经理、互联网技术博主与 AI 前端工程师等多元背景，形成从商业策略到产品落地的完整闭环。团队成员对创新技术和用户价值怀有共同的热情，彼此协作、优势互补，致力于在商业模式和技术实现上持续突破，为项目注入持久动力。</p><span id="more"></span><p>说人话版本:</p><blockquote><p><strong>“拍立懂”首页：先定位、再拍照、还可语音，一站式搞定逛超市！</strong><br>进入小程序，系统会自动识别你所在的门店，保证每一次推荐都“本地有货”。</p><ul><li>对准商品“拍一拍”，AI 秒识品牌与规格，为你生成成分&#x2F;营养解析；</li><li>打开语音助手，直接问“这款油健康吗？”，即时语音作答；</li><li>想逛逛热卖？下拉切换「日用食品 &#x2F; 零食饮料 &#x2F; 时令食材」，AI 列出今日在售优质清单。<br><strong>拍照 + 语音 + 实时库存</strong>，让你逛超市不再纠结，用 AI 把复杂信息说得清清楚楚。</li></ul></blockquote><p>架构图如下：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0f723804988ba318ff6ab24970dfc023.png" alt="0f723804988ba318ff6ab24970dfc023"></p><ol><li><strong>域名与流量入口</strong><ul><li>Route 53 购买域名以及负责域名解析</li><li>ALB 负载均衡 &amp; HTTPS 卸载</li></ul></li><li><strong>应用与模型服务</strong><ul><li><strong>Next.js</strong> (前端+SSR)</li><li><strong>Flask (语音识别、图像识别、流式对题) + FastAPI （OpenAI realtime 代理）</strong></li></ul></li><li><strong>数据与存储</strong><ul><li>S3 存音频&#x2F;图片</li><li><strong>Milvus (Zilliz Cloud on GCP)</strong> 向量检索做 RAG</li></ul></li><li><strong>多模态交互</strong><ul><li>OpenAI Vision + Realtime 实时对话</li></ul></li><li><strong>环境 &amp; CI&#x2F;CD</strong><ul><li>Lightsail 做 POC，懒猫微服异地组网调试</li><li>GitHub Actions 一件打包到 Dockerhub</li><li>Docker-compose 一键部署</li></ul></li></ol><p>这个是最初的设计。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250620102357142.png"></p><ul><li><strong>门店定位（顶部）</strong><ul><li>自动显示 _“上海市黄浦区马当路富民超市”_，说明系统已精准锁定当前购物地点，为后续推荐做铺垫。</li></ul></li><li><strong>商品识别卡片（居中大框）</strong><ul><li>灰阶渐变 + 相机图标，文案 _“对准商品 拍一拍”_。</li><li>点击即可拍照识别商品，进入智能解析流程，是整套体验的“入口 1”。</li></ul></li><li><strong>语音购物助手按钮（绿色条形）</strong><ul><li>显眼的绿色按钮 _“打开语音购物助手”_。</li><li>用户只需轻触，便可与 AI 语音对话，实现“入口 2”。</li></ul></li><li><strong>AI 推荐栏目（下方列表）</strong><ul><li>栏目标题 _“AI 帮你聪明买”_，右侧分类下拉框默认 _“日用食品”_。</li><li>下拉可切换 <em>“零食饮料 &#x2F; 时令食材”</em> 等，列表随之刷新“今日在售”优选商品。</li></ul></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f6f98cd0-65ec-41fa-ab5b-1d43b75504b5.png" alt="f6f98cd0-65ec-41fa-ab5b-1d43b75504b5"></p><ul><li><strong>拍一下，AI 帮你把配料表翻译成人话！</strong><br>识别完成后，只需点开「成分分析」，AI 会：<ol><li>精准列出主要成分、配料比例；</li><li>提醒潜在风险（如高糖、高钠或人工添加）；</li><li>结合膳食指南，给出健康建议。<br>如果想进一步了解热量、矿物质等信息，切换到「营养价值」即可；想知道同类好物怎么选？点「选购建议」一键获得。<br><strong>拍立懂，让任何瓶瓶罐罐都不再是“天书”，分分钟看懂喝得放心！</strong></li></ol></li></ul><blockquote><p>“拍一下成分表，让 AI 帮你读懂配料、评估健康影响，真正做到买得安心、喝得放心。”</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/0375561d-e43e-4618-94be-66439460d91c.png" alt="0375561d-e43e-4618-94be-66439460d91c"></p><blockquote><p><strong>“坚果零食好不好？拍立懂 3 秒告诉你！”</strong><br>拍照上传后，AI 自动识别到「烤坚果夹片 145 g × 24 片」，并细致解析：</p><ul><li><strong>坚果种类</strong>、<strong>外层配料</strong>、<strong>甜味剂 &#x2F; 添加剂</strong> 全部列出；</li><li>逐项点评对心血管、体重管理、过敏风险的影响；</li><li>支持一键切换到「营养价值」查看热量、蛋白质等详细数据，或点「选购建议」获取更健康替代品。<br><strong>拍立懂</strong> —— 把复杂配料表翻译成人话，让零食的健康度一眼可见！</li></ul></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250620134244888.png" alt="image-20250620134244888"></p><ul><li><strong>一句话上手语音购物助手</strong><br>点一下麦克风，不用输入、不用切页面，直接对 AI 说：“这款牛奶适合减肥吗？”——拍立懂立刻回答，让逛超市像跟朋友聊天一样简单。<ul><li><strong>极速连接</strong>：3 秒内完成网络 &amp; 麦克风检测</li><li><strong>实时反馈</strong>：对话状态一目了然，杜绝“说了没听见”</li><li><strong>智能引导</strong>：AI 主动提问，帮你快速聚焦购物痛点<br>语音 + AI，让信息检索更高效，让购物决策更轻松！</li></ul></li></ul><blockquote><p>“无需打字，长按语音键即可提问。拍立懂即刻为你解惑，让购物沟通更流畅。”</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f5cd2824-be07-4152-8bc9-91bc6ddc6735.jpeg" alt="f5cd2824-be07-4152-8bc9-91bc6ddc6735"></p><ul><li><strong>商业计划书</strong><br><a href="https://edwqcun7v5.feishu.cn/docx/VBtWdFMB3omZxhxT2FGco73MnIe">https://edwqcun7v5.feishu.cn/docx/VBtWdFMB3omZxhxT2FGco73MnIe</a></li><li><strong>产品演示文档</strong><br><a href="https://edwqcun7v5.feishu.cn/docx/RNvldJCQVoU1nixqHjrcZ2TNnfc">https://edwqcun7v5.feishu.cn/docx/RNvldJCQVoU1nixqHjrcZ2TNnfc</a></li></ul><hr><blockquote><p>⏰ <strong>凌晨三点的夜</strong> 还在码字、调参，但有热情就不困。<br><strong>拍立懂</strong>，让爸妈“拍一拍就懂”，也让我们更懂爸妈。</p></blockquote>]]></content>
    
    
    <summary type="html">参加 Amazon Q Idea 1000 比赛，作品登上 AWS 峰会的经历分享</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服入门篇（零）：开箱初探，硬件亮相</title>
    <link href="https://blog.no-claw.com/posts/4022bac/"/>
    <id>https://blog.no-claw.com/posts/4022bac/</id>
    <published>2025-06-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>用了一个半月的懒猫微服，来写一个伪开箱。机甲风格外壳也得值个几百块，这个外壳还支持拆机之后全身水洗。在桌面上确实颜值很高，在各个小主机都是塑料外壳的时代，属于用心良苦了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250619161920055.png" alt="image-20250619161920055"></p><p>这张图是创始人在用户群里发的实拍图：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6c54b4ac4c8d437375149b66ca7d6aee.jpg" alt="6c54b4ac4c8d437375149b66ca7d6aee"></p><p>据群友们聊天说，这台机器已经经过了多个版本迭代，最后定型为现在这个样子。以后不要再用鞋盒了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6356f1ef9911435c101dc8d9c4eec591.png" alt="6356f1ef9911435c101dc8d9c4eec591"></p><hr><span id="more"></span><h3 id="系统与配置"><a href="#系统与配置" class="headerlink" title="系统与配置"></a>系统与配置</h3><p>通过 <code>neofetch</code> 可以看到，这台机器预装的是 Debian 12，实际系统也做了不少魔改优化。核心配置如下：</p><ul><li><strong>CPU</strong>：Intel Core i5-1155G7 @ 4.5GHz（4 核心 8 线程），相当于现在主流的 N305 强了很多。</li><li><strong>内存</strong>：笔记本内存 DDR4，最大可扩展至 32GB，3200MHZ （买的时候可以谈从 16G 升级到 32G）</li><li><strong>显卡</strong>：Intel Iris Xe Graphics，英特尔最强核显，核显频率最高 1.35GHz，支持 4K 高清输出与 H.265 视频硬解</li><li><strong>存储</strong>：<ul><li>系统盘：512GB PCIe 4.0 NVMe，用于运行系统组件和保存根目录文件。</li><li>数据盘：两个 2.5 寸 SATA 盘位，可以组 RAID0 或者 RAID1。SSD 或者 HDD 不限。</li></ul></li><li><strong>主板</strong>：看上是自己做的，叫做 YENTEK LC2580，启动热键是 F11，进入 BIOS 的按键是 <code>&lt;DEL&gt;</code> 。 没有锁 BIOS，也支持刷成其他的系统。</li></ul><p>运行 <code>dmidecode</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dmidecode -t memory | grep -e <span class="string">&quot;Size&quot;</span> -e <span class="string">&quot;Form Factor&quot;</span> -e <span class="string">&quot;Locator&quot;</span></span><br><span class="line">        Size: No Module Installed</span><br><span class="line">        Form Factor: Unknown</span><br><span class="line">        Locator: Controller1-ChannelA-DIMM0</span><br><span class="line">        Bank Locator: BANK 0</span><br><span class="line">        Size: 32 GB</span><br><span class="line">        Form Factor: SODIMM</span><br><span class="line">        Locator: Controller0-ChannelA-DIMM0</span><br><span class="line">        Bank Locator: BANK 0</span><br><span class="line">        ...</span><br></pre></td></tr></table></figure><hr><h3 id="网络性能"><a href="#网络性能" class="headerlink" title="网络性能"></a>网络性能</h3><p>网络配置方面也比较主流：</p><ul><li><strong>有线</strong>：2.5G 单网口，可惜我的路由器还是千兆不能完全发挥其能力</li><li><strong>无线</strong>：Intel AX210，支持 WiFi 6 可以跑满千兆。</li></ul><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">(base) lzcbox-029c588e ~ <span class="comment"># dmesg | grep -i ethernet</span></span><br><span class="line">[    2.946824] Intel(R) 2.5G Ethernet Linux Driver</span><br><span class="line">[    9.157839] Bluetooth: BNEP (Ethernet Emulation) ver 1.3</span><br><span class="line">(base) lzcbox-029c588e ~ <span class="comment"># lspci | grep -i network</span></span><br><span class="line">04:00.0 Network controller: Intel Corporation Wi-Fi 6 AX210/AX211/AX411 160MHz (rev 1a)</span><br></pre></td></tr></table></figure><p>于 WIFI6 来说协商速率一般是 2402Mbps，所以就算达到协商速率的一般的话，也就是差不多千兆，加上很多家用路由也仅仅是千兆，我为了 POE 供电所以牺牲了部分内网带宽，所以没有跑满。如果你有 2.5G 的机器和交换机，那么一定可以跑的很舒服，基本就到机械硬盘的上限了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250619213746613.png" alt="image-20250619213746613"></p><hr><h3 id="CPU-性跑分"><a href="#CPU-性跑分" class="headerlink" title="CPU 性跑分"></a>CPU 性跑分</h3><p>跑了几次 geekbench6，能够看到单核心的性能有 1700+，由于测速的同时还在运行很多系统应用，所以实际的数值比这个还要大一些。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250619215814429.png" alt="image-20250619215814429"></p><p>后面用了 EndeavourOS KDE 的随身碟，能够看到单核心分数还能再提升。这个 CPU 跑 docker，K8S 甚至虚拟机都没啥问题了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250619215631160.png" alt="image-20250619215631160"></p><hr><h3 id="外部接口"><a href="#外部接口" class="headerlink" title="外部接口"></a>外部接口</h3><p>机器背部接口一览，我这边插了一张采集卡，非常实用：</p><p>接口方面也非常丰富：</p><ul><li><strong>USB</strong>：USB 3.0 x3</li><li><strong>Type-C</strong>：USB 3.2 Type-C x2，支持反冲保护</li><li><strong>视频输出</strong>：HDMI 2.1 x1，支持 4K 输出、多声道音频</li><li><strong>音频接口</strong>：3.5mm 耳机口，支持高阻抗耳机</li><li><strong>电源接口</strong>：DC 5525</li></ul><blockquote><p>整机搭载一枚 17mm 涡轮静音风扇，结合滚珠轴承与自研调速算法，实际体验确实安静，确实没有 3.5 寸硬盘那种炒豆子的声音了。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/15b358f335c93c896b98292e49ee0bf6.jpg" alt="15b358f335c93c896b98292e49ee0bf6"></p><p>可以看到 Deepin 的 团队出来做产品的能力还是挺强的，从主板到 OS 的深度定制，技术功底可见一斑。</p><p>下面是群友的发的效果图，实物质感确实很棒，欢迎找我下单体验！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/cd96e69cd34841430873db544aae96a4.png" alt="cd96e69cd34841430873db544aae96a4"></p>]]></content>
    
    
    <summary type="html">懒猫微服开箱体验，硬件外观、接口配置与初印象。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="电脑外设" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%84%91%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服炫技篇（二）：使用懒猫微服倒推停电时间</title>
    <link href="https://blog.no-claw.com/posts/b6479fd0/"/>
    <id>https://blog.no-claw.com/posts/b6479fd0/</id>
    <published>2025-06-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>早晨的时候，突然听到饮水机和扫地机器人重启的声音，只有一种可能是电闸重启了。没有人为重启，那就是意外断电了。赶紧 SSH 登录到懒猫微服，第一时间查看 <code>uptime</code>，果然 3 分钟之前重启了。懒猫的 BIOS 有上电自启的功能，所以能够看到确实是停电了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/610a2721b7c9a4c5bcdc51a1960736b9.png" alt="610a2721b7c9a4c5bcdc51a1960736b9"></p><p>使用 uptime -s 显示系统最后一次启动的具体时间</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617102236473.png" alt="image-20250617102236473"></p><p>然后又看了看群晖，群晖接到了 UPS 上，几乎没受到啥影响。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617100146123.png" alt="image-20250617100146123"></p><p>昨天刚把懒猫从 UPS 上拿下来，因为发现 UPS 有无线干扰。结果第二天就遇到断电，果然是怕啥来啥。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/9cf0f2aed68bcbfc70c00103441ffb52.jpg" alt="9cf0f2aed68bcbfc70c00103441ffb52"></p><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">journalctl --list-boots  # 列出所有启动记录</span><br></pre></td></tr></table></figure><p>重点观察：</p><ul><li><code>LAST ENTRY</code>：上次关机时间</li><li><code>FIRST ENTRY</code>：本次启动时间<br>这两个时间中间如果有一个空档，而没有正常的 shutdown 日志，就极可能是断电。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617101108571.png" alt="image-20250617101108571"></p><p>可以看到从 <strong>08:31 到 08:41</strong>，系统中断了 10 分钟，符合意外断电并自动重启的特征。</p><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">journalctl -b -1 | grep -i <span class="string">&quot;shutdown\|power\|crash\|kern.*panic&quot;</span>  <span class="comment"># 检查上次会话</span></span><br></pre></td></tr></table></figure><p>如果是正常关机，会有 <code>systemd-shutdown</code> 或服务停止记录；</p><p>如果是异常断电，则日志会直接中断，没有“收尾”。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617101216758.png" alt="image-20250617101216758"></p><p>到这基本上可以确定是 08 点 31 断电的了，再拉下系统日志：能看到好好的 UPNP 的日志突然中断。然后 10 分钟后转为开机日志。基本上可以确定是意外断电。</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">journalctl -S <span class="string">&quot;2025-06-17 08:30:00&quot;</span> -U <span class="string">&quot;2025-06-17 08:42:00&quot;</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617101440333.png" alt="image-20250617101440333"></p><p>赶紧查下 SMART 信息，还好没啥事。</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">smartctl -a /dev/sda</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250617101951207.png" alt="image-20250617101951207"></p>]]></content>
    
    
    <summary type="html">利用懒猫微服的 uptime 和上电自启功能倒推家中停电时间</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="炫技" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%82%AB%E6%8A%80/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（八）：如何在 ARM 机器上下载并运行X86镜像以及在X86上运行ARM镜像</title>
    <link href="https://blog.no-claw.com/posts/86ab1d7c/"/>
    <id>https://blog.no-claw.com/posts/86ab1d7c/</id>
    <published>2025-06-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前写好应用做好镜像想扔到懒猫微服上打包，都是先使用 buildx 打包双架构镜像，push 到 dockerhub 上，然后再用微服的 copy image 转成国内的镜像地址，这过程很麻烦。</p><p>因为在 Apple Silicon（如 M1&#x2F;M2 芯片）设备上，默认运行的是 ARM 架构镜像（<code>linux/arm64</code>）。但有些镜像或依赖只支持 X86（<code>linux/amd64</code>）架构。</p><p>本文将介绍如何在 ARM 设备上拉取并运行 X86 镜像，以及如何保存和加载镜像。</p><h3 id="🐳-拉取-X86-架构的-Docker-镜像"><a href="#🐳-拉取-X86-架构的-Docker-镜像" class="headerlink" title="🐳 拉取 X86 架构的 Docker 镜像"></a>🐳 拉取 X86 架构的 Docker 镜像</h3><p>使用 <code>--platform=amd64</code> 参数即可拉取 X86 架构镜像：</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">docker pull --platform=amd64 nginx:latest</span><br></pre></td></tr></table></figure><ul><li><code>docker pull</code>：从远程仓库拉取镜像</li><li><code>--platform=amd64</code>：显式指定拉取 <code>x86_64</code> 架构的镜像</li><li><code>nginx:latest</code>：镜像名与标签</li></ul><blockquote><p>适用于在 M 系列 Mac 上使用 X86 镜像进行兼容性测试或运行仅支持 x86 的应用。</p></blockquote><h2 id=""><a href="#" class="headerlink" title=""></a><span id="more"></span></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></pre></td><td class="code"><pre><span class="line">docker image inspect nginx:latest --format <span class="string">&#x27;&#123;&#123;.Os&#125;&#125;/&#123;&#123;.Architecture&#125;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>示例输出（成功拉取 X86 架构）：</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">linux/amd64</span><br></pre></td></tr></table></figure><hr><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></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> -it --platform=amd64 nginx:latest</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">docker: Error response from daemon: image with reference nginx:latest was found but its platform (linux/amd64) does not match the specified platform (darwin/amd64)</span><br></pre></td></tr></table></figure><h4 id="📌-错误原因解析："><a href="#📌-错误原因解析：" class="headerlink" title="📌 错误原因解析："></a>📌 错误原因解析：</h4><p>Docker 镜像是 <strong>基于 Linux 内核</strong> 的容器运行时，不支持 <code>darwin/amd64</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">--platform=linux/amd64</span><br></pre></td></tr></table></figure><h4 id="✅-正确命令："><a href="#✅-正确命令：" class="headerlink" title="✅ 正确命令："></a>✅ 正确命令：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> -it --platform=linux/amd64 nginx:latest</span><br></pre></td></tr></table></figure><p>此时 Docker Desktop 会自动调用 <code>qemu</code> 进行跨架构模拟（无需额外配置），即在 ARM Mac 上模拟运行 X86 容器。</p><hr><h3 id="📦-Docker-镜像的保存与加载"><a href="#📦-Docker-镜像的保存与加载" class="headerlink" title="📦 Docker 镜像的保存与加载"></a>📦 Docker 镜像的保存与加载</h3><p>Docker 提供 <code>save</code> 和 <code>load</code> 命令，支持将镜像打包导出为文件，便于备份或跨设备迁移。</p><h4 id="✅-1-保存镜像为-tar-文件"><a href="#✅-1-保存镜像为-tar-文件" class="headerlink" title="✅ 1. 保存镜像为 .tar 文件"></a>✅ 1. 保存镜像为 <code>.tar</code> 文件</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker save -o nginx-amd64.tar nginx:latest</span><br></pre></td></tr></table></figure><ul><li><code>-o nginx-amd64.tar</code>：导出的文件名</li><li><code>nginx:latest</code>：指定要导出的镜像标签</li></ul><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">docker save -o images.tar nginx:latest redis:alpine</span><br></pre></td></tr></table></figure><hr><h4 id="✅-2-加载-tar-镜像文件"><a href="#✅-2-加载-tar-镜像文件" class="headerlink" title="✅ 2. 加载 .tar 镜像文件"></a>✅ 2. 加载 <code>.tar</code> 镜像文件</h4><p>使用 SCP 或者 FTP 传到懒猫微服上，使用以下命令导入：</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">docker load -i nginx-amd64.tar</span><br></pre></td></tr></table></figure><p>导入成功后镜像将出现在 <code>docker images</code> 列表中。</p><hr><h4 id="✅-3-跨架构导入运行示例"><a href="#✅-3-跨架构导入运行示例" class="headerlink" title="✅ 3. 跨架构导入运行示例"></a>✅ 3. 跨架构导入运行示例</h4><p>如果你从懒猫微服上保存了镜像（如 <code>linux/amd64</code> 的 nginx），在 ARM Mac 上可通过以下方式运行：</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">docker run --<span class="built_in">rm</span> -it --platform=linux/amd64 nginx:latest</span><br></pre></td></tr></table></figure><hr><h4 id="✅-4-导出为压缩文件（可选）"><a href="#✅-4-导出为压缩文件（可选）" class="headerlink" title="✅ 4. 导出为压缩文件（可选）"></a>✅ 4. 导出为压缩文件（可选）</h4><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">docker save nginx:latest | gzip &gt; nginx.tar.gz</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">gunzip -c nginx.tar.gz | docker load</span><br></pre></td></tr></table></figure><hr><h3 id="📝-小结"><a href="#📝-小结" class="headerlink" title="📝 小结"></a>📝 小结</h3><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>拉取 X86 镜像</td><td><code>docker pull --platform=amd64 nginx:latest</code></td></tr><tr><td>运行 X86 镜像</td><td><code>docker run --rm -it --platform=linux/amd64 nginx:latest</code></td></tr><tr><td>保存镜像</td><td><code>docker save -o nginx.tar nginx:latest</code></td></tr><tr><td>加载镜像</td><td><code>docker load -i nginx.tar</code></td></tr></tbody></table><p>如果是在 懒猫微服 运行 ARM 镜像呢？</p><h3 id="✅-拉取-ARM-架构镜像（在-X86-主机上）"><a href="#✅-拉取-ARM-架构镜像（在-X86-主机上）" class="headerlink" title="✅ 拉取 ARM 架构镜像（在 X86 主机上）"></a>✅ 拉取 ARM 架构镜像（在 X86 主机上）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull --platform=linux/arm64 nginx:latest</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">docker pull --platform=arm64 nginx:latest</span><br></pre></td></tr></table></figure><p>这会拉取适用于 <code>linux/arm64</code> 的 nginx 镜像（即 ARM 设备如 Raspberry Pi 或 Apple Silicon 可运行的版本）。</p><hr><h3 id="✅-运行-ARM-镜像（在-X86-上）"><a href="#✅-运行-ARM-镜像（在-X86-上）" class="headerlink" title="✅ 运行 ARM 镜像（在 X86 上）"></a>✅ 运行 ARM 镜像（在 X86 上）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> -it --platform=linux/arm64 nginx:latest</span><br></pre></td></tr></table></figure><p>Docker Desktop 会通过内置的 QEMU 模拟 ARM 架构运行该容器。</p><blockquote><p>⚠️ 前提是你的 Docker 环境启用了 QEMU 多平台支持（默认大多数 Docker Desktop 安装都已经启用）。</p></blockquote><hr><h3 id="✅-验证运行中的容器架构"><a href="#✅-验证运行中的容器架构" class="headerlink" title="✅ 验证运行中的容器架构"></a>✅ 验证运行中的容器架构</h3><p>方案一：确认 QEMU 是否已配置（X86 主机想运行 ARM 镜像）<br>如果你在 Intel &#x2F; X86 主机上运行 –platform&#x3D;linux&#x2F;arm64，需要先启用跨架构支持：</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">docker run --<span class="built_in">rm</span> --privileged multiarch/qemu-user-static --reset -p <span class="built_in">yes</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250616232147019.png" alt="image-20250616232147019"></p><p>进入容器执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">uname</span> -m</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">aarch64</span><br></pre></td></tr></table></figure><p>说明该容器运行在 ARM 架构上。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250616232100579.png" alt="image-20250616232100579"></p><p>如果遇到：<br>exec &#x2F;docker-entrypoint.sh: exec format error<br>意味着：你尝试在一个 与镜像架构不匹配的主机或模拟环境中运行该镜像，导致容器入口脚本无法被执行。</p><table><thead><tr><th>目标架构</th><th><code>--platform</code> 参数</th><th>常见用途</th></tr></thead><tbody><tr><td>X86 (Intel&#x2F;AMD)</td><td><code>linux/amd64</code></td><td>默认平台，大多数镜像的标准版本</td></tr><tr><td>ARM (如 M1&#x2F;M2&#x2F;Raspberry Pi)</td><td><code>linux/arm64</code></td><td>在 Apple Silicon 上或嵌入式设备运行</td></tr><tr><td>在 X86 上模拟 ARM</td><td><code>--platform=linux/arm64</code></td><td>跨架构测试、兼容性验证</td></tr></tbody></table>]]></content>
    
    
    <summary type="html">ARM 与 X86 架构互跑 Docker 镜像的方法，解决懒猫微服跨架构兼容问题。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>给群晖安装Iperf3，一键局域网测速</title>
    <link href="https://blog.no-claw.com/posts/e70dd7d7/"/>
    <id>https://blog.no-claw.com/posts/e70dd7d7/</id>
    <published>2025-06-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>群晖默认不带 iperf3，所以需要套件来安装：</p><p>套件中心搜索 SynoCli，然后安装 SynoCli Monitor Tools ，里面有常见的监控工具。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250615183217020.png" alt="image-20250615183217020"></p><p>然后在群晖上启动服务端：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">name@qun:~$ iperf3 -s</span><br><span class="line">-----------------------------------------------------------</span><br><span class="line">Server listening on 5201 (test #1)</span><br><span class="line">-----------------------------------------------------------</span><br><span class="line"></span><br></pre></td></tr></table></figure><span id="more"></span><p>在 Mac 上测速，iperf3 -c 和</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250615184708521.png" alt="image-20250615184708521"></p><p>这两个命令的差别关键在于 <strong>数据传输方向</strong>，也就是谁是“发”谁是“收”。</p><hr><h3 id="✅-iperf3-c"><a href="#✅-iperf3-c" class="headerlink" title="✅ iperf3 -c &lt;server_ip&gt;"></a>✅ <code>iperf3 -c &lt;server_ip&gt;</code></h3><ul><li><strong>含义</strong>：客户端主动发数据，服务器接收。</li><li><strong>方向</strong>：📤 客户端 → 服务器（上传测试）</li><li><strong>谁发数据？</strong>：客户端</li><li><strong>谁测带宽？</strong>：客户端测发送速度，服务器测接收速度。</li></ul><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">iperf3 -c 192.168.5.171</span><br></pre></td></tr></table></figure><p>表示测试<strong>从你这台机器上传数据到服务器的带宽</strong>。</p><hr><h3 id="✅-iperf3-c-R"><a href="#✅-iperf3-c-R" class="headerlink" title="✅ iperf3 -c &lt;server_ip&gt; -R"></a>✅ <code>iperf3 -c &lt;server_ip&gt; -R</code></h3><ul><li><strong>含义</strong>：反向测试，客户端建立连接后，让服务器发数据。</li><li><strong>方向</strong>：📥 服务器 → 客户端（下载测试）</li><li><strong>谁发数据？</strong>：服务器</li><li><strong>谁测带宽？</strong>：客户端测接收速度，服务器测发送速度。</li></ul><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">iperf3 -c 192.168.5.171 -R</span><br></pre></td></tr></table></figure><p>表示测试<strong>从服务器下载数据到你的机器的带宽</strong>。</p><hr><h3 id="🔁-表格对比总结："><a href="#🔁-表格对比总结：" class="headerlink" title="🔁 表格对比总结："></a>🔁 表格对比总结：</h3><table><thead><tr><th>命令</th><th>数据方向</th><th>谁发送</th><th>谁接收</th><th>测的是什么带宽</th></tr></thead><tbody><tr><td><code>iperf3 -c</code></td><td>客户端 → 服务器</td><td>客户端</td><td>服务器</td><td><strong>上传带宽</strong></td></tr><tr><td><code>iperf3 -c -R</code></td><td>服务器 → 客户端</td><td>服务器</td><td>客户端</td><td><strong>下载带宽</strong></td></tr></tbody></table><hr><h3 id="🎯-提示："><a href="#🎯-提示：" class="headerlink" title="🎯 提示："></a>🎯 提示：</h3><ul><li>二者都只能测试<strong>一个方向</strong>，不会自动“双向”测试。</li><li>如果你想做双向测试，请手动运行这两个命令各一次。</li></ul>]]></content>
    
    
    <summary type="html">在群晖上安装 Iperf3，一条命令测试局域网带宽速度。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/%E7%BE%A4%E6%99%96/"/>
    
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>如何在 AWS EC2 上部署 Isaac Sim</title>
    <link href="https://blog.no-claw.com/posts/10d4e175/"/>
    <id>https://blog.no-claw.com/posts/10d4e175/</id>
    <published>2025-06-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>网上已经有一些关于在阿里云和腾讯云上部署 Isaac Sim 的教程，本文将带大家了解如何在 <strong>AWS EC2 上部署 NVIDIA Isaac Sim 仿真平台</strong>，并以 <strong>A10G GPU（g5.2xlarge 实例）</strong>为例进行实战操作。</p><hr><h2 id="一、环境说明"><a href="#一、环境说明" class="headerlink" title="一、环境说明"></a>一、环境说明</h2><ul><li><strong>GPU 类型</strong>：A10G（适用于 RTX 渲染）</li><li><strong>实例类型</strong>：<code>g5.2xlarge</code></li><li><strong>操作系统镜像（AMI）</strong>：<br><code>Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.7 (Ubuntu 22.04)</code><br>👉 该镜像自带 NVIDIA 驱动、CUDA、Docker、nvidia-docker，无需手动安装</li></ul><hr><span id="more"></span><h2 id="二、登录-AWS-控制台并创建实例"><a href="#二、登录-AWS-控制台并创建实例" class="headerlink" title="二、登录 AWS 控制台并创建实例"></a>二、登录 AWS 控制台并创建实例</h2><ol><li><p>打开 <a href="https://aws.amazon.com/">AWS 官网</a>，点击右上角登录。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220057596.png" alt="登录入口"></p></li><li><p>选择 <strong>使用 Root 账户登录</strong>：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220133816.png" alt="root 登录"></p></li><li><p>输入 root 邮箱和密码，若首次登录需要绑定 MFA（建议使用 Authenticator App）：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220233221.png" alt="MFA 验证"></p></li><li><p>进入 AWS 控制台后，选择左侧的 EC2，点击右上角的 <strong>“启动实例”</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220401797.png" alt="启动 EC2 实例"></p></li></ol><hr><h2 id="三、配置-EC2-实例（含-GPU-驱动）"><a href="#三、配置-EC2-实例（含-GPU-驱动）" class="headerlink" title="三、配置 EC2 实例（含 GPU 驱动）"></a>三、配置 EC2 实例（含 GPU 驱动）</h2><ol><li><p><strong>选择操作系统镜像（AMI）</strong>：<br>搜索并选择：</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">Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.7 (Ubuntu 22.04)</span><br></pre></td></tr></table></figure><blockquote><p>自带了 NVIDIA 驱动、nvidia-container-toolkit、Docker 等，无需额外安装。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220549942.png" alt="选择镜像"></p></li><li><p><strong>选择实例类型</strong>：<code>g5.2xlarge</code>（带 A10G GPU）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220637975.png" alt="选择实例类型"></p></li><li><p><strong>创建密钥对</strong>：系统会生成 <code>.pem</code> 格式的密钥，下载后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 400 your-key.pem</span><br></pre></td></tr></table></figure></li><li><p><strong>网络设置</strong>：</p><ul><li>选择已有 VPC，或默认网络</li><li>确保启用公网 IP 分配</li></ul></li><li><p><strong>安全组设置</strong>：</p><ul><li>开放所需端口</li><li>如部署 livestream 或远程访问，确保相应端口可用</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614220708780.png" alt="安全组设置"></p></li></ol><hr><h2 id="四、连接实例并确认环境"><a href="#四、连接实例并确认环境" class="headerlink" title="四、连接实例并确认环境"></a>四、连接实例并确认环境</h2><p>使用 SSH 登录 EC2 实例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -i your-key.pem ubuntu@&lt;EC2公有IP&gt;</span><br></pre></td></tr></table></figure><ol><li><p>查看基本系统信息（需先安装 neofetch）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install neofetch -y</span><br><span class="line">neofetch</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/d7283c4b7f3ae527c53fdb06facaf7bf.png" alt="neofetch 系统信息"></p></li><li><p>查看 GPU 驱动是否正常：</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">nvidia-smi</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614221542279.png" alt="nvidia-smi"></p></li><li><p>查看是否已安装 <code>nvidia-docker</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">docker info | grep -i nvidia</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250614225538525.png" alt="nvidia-docker 插件"></p></li></ol><hr><h2 id="五、安装-Isaac-Sim（官方容器方式）"><a href="#五、安装-Isaac-Sim（官方容器方式）" class="headerlink" title="五、安装 Isaac Sim（官方容器方式）"></a>五、安装 Isaac Sim（官方容器方式）</h2><p>接下来按照 NVIDIA 官方文档进行 Isaac Sim 的容器部署即可：</p><p>👉 文档链接：<br><a href="https://docs.isaacsim.omniverse.nvidia.com/4.5.0/installation/install_container.html">https://docs.isaacsim.omniverse.nvidia.com/4.5.0/installation/install_container.html</a></p><ul><li>如果首次启动卡在 <code>RtPso async compilation</code> 阶段较久（10 分钟左右），这是因为光线追踪 shader 正在编译。只要缓存持久化，之后启动会非常快（1 分钟内）。</li></ul><hr><h2 id="✅-总结"><a href="#✅-总结" class="headerlink" title="✅ 总结"></a>✅ 总结</h2><p>通过 AWS 的 G5 系列实例（搭载 A10G GPU），我们可以方便地在云端部署 Isaac Sim。选用 NVIDIA 官方预装驱动的 AMI，可以省去繁琐的 CUDA 和容器配置。搭配持久化缓存和合理的端口管理，即可稳定高效运行 Isaac Sim 的云端仿真。</p>]]></content>
    
    
    <summary type="html">在 AWS EC2 G5 实例上部署 NVIDIA Isaac Sim 仿真平台的实战教程</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="Isaac" scheme="https://blog.no-claw.com/tags/Isaac/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服故事篇（二）：西湖邂逅后，我手把手教她玩转NAS</title>
    <link href="https://blog.no-claw.com/posts/943b4ba6/"/>
    <id>https://blog.no-claw.com/posts/943b4ba6/</id>
    <published>2025-06-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>去年与她相识于杭州，是在西湖的游船上。参加活动过后，我想在杭州逗留一天，看看被世人称为眼泪的西湖水。没做攻略，匆匆向前台要了手册，然后来到距离最近的码头。磨磨蹭蹭之后总算开船，隔着一堆大爷大妈看着看着她在拍照，也邀请我帮她拍照。于是找她要攻略，一起逛三潭映月，净慈寺，讨论雷峰塔的倒下。</p><p>因为苏堤，我们聊到东坡，美食以及宦海沉浮。聊到最爱的粤菜和川菜更是共同的爱好，去成都旅游的时候找他要了攻略，并且约定下次去她的城市旅游给我当导游。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608222101922.png" alt="image-20250608222101922"></p><span id="more"></span><p><a href="https://appstore.lazycat.cloud/#/shop/detail/cloud.lazycat.shell.files">https://appstore.lazycat.cloud/#/shop/detail/cloud.lazycat.shell.files</a></p><p>加了微信一直零零碎碎的聊着。一个周末的早上，她找我帮忙转换视频格式。由于微信的限制无法发送大文件，于是我建议她通过懒猫网盘传给我。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214046993.png" alt="image-20250608214046993"></p><p>于是开始做思想工作，把数据上传到我的家里的懒猫微服上。这里还是感谢信任和支持，没有认为我这个是一些诈骗盗取的网站。（毕竟曾经在学校讨论代理问题，被文科生当成黑客）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214628565.png" alt="image-20250608214628565"></p><p>然后开账户，她的 windows 好像没有可以扫码的地方，所以我帮助她注册好，然后发给她。和她自己注册不一样的是，她的设备我登录时候我这边会弹出“安全码”，然后再发给她，这样她就就可以自己处理登录的问题了。我给她开了懒猫相册，清单，网盘和一些好玩的 APP，除了处理这个事情之外，也希望后面也能慢慢用起来其他的功能。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214304796.png" alt="image-20250608214304796"></p><p>我是一个非常不喜欢 MFA 的人，但是这个二次验证还是能够接受。常规的 MFA 是每次登录都要手动输入二次验证码，而这个相同的设备只需要一次。虽然从系统设计的角度上看二者没有太大的区别，但是还是感觉这个设计，用来节约我们浪费在二次验证上的时间。我们不是牛马，我们是人，我要相信自己的验证。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608221352408.png" alt="image-20250608221352408"></p><p>然后我告诉他把文件上传到网盘上，然后共享整个文件夹给我。在我的不完全测试下只有文件夹才能共享，然后操作完文件之后再做同样的操作共享给她。毕竟对比被共享人而言，这个目录是只读的，所以我们用共享给对方来完成这个操作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214327010.png" alt="image-20250608214327010"></p><p>当然中间有个小插曲，就是上传需要等好久。她的是文科生所以对网络一知半解。一开始还以为她家的网速慢，还稍微吐槽了下。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214532318.png" alt="image-20250608214532318"></p><p>上传完成之后，我于是让她测试下家里的网速。毕竟以前找我修电脑的姑娘，不是电脑配置过时就是，几乎完全的电脑盲。然后测试下来发现她家的网速还算可以，主要是国内的运营商限制比较多。国内是唯一按照 BGP 收费的，加上还要打击 PCDN，所以每家的上传少的可怜，30MBPS 是刚好能玩的水准，转算成实际的速度嘛，还是等等吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250608214510642.png" alt="image-20250608214510642"></p><p>实际上，她给我的文件竟然有 15 个 G 多。这种大文件如果不是自建 NAS 或者商业方案根本没有传输的办法。QQ 和微信限制大小，就连邮箱也要限速 5 个 G。最早以前，我们用的办法就是，分段压缩成多个压缩包，然后一个个传给对面，对面再用相同的办法解开。十分麻烦并且耗时。而使用传统的 NAS 还要面临不定时封端口的噩梦，装机师傅和客服都解释不清楚，没有地方去问。虽然家里有公网 IP，但是不想每天被这种琐碎的问题困扰，当初购买懒猫微服也是最喜欢他们这个穿透的卖点。内网传统的是永恒的问题，然后是动态域名解析，做好还要加上健康检查，之前休假的时候把家里的机器透传到公网上，然后三五天就被封端口，但是回去看的时候内网怎么都是好的，但公网 telnet 依然有问题，除非重启路由器更换 IP。</p><p>现在我不用为了网络穿透的问题烦恼了，不用担心二次验证以及黑客攻击的问题。用拓竹的例子来说吧，只有用懒猫是在玩 NAS，其他的传统硬件都是在折腾。折腾固然好，兴趣价更高。若为自由故，二者皆可抛。</p><p>懒猫带给我的，是丰富的硬件资源和社群沟通，以及售后的专业和及时。花钱买省心，剩下抄作业。大抵如此了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/5771c5090caa9e9aa5d983203d61deb6.png" alt="5771c5090caa9e9aa5d983203d61deb6"></p>]]></content>
    
    
    <summary type="html">西湖偶遇后手把手教朋友使用懒猫微服 NAS 的有趣经历</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="故事" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%95%85%E4%BA%8B/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>本地 RAG 实战：用 Easysearch + Ollama SDK 半小时搭建检索增强问答系统</title>
    <link href="https://blog.no-claw.com/posts/5a5307d/"/>
    <id>https://blog.no-claw.com/posts/5a5307d/</id>
    <published>2025-06-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>✅ 目标：只用两台服务器（或同一台）就跑通 <strong>“向量检索 + 本地大模型”</strong> 原型<br>✅ 特点：<strong>完全离线</strong>、依赖极少、部署脚本即文档<br>✅ 适合：快速 PoC、内网合规场景、想深挖 RAG 工作机理的开发者</p></blockquote><p>生成式 AI 聊天固然强大，但当问题依赖本地私有知识时，单靠 LLM 参数内的“世界记忆”往往答非所问。<strong>RAG（Retrieval-Augmented Generation）</strong> 的思路是：</p><ol><li><strong>把文档切片 → 向量化 → 入库</strong></li><li><strong>用户提问 → 同样向量化 → 检索</strong></li><li>将召回片段拼进 prompt，让大模型“带着材料”再回答</li></ol><p>多数教程直接用云端 Embedding+OpenAI GPT-4o，但<strong>一些团队因隐私、成本或离线环境</strong>无法这样做。<br>本文选用：</p><ul><li><strong>EasySearch (&#x3D; OpenSearch + Elastiknn)</strong> 做向量存取</li><li><strong>Ollama SDK</strong> 连接本地 LLM</li><li><strong>Python + requests + ollama</strong> 三个依赖即可<span id="more"></span></li></ul><h2 id="1-系统架构"><a href="#1-系统架构" class="headerlink" title="1. 系统架构"></a>1. 系统架构</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">用户问题 ──▶ 嵌入模型 (Ollama) ──▶ EasySearch 向量检索 ──▶ Top-k 片段</span><br><span class="line">                    ▲                                             │</span><br><span class="line">                    │                                             │</span><br><span class="line">       LLM (Ollama Chat) ◀── 拼 Prompt + 生成答案 ◀───────────────┘</span><br></pre></td></tr></table></figure><ul><li><strong>嵌入模型</strong>：<code>nomic-embed-text</code>（768 维，多语言通用）</li><li><strong>检索引擎</strong>：EasySearch 2.x + Elastiknn <code>knn_dense_float_vector</code></li><li><strong>对话模型</strong>：<code>deepseek-r1:7b</code>（轻量，好部署；可换 <code>llama3</code> &#x2F; <code>qwen</code>）</li></ul><h2 id="2-环境与依赖"><a href="#2-环境与依赖" class="headerlink" title="2. 环境与依赖"></a>2. 环境与依赖</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os, json, requests, warnings</span><br><span class="line"><span class="keyword">from</span> ollama <span class="keyword">import</span> Client</span><br><span class="line"><span class="keyword">from</span> requests.packages.urllib3.exceptions <span class="keyword">import</span> InsecureRequestWarning</span><br><span class="line">warnings.filterwarnings(<span class="string">&quot;ignore&quot;</span>, category=InsecureRequestWarning)</span><br></pre></td></tr></table></figure><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"># Python 依赖</span></span><br><span class="line">pip install ollama requests</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拉取模型</span></span><br><span class="line">ollama pull nomic-embed-text</span><br><span class="line">ollama pull deepseek-r1:7b</span><br></pre></td></tr></table></figure><h2 id="3-代码逐段拆解"><a href="#3-代码逐段拆解" class="headerlink" title="3. 代码逐段拆解"></a>3. 代码逐段拆解</h2><h3 id="3-1-全局配置"><a href="#3-1-全局配置" class="headerlink" title="3.1 全局配置"></a>3.1 全局配置</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ────────────── 配置区 ──────────────</span></span><br><span class="line">ES_URL   = os.getenv(<span class="string">&quot;ES_URL&quot;</span>, <span class="string">&quot;https://localhost:9200&quot;</span>)</span><br><span class="line">ES_AUTH  = (<span class="string">&quot;admin&quot;</span>, <span class="string">&quot;c59a759f31e901e8d279&quot;</span>)   <span class="comment"># 无认证设为 None</span></span><br><span class="line">INDEX    = <span class="string">&quot;rag_demo&quot;</span></span><br><span class="line"></span><br><span class="line">OLLAMA_HOST  = os.getenv(<span class="string">&quot;OLLAMA_URL&quot;</span>, <span class="string">&quot;http://localhost:11434&quot;</span>)</span><br><span class="line">EMBED_MODEL  = <span class="string">&quot;nomic-embed-text&quot;</span>         <span class="comment"># 向量模型</span></span><br><span class="line">CHAT_MODEL   = <span class="string">&quot;deepseek-r1:7b&quot;</span>           <span class="comment"># 对话模型</span></span><br><span class="line">TOP_K        = <span class="number">4</span></span><br><span class="line">NUM_CAND     = <span class="number">200</span></span><br></pre></td></tr></table></figure><p>这段代码只是<strong>给脚本提前设定一些“连接参数”与“模型选择”</strong>，方便后面统一引用。逐行解释如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ES_URL   = <span class="string">&quot;https://&lt;es_host&gt;:9200&quot;</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>ES_URL</strong>：EasySearch &#x2F; OpenSearch 集群的完整地址（含协议与端口）。</p><ul><li><code>&lt;es_host&gt;</code> 是占位符，实际部署时要替换成你的 IP 或域名。</li><li>如果你的集群没开 TLS，可写成 <code>http://10.0.0.8:9200</code>。</li></ul></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ES_AUTH  = (<span class="string">&quot;elastic&quot;</span>, <span class="string">&quot;password&quot;</span>)   <span class="comment"># 无认证设为 None</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>ES_AUTH</strong>：连接集群的账号密码元组。</p><ul><li>脚本里传给 <code>requests</code> 的 <code>auth=</code> 参数，会自动加 Basic Auth 头。</li><li>若集群关闭了安全认证或走内网匿名访问，就把它设成 <code>None</code>。</li></ul></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INDEX    = <span class="string">&quot;rag_demo&quot;</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>INDEX</strong>：向量索引（或文档索引）的名称。</p><ul><li>脚本后面会对该索引做 <em>create &#x2F; bulk write &#x2F; search</em> 等操作。</li><li>换成别的名字时记得保持一致，例如 <code>&quot;knowledge_base&quot;</code>。</li></ul></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">OLLAMA_HOST = <span class="string">&quot;http://&lt;ollama_host&gt;:11434&quot;</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>OLLAMA_HOST</strong>：本地 Ollama 服务的 HTTP 起始地址。</p><ul><li><code>&lt;ollama_host&gt;</code> 也是占位符；若脚本与 Ollama 在同一台机器，可写 <code>http://localhost:11434</code>。</li><li>端口 <code>11434</code> 是 Ollama 默认 REST 端口。</li></ul></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">EMBED_MODEL = <span class="string">&quot;nomic-embed-text&quot;</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>EMBED_MODEL</strong>：用于生成文本向量（embeddings）的模型名。</p><ul><li>在脚本里会调用 <code>client.embeddings(model=EMBED_MODEL, …)</code>。</li><li>替换规则：先执行 <code>ollama pull &lt;模型名&gt;</code>，确保本地已下载。</li></ul></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CHAT_MODEL  = <span class="string">&quot;deepseek-r1:7b&quot;</span></span><br></pre></td></tr></table></figure><ul><li><p><strong>CHAT_MODEL</strong>：负责最终回答的聊天 &#x2F; 生成式模型。</p><ul><li>脚本会用 <code>client.chat(model=CHAT_MODEL, …)</code> 进行对话。</li><li>同理，若想用 <code>llama3:8b-chat</code>、<code>qwen:7b-chat</code> 等，先 <code>ollama pull</code> 再改这里。</li></ul></li></ul><hr><h3 id="3-2-嵌入与对话（Ollama-SDK）"><a href="#3-2-嵌入与对话（Ollama-SDK）" class="headerlink" title="3.2 嵌入与对话（Ollama SDK）"></a>3.2 嵌入与对话（Ollama SDK）</h3><p>client 是连接 Ollama 模型服务的客户端，用来发请求。</p><p>session 是访问 Elasticsearch 用的请求会话，能提高网络效率。</p><p>用指定的嵌入模型（比如 nomic-embed-text）把文本转成向量，用于相似度搜索。</p><p>用指定的聊天模型（比如 deepseek-r1:7b）回答问题，返回回复文本。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ────────────── 初始化 ──────────────</span></span><br><span class="line">client = Client(host=OLLAMA_HOST)</span><br><span class="line">session = requests.Session()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ────────────── Ollama 封装 ──────────────</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">embed</span>(<span class="params">text: <span class="built_in">str</span></span>) -&gt; <span class="built_in">list</span>[<span class="built_in">float</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;返回文本向量 (list[float])&quot;&quot;&quot;</span></span><br><span class="line">    resp = client.embeddings(model=EMBED_MODEL, prompt=text)</span><br><span class="line">    <span class="keyword">return</span> resp[<span class="string">&quot;embedding&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">chat</span>(<span class="params">prompt: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;与聊天模型对话（完整回复）&quot;&quot;&quot;</span></span><br><span class="line">    resp = client.chat(</span><br><span class="line">        model=CHAT_MODEL,</span><br><span class="line">        messages=[&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;],</span><br><span class="line">        <span class="comment"># stream=False</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> resp[<span class="string">&quot;message&quot;</span>][<span class="string">&quot;content&quot;</span>]</span><br></pre></td></tr></table></figure><h3 id="3-3-建索引（Elastiknn）"><a href="#3-3-建索引（Elastiknn）" class="headerlink" title="3.3 建索引（Elastiknn）"></a>3.3 建索引（Elastiknn）</h3><p>在构建基于向量的 RAG（Retrieval-Augmented Generation）系统时，我们首先需要在向量数据库中创建一个支持向量检索的索引。本文使用 EasySearch 作为底层存储，向其中注册一个支持近似向量搜索的索引结构。</p><p>create_index 函数的作用是通过 RESTful API 创建一个名为 rag_demo 的索引，并定义字段结构如下：</p><p>content：文本内容字段，类型为 text，可用于全文搜索或作为上下文返回。</p><p>vec：向量字段，类型为 knn_dense_float_vector，支持高维向量的快速相似度搜索。<br>配置中使用了 LSH（局部敏感哈希）模型与 cosine 相似度度量，同时设定了近似参数 L 与 k，分别控制候选样本数量和返回结果数。</p><p>通过设定 “index.knn”: True，该索引支持使用 k-NN 查询来高效地检索与查询向量最相似的文档。在实际使用中，嵌入模型如 nomic-embed-text 可将输入文本转换为高维向量，存入此索引中，与用户查询语义对齐，实现高效的语义检索能力。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ---------- ① 创建索引：Elastiknn 映射 ----------</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_index</span>(<span class="params">dim: <span class="built_in">int</span></span>):</span><br><span class="line">    mapping = &#123;</span><br><span class="line">        <span class="string">&quot;settings&quot;</span>: &#123; <span class="string">&quot;index.knn&quot;</span>: <span class="literal">True</span> &#125;,</span><br><span class="line">        <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;content&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&quot;</span> &#125;,</span><br><span class="line">                <span class="string">&quot;vec&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;knn_dense_float_vector&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;knn&quot;</span>: &#123;</span><br><span class="line">                        <span class="string">&quot;dims&quot;</span>: dim,</span><br><span class="line">                        <span class="string">&quot;model&quot;</span>: <span class="string">&quot;lsh&quot;</span>,        <span class="comment"># 也可 hnsw / exact</span></span><br><span class="line">                        <span class="string">&quot;similarity&quot;</span>: <span class="string">&quot;cosine&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;L&quot;</span>: <span class="number">99</span>,</span><br><span class="line">                        <span class="string">&quot;k&quot;</span>: <span class="number">1</span></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><span class="line">    &#125;</span><br><span class="line">    r = session.put(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/<span class="subst">&#123;INDEX&#125;</span>&quot;</span>,</span><br><span class="line">                    json=mapping, verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    <span class="keyword">if</span> r.status_code <span class="keyword">not</span> <span class="keyword">in</span> (<span class="number">200</span>, <span class="number">201</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;resource_already_exists_exception&quot;</span> <span class="keyword">not</span> <span class="keyword">in</span> r.text:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Create index error:\n&quot;</span>, r.text)</span><br><span class="line">            r.raise_for_status()</span><br></pre></td></tr></table></figure><h3 id="3-4-写入文档"><a href="#3-4-写入文档" class="headerlink" title="3.4 写入文档"></a>3.4 写入文档</h3><p>以下是对这段 <code>bulk_upload</code> 函数的简明解释，可用于博客正文或技术文档：</p><p>在 RAG 系统中，为了支持高效的语义检索，我们需要将原始文本与其对应的向量一起写入向量索引中。<code>bulk_upload</code> 函数正是完成这一任务的核心组件，它使用 Elasticsearch 的 <code>_bulk</code> 接口实现批量写入，显著提高写入效率。</p><ul><li><p>每条记录包含两个部分：</p><ol><li><p><code>index</code> 元数据，指定目标索引（<code>rag_demo</code>）及文档 <code>_id</code>。</p></li><li><p>实际文档内容，包括：</p><ul><li><code>content</code>：原始文本内容；</li><li><code>vec</code>：对应的文本向量，<strong>必须使用 <code>{&quot;values&quot;: [...]}</code> 的对象结构</strong>。</li></ul></li></ol></li><li><p>向量通过 <code>embed(t)</code> 获得，调用本地部署的 Ollama 模型（如 <code>nomic-embed-text</code>）生成。</p></li><li><p>所有数据最终编码为 JSON，通过 <code>Content-Type: application/x-ndjson</code> 提交到 <code>/_bulk</code> API 接口，实现一次性批量写入。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ---------- ② 批量写入：向量必须包 &#123;&quot;values&quot;: …&#125; ----------</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bulk_upload</span>(<span class="params">texts: <span class="built_in">list</span>[<span class="built_in">str</span>]</span>):</span><br><span class="line">    bulk = []</span><br><span class="line">    <span class="keyword">for</span> i, t <span class="keyword">in</span> <span class="built_in">enumerate</span>(texts):</span><br><span class="line">        bulk.append(&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_index&quot;</span>: INDEX, <span class="string">&quot;_id&quot;</span>: i &#125; &#125;)</span><br><span class="line">        bulk.append(&#123;</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: t,</span><br><span class="line">            <span class="string">&quot;vec&quot;</span>: &#123; <span class="string">&quot;values&quot;</span>: embed(t) &#125;     <span class="comment"># ★ 关键：对象格式</span></span><br><span class="line">        &#125;)</span><br><span class="line">    ndjson = <span class="string">&quot;\n&quot;</span>.join(json.dumps(d, ensure_ascii=<span class="literal">False</span>) <span class="keyword">for</span> d <span class="keyword">in</span> bulk) + <span class="string">&quot;\n&quot;</span></span><br><span class="line">    r = session.post(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/_bulk&quot;</span>,</span><br><span class="line">                     data=ndjson.encode(<span class="string">&quot;utf-8&quot;</span>),</span><br><span class="line">                     headers=&#123;<span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-ndjson&quot;</span>&#125;,</span><br><span class="line">                     verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    r.raise_for_status()</span><br></pre></td></tr></table></figure><h3 id="3-5-语义检索"><a href="#3-5-语义检索" class="headerlink" title="3.5 语义检索"></a>3.5 语义检索</h3><p>这段 <code>search</code> 函数的作用是在 RAG 系统中执行基于向量的语义检索，以下是适合用于博客中的简明解释：</p><p>RAG 系统的核心是从向量索引中找到与用户问题最相近的语义片段。<code>search</code> 函数即完成了这个过程，它调用 EasySearch 的向量检索接口，返回最相似的文本内容。</p><ol><li><p><strong>文本向量化</strong>：通过 <code>embed(question)</code> 把用户输入的问题转换成向量 <code>qvec</code>。</p></li><li><p><strong>构造检索请求</strong>：<br>使用 <code>knn_nearest_neighbors</code> 查询</p><ul><li><code>field</code>: 向量字段名（本例中是 <code>&quot;vec&quot;</code>）；</li><li><code>vec</code>: 查询向量，必须写成 <code>{ &quot;values&quot;: [...] }</code> 的对象结构；</li><li><code>model</code>: 向量近似检索模型（如 <code>&quot;lsh&quot;</code>）；</li><li><code>similarity</code>: 相似度度量方式（如 <code>&quot;cosine&quot;</code>）；</li><li><code>k</code>: 返回的结果数；</li><li><code>candidates</code>: 候选池大小，用于粗排优化检索效果。</li></ul></li><li><p><strong>发送请求并解析响应</strong>：<br>请求通过 Elasticsearch <code>_search</code> 接口提交，若返回不成功，则输出报错信息；成功后提取 <code>_source[&quot;content&quot;]</code> 字段，返回给上层用于回答生成。</p></li></ol><h3 id="✅-示例用途："><a href="#✅-示例用途：" class="headerlink" title="✅ 示例用途："></a>✅ 示例用途：</h3><p>用户提问：“张三是谁”，系统会将该问题向量化，然后在已有文本向量中进行相似度匹配，从而返回如“张三是法律专家……”的片段，作为构建回答的上下文。</p><p>这段逻辑是 RAG 模型“Retriever”阶段的核心，让大模型在“有知识”的基础上作答，提升准确性和实用性。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">search</span>(<span class="params">question: <span class="built_in">str</span>, top_k: <span class="built_in">int</span> = TOP_K</span>):</span><br><span class="line">    qvec = embed(question)</span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: top_k,</span><br><span class="line">        <span class="string">&quot;query&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;knn_nearest_neighbors&quot;</span>: &#123;        <span class="comment"># 若 Elastiknn 0.7.x 用 elastiknn_nearest_neighbors</span></span><br><span class="line">                <span class="string">&quot;field&quot;</span>: <span class="string">&quot;vec&quot;</span>,</span><br><span class="line">                <span class="string">&quot;vec&quot;</span>: &#123; <span class="string">&quot;values&quot;</span>: qvec &#125;,</span><br><span class="line">                <span class="string">&quot;model&quot;</span>: <span class="string">&quot;lsh&quot;</span>,</span><br><span class="line">                <span class="string">&quot;similarity&quot;</span>: <span class="string">&quot;cosine&quot;</span>,</span><br><span class="line">                <span class="string">&quot;k&quot;</span>: top_k,</span><br><span class="line">                <span class="string">&quot;candidates&quot;</span>: <span class="number">200</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    r = session.post(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/<span class="subst">&#123;INDEX&#125;</span>/_search&quot;</span>,</span><br><span class="line">                     json=body, verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> r.ok:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;== ES response ==&quot;</span>, r.text)</span><br><span class="line">    r.raise_for_status()</span><br><span class="line">    <span class="keyword">return</span> [hit[<span class="string">&quot;_source&quot;</span>][<span class="string">&quot;content&quot;</span>] <span class="keyword">for</span> hit <span class="keyword">in</span> r.json()[<span class="string">&quot;hits&quot;</span>][<span class="string">&quot;hits&quot;</span>]]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-6-主循环"><a href="#3-6-主循环" class="headerlink" title="3.6 主循环"></a>3.6 主循环</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ────────────── CLI 主逻辑 ──────────────</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    docs = [</span><br><span class="line">    <span class="comment"># 原来的 3 条</span></span><br><span class="line">    <span class="string">&quot;张三是法律专家，擅长合同法与知识产权。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;李四在人机交互领域研究多年，尤其关注可用性测试。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;王五是一名资深软件工程师，对云原生、DevOps 有丰富经验。&quot;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 新增 100 条</span></span><br><span class="line">    <span class="string">&quot;赵六是一名数据科学家，专注机器学习模型调优。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;孙七具有十年金融风控经验，熟悉巴塞尔协议。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;周八是区块链开发者，擅长智能合约安全审计。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;吴九长期研究边缘计算，在 IoT 网关架构方面有实践。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;郑十是资深 DBA，精通 MySQL 性能调优与高可用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;钱十一擅长云原生安全治理，主导多家企业零信任落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蒋十二是 GPU 运维专家，对 CUDA 优化有深入研究。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;沈十三专注深度学习推理加速，维护 TensorRT 插件。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;韩十四是 FaaS 平台架构师，关注冷启动优化策略。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;姚十五从事量化交易算法开发，对高频数据处理熟练。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邵十六是渗透测试工程师，擅长 Web 漏洞挖掘与利用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;汪十七主攻 AIGC 版权合规，为多家媒体机构提供方案。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;孔十八研究联邦学习，解决数据孤岛隐私问题。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;曹十九负责 SRE 团队，精通混沌工程与错误预算管理。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;严二十是网络取证专家，参与多起重大案件分析。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;华二一研发 AutoML 平台，降低模型训练门槛。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;雷二二在 5G 边缘网协同计算领域发表多篇论文。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;凌二三是 DevRel 经理，推动开源社区增长。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;史二四对 RAG 架构有深入实践，优化检索召回率。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;阮二五是 WebGPU 先行者，致力提升前端渲染性能。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;杭二六主导多云成本治理项目，节省 30% 预算。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;乔二七是一名 AIGC Prompt 工程师，专精多模态指令设计。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;詹二八擅长大规模 AB 测试框架落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;顾二九是 Serverless 架构布道者，编写多本技术书籍。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;龚三十关注 DORA 指标，用数据驱动 DevOps 改进。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;计三一是 API 网关专家，实现百万 QPS 低延迟。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蒲三二研究影像分割模型，用于医学辅助诊断。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邱三三是 Zig 语言早期贡献者，推行内存安全编码。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;庄三四长期维护 Kafka 集群，擅长 Topic 规划。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宫三五是低代码平台架构师，关注插件生态。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蓝三六研究 ICEBERG 表格式，提升湖仓查询效率。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;聂三七在安全编排 SOAR 产品设计上经验丰富。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;陆三八主导 SaaS 产品国际化，本地化流程成熟。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;温三九负责混合云 DR 方案，实现分钟级切换。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;袁四十是语音合成工程师，优化多 speaker 适配。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;贾四一深入研究 DDD，帮助团队理清领域边界。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;伏四二从事实时风控大数据平台架构，处理亿级流量。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;程四三是 ARM SoC 驱动工程师，对电源管理熟悉。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;屈四四在 Federated GraphQL 网关治理方面有案例。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;申四五带队实现 MLOps 自动化发布流程。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;罗四六研究 VDBMS，支持 PB 级向量检索。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;祝四七是 HTAP 数据库布道者，优化混合负载。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;左四八在 IAM 与 RBAC 设计领域深耕。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;冷四九是链路可观测性工程师，推广 OTEL 标准。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;包五十投入异构计算调度框架研究。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;滑五一精通 eBPF 在安全可观测性场景的落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;柴五二研究量子安全算法，对国密迁移方案熟悉。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;谈五三是内核安全研究员，发现多个 0-day 漏洞。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;鄢五四主导 SaaS 计费系统重构，支持灵活套餐。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邸五五是绿色数据中心规划师，推动 PUE 降到 1.2。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;候五六在自动驾驶 SLAM 算法具有专利。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;古五七关注 CDP 架构，连接多源营销数据。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;丁五八是 FPGA 加速工程师，实现低延迟推理。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;靳五九研究 WASM 边缘运行时，降低冷启动。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;柴六十在 DevSecOps 流水线集成方面经验丰富。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;花六一策划大规模黑客马拉松，促成 500+ 项目孵化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;牛六二是边缘 AI 推理框架作者，重视功耗优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;焦六三研究自监督学习在推荐系统的应用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;商六四是 Rust Web 开发者，推广零拷贝 JSON 解析。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;阎六五投入数字孪生城市平台研发。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;弓六六主攻 OTA 升级安全，覆盖汽车 ECU。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;怀六七是 MAC 数据平面专家，优化转发性能。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宓六八参与多场灾备演练，完善演练脚本体系。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;郝六九是 PKI 架构师，设计大规模证书生命周期。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;嵇七十致力于多媒体编解码标准化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邝七一研究 EDA 自动布线算法。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桑七二打造 AI 工厂流水线，实现模型快速迭代。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桂七三专注 DPU 加速网络虚拟化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;麻七四是 Supabase 中国社区维护者，推广 BaaS。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;仇七五实现企业级 KYC 流程自动化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;薄七六研究多模态情感分析，用于客服质检。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;谯七七是 SD-WAN 产品经理，聚焦海外专线优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;巫七八负责 Kafka to Pulsar 迁移方案。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桑七九在 DAG 引擎优化 CPS 流水线。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邬八十研究端侧 LLM 蒸馏压缩。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;臧八一是三维重建算法工程师，服务文博数字化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;禾八二专攻 S3 兼容对象存储网关。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;原八三参与可信执行环境 TEE 方案落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;淦八四是工业互联网安全规划顾问。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;练八五实现 GPU 多租户 QoS 调度器。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;禹八六关注跨境合规要求，精通 GDPR。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;廉八七是 SDN 控制器开源贡献者。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;亓八八专注高并发长连接网关。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宗八九打造零代码机器学习平台。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;公冶九十研究 PIM 存内计算架构。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;红九一是 MESH 网络性能调优专家。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;眭九二致力于 AI 合成音频版权检测。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;米九三推动碳排放数据平台建设。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;隗九四是机器人运动规划算法专家。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;拉九五研究语义分割在遥感图像的应用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蔺九六负责 0-RTT QUIC 协议优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;臧九七专注 BERT 在法律文本的细粒度实体抽取。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;昝九八是 RPG 游戏 AI NPC 行为树作者。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;贝九九研究差分隐私在广告数据的实践。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;施一百主导云原生 API 安全监控平台。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;伏一零一优化 Kafka Connect 大规模同步。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;堵一零二研究车辆 V2X 协议栈实现。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;莎一零三聚焦 A/B 决策平台可视化。&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    dim = <span class="built_in">len</span>(embed(<span class="string">&quot;维度探测&quot;</span>))      <span class="comment"># 动态探测向量维度</span></span><br><span class="line">    create_index(dim)</span><br><span class="line">    bulk_upload(docs)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;[√] 初始化完成（向量维度 <span class="subst">&#123;dim&#125;</span>）。开始提问，Ctrl+C 退出。\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            q = <span class="built_in">input</span>(<span class="string">&quot;Q: &quot;</span>).strip()</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> q:</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            passages = search(q)</span><br><span class="line">            context  = <span class="string">&quot;\n&quot;</span>.join(<span class="string">f&quot;资料<span class="subst">&#123;i+<span class="number">1</span>&#125;</span>：<span class="subst">&#123;p&#125;</span>&quot;</span> <span class="keyword">for</span> i, p <span class="keyword">in</span> <span class="built_in">enumerate</span>(passages))</span><br><span class="line">            prompt   = (<span class="string">f&quot;已知资料如下：\n<span class="subst">&#123;context&#125;</span>\n\n请根据以上资料回答用户问题：<span class="subst">&#123;q&#125;</span>&quot;</span>)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;\nA:&quot;</span>, chat(prompt))</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;引用:&quot;</span>, passages, <span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\nBye!&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="4-运行效果"><a href="#4-运行效果" class="headerlink" title="4. 运行效果"></a>4. 运行效果</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Q: 张三擅长什么</span><br><span class="line">A: 张三是一名法律专家，专长领域为合同法与知识产权。</span><br><span class="line">引用: [&#x27;张三是法律专家，擅长合同法与知识产权。&#x27;]</span><br></pre></td></tr></table></figure><p>结果如下：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f6369edd-b2cb-4f7d-b868-c3b0e4044322.png" alt="f6369edd-b2cb-4f7d-b868-c3b0e4044322"></p><p>完整代码：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os, json, requests, warnings</span><br><span class="line"><span class="keyword">from</span> ollama <span class="keyword">import</span> Client</span><br><span class="line"><span class="keyword">from</span> requests.packages.urllib3.exceptions <span class="keyword">import</span> InsecureRequestWarning</span><br><span class="line">warnings.filterwarnings(<span class="string">&quot;ignore&quot;</span>, category=InsecureRequestWarning)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ────────────── 配置区 ──────────────</span></span><br><span class="line">ES_URL   = os.getenv(<span class="string">&quot;ES_URL&quot;</span>, <span class="string">&quot;https://localhost:9200&quot;</span>)</span><br><span class="line">ES_AUTH  = (<span class="string">&quot;admin&quot;</span>, <span class="string">&quot;c59a759f31e901e8d279&quot;</span>)   <span class="comment"># 无认证设为 None</span></span><br><span class="line">INDEX    = <span class="string">&quot;rag_demo&quot;</span></span><br><span class="line"></span><br><span class="line">OLLAMA_HOST  = os.getenv(<span class="string">&quot;OLLAMA_URL&quot;</span>, <span class="string">&quot;http://localhost:11434&quot;</span>)</span><br><span class="line">EMBED_MODEL  = <span class="string">&quot;nomic-embed-text&quot;</span>         <span class="comment"># 向量模型</span></span><br><span class="line">CHAT_MODEL   = <span class="string">&quot;deepseek-r1:7b&quot;</span>           <span class="comment"># 对话模型</span></span><br><span class="line">TOP_K        = <span class="number">4</span></span><br><span class="line">NUM_CAND     = <span class="number">200</span>                        <span class="comment"># kNN 先粗召回条数</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">client = Client(host=OLLAMA_HOST)</span><br><span class="line">session = requests.Session()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ────────────── Ollama 封装 ──────────────</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">embed</span>(<span class="params">text: <span class="built_in">str</span></span>) -&gt; <span class="built_in">list</span>[<span class="built_in">float</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;返回文本向量 (list[float])&quot;&quot;&quot;</span></span><br><span class="line">    resp = client.embeddings(model=EMBED_MODEL, prompt=text)</span><br><span class="line">    <span class="keyword">return</span> resp[<span class="string">&quot;embedding&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">chat</span>(<span class="params">prompt: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;与聊天模型对话（完整回复）&quot;&quot;&quot;</span></span><br><span class="line">    resp = client.chat(</span><br><span class="line">        model=CHAT_MODEL,</span><br><span class="line">        messages=[&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;],</span><br><span class="line">        <span class="comment"># stream=False</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> resp[<span class="string">&quot;message&quot;</span>][<span class="string">&quot;content&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------- ① 创建索引：Elastiknn 映射 ----------</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_index</span>(<span class="params">dim: <span class="built_in">int</span></span>):</span><br><span class="line">    mapping = &#123;</span><br><span class="line">        <span class="string">&quot;settings&quot;</span>: &#123; <span class="string">&quot;index.knn&quot;</span>: <span class="literal">True</span> &#125;,</span><br><span class="line">        <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;content&quot;</span>: &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&quot;</span> &#125;,</span><br><span class="line">                <span class="string">&quot;vec&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;knn_dense_float_vector&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;knn&quot;</span>: &#123;</span><br><span class="line">                        <span class="string">&quot;dims&quot;</span>: dim,</span><br><span class="line">                        <span class="string">&quot;model&quot;</span>: <span class="string">&quot;lsh&quot;</span>,        <span class="comment"># 也可 hnsw / exact</span></span><br><span class="line">                        <span class="string">&quot;similarity&quot;</span>: <span class="string">&quot;cosine&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;L&quot;</span>: <span class="number">99</span>,</span><br><span class="line">                        <span class="string">&quot;k&quot;</span>: <span class="number">1</span></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><span class="line">    &#125;</span><br><span class="line">    r = session.put(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/<span class="subst">&#123;INDEX&#125;</span>&quot;</span>,</span><br><span class="line">                    json=mapping, verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    <span class="keyword">if</span> r.status_code <span class="keyword">not</span> <span class="keyword">in</span> (<span class="number">200</span>, <span class="number">201</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;resource_already_exists_exception&quot;</span> <span class="keyword">not</span> <span class="keyword">in</span> r.text:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Create index error:\n&quot;</span>, r.text)</span><br><span class="line">            r.raise_for_status()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------- ② 批量写入：向量必须包 &#123;&quot;values&quot;: …&#125; ----------</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bulk_upload</span>(<span class="params">texts: <span class="built_in">list</span>[<span class="built_in">str</span>]</span>):</span><br><span class="line">    bulk = []</span><br><span class="line">    <span class="keyword">for</span> i, t <span class="keyword">in</span> <span class="built_in">enumerate</span>(texts):</span><br><span class="line">        bulk.append(&#123; <span class="string">&quot;index&quot;</span>: &#123; <span class="string">&quot;_index&quot;</span>: INDEX, <span class="string">&quot;_id&quot;</span>: i &#125; &#125;)</span><br><span class="line">        bulk.append(&#123;</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: t,</span><br><span class="line">            <span class="string">&quot;vec&quot;</span>: &#123; <span class="string">&quot;values&quot;</span>: embed(t) &#125;     <span class="comment"># ★ 关键：对象格式</span></span><br><span class="line">        &#125;)</span><br><span class="line">    ndjson = <span class="string">&quot;\n&quot;</span>.join(json.dumps(d, ensure_ascii=<span class="literal">False</span>) <span class="keyword">for</span> d <span class="keyword">in</span> bulk) + <span class="string">&quot;\n&quot;</span></span><br><span class="line">    r = session.post(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/_bulk&quot;</span>,</span><br><span class="line">                     data=ndjson.encode(<span class="string">&quot;utf-8&quot;</span>),</span><br><span class="line">                     headers=&#123;<span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-ndjson&quot;</span>&#125;,</span><br><span class="line">                     verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    r.raise_for_status()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------- ③ 查询：knn_nearest_neighbors ----------</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search</span>(<span class="params">question: <span class="built_in">str</span>, top_k: <span class="built_in">int</span> = TOP_K</span>):</span><br><span class="line">    qvec = embed(question)</span><br><span class="line">    body = &#123;</span><br><span class="line">        <span class="string">&quot;size&quot;</span>: top_k,</span><br><span class="line">        <span class="string">&quot;query&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;knn_nearest_neighbors&quot;</span>: &#123;        <span class="comment"># 若 Elastiknn 0.7.x 用 elastiknn_nearest_neighbors</span></span><br><span class="line">                <span class="string">&quot;field&quot;</span>: <span class="string">&quot;vec&quot;</span>,</span><br><span class="line">                <span class="string">&quot;vec&quot;</span>: &#123; <span class="string">&quot;values&quot;</span>: qvec &#125;,</span><br><span class="line">                <span class="string">&quot;model&quot;</span>: <span class="string">&quot;lsh&quot;</span>,</span><br><span class="line">                <span class="string">&quot;similarity&quot;</span>: <span class="string">&quot;cosine&quot;</span>,</span><br><span class="line">                <span class="string">&quot;k&quot;</span>: top_k,</span><br><span class="line">                <span class="string">&quot;candidates&quot;</span>: <span class="number">200</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    r = session.post(<span class="string">f&quot;<span class="subst">&#123;ES_URL&#125;</span>/<span class="subst">&#123;INDEX&#125;</span>/_search&quot;</span>,</span><br><span class="line">                     json=body, verify=<span class="literal">False</span>, auth=ES_AUTH)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> r.ok:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;== ES response ==&quot;</span>, r.text)</span><br><span class="line">    r.raise_for_status()</span><br><span class="line">    <span class="keyword">return</span> [hit[<span class="string">&quot;_source&quot;</span>][<span class="string">&quot;content&quot;</span>] <span class="keyword">for</span> hit <span class="keyword">in</span> r.json()[<span class="string">&quot;hits&quot;</span>][<span class="string">&quot;hits&quot;</span>]]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ────────────── CLI 主逻辑 ──────────────</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    docs = [</span><br><span class="line">    <span class="comment"># 原来的 3 条</span></span><br><span class="line">    <span class="string">&quot;张三是法律专家，擅长合同法与知识产权。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;李四在人机交互领域研究多年，尤其关注可用性测试。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;王五是一名资深软件工程师，对云原生、DevOps 有丰富经验。&quot;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 新增 100 条</span></span><br><span class="line">    <span class="string">&quot;赵六是一名数据科学家，专注机器学习模型调优。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;孙七具有十年金融风控经验，熟悉巴塞尔协议。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;周八是区块链开发者，擅长智能合约安全审计。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;吴九长期研究边缘计算，在 IoT 网关架构方面有实践。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;郑十是资深 DBA，精通 MySQL 性能调优与高可用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;钱十一擅长云原生安全治理，主导多家企业零信任落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蒋十二是 GPU 运维专家，对 CUDA 优化有深入研究。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;沈十三专注深度学习推理加速，维护 TensorRT 插件。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;韩十四是 FaaS 平台架构师，关注冷启动优化策略。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;姚十五从事量化交易算法开发，对高频数据处理熟练。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邵十六是渗透测试工程师，擅长 Web 漏洞挖掘与利用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;汪十七主攻 AIGC 版权合规，为多家媒体机构提供方案。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;孔十八研究联邦学习，解决数据孤岛隐私问题。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;曹十九负责 SRE 团队，精通混沌工程与错误预算管理。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;严二十是网络取证专家，参与多起重大案件分析。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;华二一研发 AutoML 平台，降低模型训练门槛。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;雷二二在 5G 边缘网协同计算领域发表多篇论文。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;凌二三是 DevRel 经理，推动开源社区增长。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;史二四对 RAG 架构有深入实践，优化检索召回率。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;阮二五是 WebGPU 先行者，致力提升前端渲染性能。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;杭二六主导多云成本治理项目，节省 30% 预算。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;乔二七是一名 AIGC Prompt 工程师，专精多模态指令设计。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;詹二八擅长大规模 AB 测试框架落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;顾二九是 Serverless 架构布道者，编写多本技术书籍。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;龚三十关注 DORA 指标，用数据驱动 DevOps 改进。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;计三一是 API 网关专家，实现百万 QPS 低延迟。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蒲三二研究影像分割模型，用于医学辅助诊断。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邱三三是 Zig 语言早期贡献者，推行内存安全编码。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;庄三四长期维护 Kafka 集群，擅长 Topic 规划。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宫三五是低代码平台架构师，关注插件生态。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蓝三六研究 ICEBERG 表格式，提升湖仓查询效率。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;聂三七在安全编排 SOAR 产品设计上经验丰富。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;陆三八主导 SaaS 产品国际化，本地化流程成熟。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;温三九负责混合云 DR 方案，实现分钟级切换。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;袁四十是语音合成工程师，优化多 speaker 适配。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;贾四一深入研究 DDD，帮助团队理清领域边界。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;伏四二从事实时风控大数据平台架构，处理亿级流量。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;程四三是 ARM SoC 驱动工程师，对电源管理熟悉。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;屈四四在 Federated GraphQL 网关治理方面有案例。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;申四五带队实现 MLOps 自动化发布流程。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;罗四六研究 VDBMS，支持 PB 级向量检索。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;祝四七是 HTAP 数据库布道者，优化混合负载。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;左四八在 IAM 与 RBAC 设计领域深耕。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;冷四九是链路可观测性工程师，推广 OTEL 标准。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;包五十投入异构计算调度框架研究。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;滑五一精通 eBPF 在安全可观测性场景的落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;柴五二研究量子安全算法，对国密迁移方案熟悉。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;谈五三是内核安全研究员，发现多个 0-day 漏洞。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;鄢五四主导 SaaS 计费系统重构，支持灵活套餐。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邸五五是绿色数据中心规划师，推动 PUE 降到 1.2。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;候五六在自动驾驶 SLAM 算法具有专利。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;古五七关注 CDP 架构，连接多源营销数据。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;丁五八是 FPGA 加速工程师，实现低延迟推理。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;靳五九研究 WASM 边缘运行时，降低冷启动。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;柴六十在 DevSecOps 流水线集成方面经验丰富。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;花六一策划大规模黑客马拉松，促成 500+ 项目孵化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;牛六二是边缘 AI 推理框架作者，重视功耗优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;焦六三研究自监督学习在推荐系统的应用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;商六四是 Rust Web 开发者，推广零拷贝 JSON 解析。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;阎六五投入数字孪生城市平台研发。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;弓六六主攻 OTA 升级安全，覆盖汽车 ECU。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;怀六七是 MAC 数据平面专家，优化转发性能。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宓六八参与多场灾备演练，完善演练脚本体系。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;郝六九是 PKI 架构师，设计大规模证书生命周期。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;嵇七十致力于多媒体编解码标准化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邝七一研究 EDA 自动布线算法。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桑七二打造 AI 工厂流水线，实现模型快速迭代。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桂七三专注 DPU 加速网络虚拟化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;麻七四是 Supabase 中国社区维护者，推广 BaaS。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;仇七五实现企业级 KYC 流程自动化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;薄七六研究多模态情感分析，用于客服质检。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;谯七七是 SD-WAN 产品经理，聚焦海外专线优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;巫七八负责 Kafka to Pulsar 迁移方案。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;桑七九在 DAG 引擎优化 CPS 流水线。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;邬八十研究端侧 LLM 蒸馏压缩。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;臧八一是三维重建算法工程师，服务文博数字化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;禾八二专攻 S3 兼容对象存储网关。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;原八三参与可信执行环境 TEE 方案落地。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;淦八四是工业互联网安全规划顾问。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;练八五实现 GPU 多租户 QoS 调度器。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;禹八六关注跨境合规要求，精通 GDPR。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;廉八七是 SDN 控制器开源贡献者。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;亓八八专注高并发长连接网关。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;宗八九打造零代码机器学习平台。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;公冶九十研究 PIM 存内计算架构。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;红九一是 MESH 网络性能调优专家。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;眭九二致力于 AI 合成音频版权检测。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;米九三推动碳排放数据平台建设。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;隗九四是机器人运动规划算法专家。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;拉九五研究语义分割在遥感图像的应用。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;蔺九六负责 0-RTT QUIC 协议优化。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;臧九七专注 BERT 在法律文本的细粒度实体抽取。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;昝九八是 RPG 游戏 AI NPC 行为树作者。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;贝九九研究差分隐私在广告数据的实践。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;施一百主导云原生 API 安全监控平台。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;伏一零一优化 Kafka Connect 大规模同步。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;堵一零二研究车辆 V2X 协议栈实现。&quot;</span>,</span><br><span class="line">    <span class="string">&quot;莎一零三聚焦 A/B 决策平台可视化。&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    dim = <span class="built_in">len</span>(embed(<span class="string">&quot;维度探测&quot;</span>))      <span class="comment"># 动态探测向量维度</span></span><br><span class="line">    create_index(dim)</span><br><span class="line">    bulk_upload(docs)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;[√] 初始化完成（向量维度 <span class="subst">&#123;dim&#125;</span>）。开始提问，Ctrl+C 退出。\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            q = <span class="built_in">input</span>(<span class="string">&quot;Q: &quot;</span>).strip()</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> q:</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            passages = search(q)</span><br><span class="line">            context  = <span class="string">&quot;\n&quot;</span>.join(<span class="string">f&quot;资料<span class="subst">&#123;i+<span class="number">1</span>&#125;</span>：<span class="subst">&#123;p&#125;</span>&quot;</span> <span class="keyword">for</span> i, p <span class="keyword">in</span> <span class="built_in">enumerate</span>(passages))</span><br><span class="line">            prompt   = (<span class="string">f&quot;已知资料如下：\n<span class="subst">&#123;context&#125;</span>\n\n请根据以上资料回答用户问题：<span class="subst">&#123;q&#125;</span>&quot;</span>)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;\nA:&quot;</span>, chat(prompt))</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;引用:&quot;</span>, passages, <span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;\nBye!&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><blockquote><p><strong>EasySearch × Ollama</strong> 让我们在本地就能体验到“RAG 的爽点”：检索带来实时、可信的上下文，大模型负责自然语言表达，二者合体即是一个可交付的“企业私有知识助手”。如果你也想在内网快速验证 PoC，这份脚本拷过去改两个地址即可开跑。祝玩得开心！</p></blockquote>]]></content>
    
    
    <summary type="html">用 Easysearch 和 Ollama SDK 半小时搭建本地 RAG 检索增强问答系统</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>我用Amazon Q写了一个Docker客户端，并上架了懒猫微服商店</title>
    <link href="https://blog.no-claw.com/posts/8f0212bf/"/>
    <id>https://blog.no-claw.com/posts/8f0212bf/</id>
    <published>2025-06-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.containly">https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.containly</a></p><p>自从被种草了 Amazon Q，我陆陆续续写了不少小软件，其中这个 Docker 客户端是一个典型的例子，比较符合自己平时使用的习惯，也分享给一些朋友和 NAS 爱好者来用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606190108571.png" alt="image-20250606190108571"></p><p>故事还要用上次折腾黑群晖说起，本意想把 NAS 和打印机共享二合一的，所以把闲着的软路由做了改装。顺便使用 Docker 跑一些服务，有老本行的 ES 集群，也有自己写的一些工具类型的服务。</p><span id="more"></span><p>随着时间增长，部署的服务多了，时间长了就会忘记服务的端口，甚至还要登录群晖 Web 端进行查看，群晖的 Container Manager 很好用，就是登录的密码策略比较复杂，每次登录都比较麻烦，所以后来使用了一个 HomePage 来保存这些服务。但是每次调试 Docker 都非常麻烦。与 Portainer 相比，我需要的只是一个简洁的面板来查看容器的 URI、状态，并进行启停操作，因此我决定自己开发一个。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606164850382.png" alt="image-20250606164850382"></p><p>这个是群晖的 Container Manager，后面还有很多容器。记住这么多端口然后随时维护绝对不是一个容易的事。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606164632836.png" alt="image-20250606164632836"></p><p>我开发容器面板叫做 Containly， 是一个 Container 的管理工具。最早的时候用我是用 GPT 写的。但是随着项目越来越大，GPT 每次都会丢一些东西，而且还没办法操作本地目录，后来才转向了 Amazon Q，这个版本还是用 Q CLI 来做的。</p><p>于是写好之后我把这个 APP 上架了懒猫微服的商店，这个是一款国产化的 NAS，可玩性非常高，对开发者也十分友好。上线当日就有很多开发者安装使用了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/c970b2f8c3fea3a9246510fd1e20d9aa.png" alt="c970b2f8c3fea3a9246510fd1e20d9aa"></p><p>Containly 的核心功能是通过目录映射的 Docker 引擎读取所有容器信息，包括容器的启动、退出、停止及其他状态。例如，当容器处于“Create”状态时，它会被标记为“Other”状态，便于管理。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250520104141112.png" alt="image-20250520104141112"></p><p>默认情况下，每个容器卡片会显示容器的网桥信息、端口映射和 URL。默认使用 HTTP 协议，鼠标悬停时，会在右侧显示操作按钮。通过点击这些按钮，操作会被保留，再次点击会隐藏，这样子就整个比较美观。</p><p>按钮功能包括：</p><ul><li>停止&#x2F;启动</li><li>重启</li><li>查看日志</li><li>SSH 进入容器</li><li>切换 HTTP&#x2F;HTTPS</li><li>黑名单管理</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606190151980.png" alt="image-20250606190151980"></p><p>此外，Containly 还提供了一个输入框，用户可以输入需要监控的 NAS 域名，面板会自动根据域名和端口拼接成 URI，并存储在 localStorage 中。更进一步，Containly 还支持暗黑模式，提升了用户体验。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606185740253.png" alt="image-20250606185740253"></p><p>另外如果多节点部署服务的话，还可以把从节点放入黑名单，这样子就只显示主节点的信息，面板就比较清爽。如果需要从节点的信息再从黑名单移除。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606190231618.png" alt="image-20250606190231618"></p><p>利用面板的 SSH 功能， 能够直接从面板进去访问容器的 SHELL，不用执行再 docker exec 的命令。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606192901984.png" alt="image-20250606192901984"></p><p>看日志也很方便，也无需再使用 docker logs，这样调试容器的时候就很方便了。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606192939172.png" alt="image-20250606192939172"></p><p>我已经打包好了 Docker 镜像并配置了 GitHub Actions，便于自动化部署。你可以通过以下方式部署 Containly：</p><h4 id="Docker-部署命令"><a href="#Docker-部署命令" class="headerlink" title="Docker 部署命令"></a><strong>Docker 部署命令</strong></h4><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">docker run -d \</span><br><span class="line">  --name containly \</span><br><span class="line">  -p 5000:5000 \  <span class="comment"># 映射容器端口到主机</span></span><br><span class="line">  -v /var/run/docker.sock:/var/run/docker.sock \  <span class="comment"># 挂载Docker socket，允许访问宿主机Docker</span></span><br><span class="line">  cloudsmithy/containly:latest  <span class="comment"># 使用最新版本的Containly镜像</span></span><br></pre></td></tr></table></figure><h4 id="Compose-配置"><a href="#Compose-配置" class="headerlink" title="Compose 配置"></a><strong>Compose 配置</strong></h4><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">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">containly:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">cloudsmithy/containly:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5000:5000&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/run/docker.sock:/var/run/docker.sock</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><p>这个是使用 Q 修改的部分代码截图：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/2a4ea3b79c3998ba0742e79cee8f1672.png" alt="2a4ea3b79c3998ba0742e79cee8f1672"></p><p>后来机缘巧合之下用了 Q pro，看来也不能优化再多。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606184151764.png" alt="image-20250606184151764"></p><p>除了使用 Q CLI，我们还可以通过安装 VSCode 和 JetBrains 插件来使用 Q，安装插件后，免费版本可以使用 Builder ID 登录，Pro 版本则支持使用 IAM Identity Center 登录。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606190814362.png" alt="image-20250606190814362"></p><p>在 VSCode 中，你可以通过 Q 聊天面板与 Q 进行交互，并且支持中文聊天。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606191035548.png" alt="image-20250606191035548"></p><p>与 GPT 相比，Q 的优势在于它可以直接操作本地文件，用户可以直接在文件夹中生成工程文件，极大提升了开发效率。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606191616025.png" alt="image-20250606191616025"></p>]]></content>
    
    
    <summary type="html">使用 Amazon Q 开发 Docker 客户端并上架懒猫微服商店的全过程</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（七）：懒猫的镜像仓库</title>
    <link href="https://blog.no-claw.com/posts/8cdf9a5/"/>
    <id>https://blog.no-claw.com/posts/8cdf9a5/</id>
    <published>2025-06-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>这几年国内访问 Dockerhub 总有类似的问题，所以很多情况都建议自建 docker 仓库，比如 Habor，Nexus 或者。毕竟 GFW 的花样我们想不到。</p><p>云厂商的 ECR。但是对于个人玩家或者爱好者来说这一套实在是太重太难以维护了，可能也就是这个原因，懒猫微服也提供了镜像仓库的和本地仓库的功能。</p><p>先说镜像仓库，就是从懒猫微服的服务器上先拉 Docker image，然后再推到自己的 registry。这一步骤通常由出海链路比较好的机器来完成。</p><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></pre></td><td class="code"><pre><span class="line">lzc-cli appstore copy-image cloudsmithy/lazycat-nav</span><br></pre></td></tr></table></figure><p>执行之后就可以看到镜像仓库，registry.lazycat.cloud 这个地址。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606062550912.png" alt="镜像同步界面截图"></p><p><strong>重要说明</strong>：<br>这个地址只能在微服环境中使用，如果在其他地方使用会出现认证错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull registry.lazycat.cloud/u04123229/cloudsmithy/lazycat-nav:854b14e73ab0726e</span><br><span class="line">Error response from daemon: Head <span class="string">&quot;https://registry.lazycat.cloud/v2/u04123229/cloudsmithy/lazycat-nav/manifests/854b14e73ab0726e&quot;</span>: no basic auth credentials</span><br></pre></td></tr></table></figure><p>其实就一个加了认证的 registry，只是微服有凭证可以直接进。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606081108772.png" alt="认证错误截图"></p><h3 id="内置-Docker-Registry-V2"><a href="#内置-Docker-Registry-V2" class="headerlink" title="内置 Docker Registry V2"></a>内置 Docker Registry V2</h3><p>懒猫微服内置了一个简化版的 registry，完整使用流程如下：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 构建x86架构镜像</span></span><br><span class="line">docker build --platform linux/amd64 -t helloworld:latest .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前微服名称</span></span><br><span class="line">BOXNAME=$(lzc-cli box default)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 为镜像打上仓库标签</span></span><br><span class="line">docker tag helloworld:latest dev.<span class="variable">$BOXNAME</span>.heiyu.space/helloworld:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 推送镜像到仓库</span></span><br><span class="line">docker push dev.<span class="variable">$BOXNAME</span>.heiyu.space/helloworld:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从仓库拉取镜像</span></span><br><span class="line">docker pull dev.<span class="variable">$BOXNAME</span>.heiyu.space/helloworld:latest</span><br></pre></td></tr></table></figure><p><strong>实际操作演示</strong>：</p><p>在 M2 芯片设备上的构建过程：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606065450474.png" alt="M2构建截图"></p><p>在 Orbstack 上拉取验证（先删除本地镜像再拉取）：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606065226304.png" alt="镜像拉取验证"></p><p>通过 API 查看镜像列表：<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250606075246823.png" alt="镜像列表API"></p><p>这个简单版本的 docker registry v2，后面用来做跑 CI 的镜像仓库应该是够了。</p><h3 id="插曲："><a href="#插曲：" class="headerlink" title="插曲："></a>插曲：</h3><p>如果遇到这个问题，千万别信 AI 是 buildX 坏了，就是中文路径的问题。（AI 查一小时。Google 一分钟）</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">docker-container:multiarchERROR: failed to dial gRPC: rpc error: code = Internal desc = rpc error: code= Internal desc =header key<span class="string">&quot;x-docker-expose-session-sharedkey&quot;</span> contains value with non-printable ASCII characters</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">懒猫微服内置镜像仓库的使用方法与管理技巧。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（六）：以纸砚双拼为例，快速移植纯前端项目到懒猫微服</title>
    <link href="https://blog.no-claw.com/posts/7294709a/"/>
    <id>https://blog.no-claw.com/posts/7294709a/</id>
    <published>2025-06-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前移植了一款颜值极高的纸砚双拼，想着纯前端类的软件应该都是一样的操作，所以把操作记录下来，方便以后复习查看，也相当于一个教程吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250531195827292.png" alt="image-20250531195827292"></p><p>对于这种场景来说，前端项目开发完成后，我们将构建好的静态页面部署到服务器上。借助 Docker 和 Nginx，可以实现一套<strong>轻量、快速、可移植</strong>的部署方式，特别适合懒猫微服这种容器化的环境，也很适合日常调试测试使用。</p><p>下面将手把手教你如何打包一个 Vue 或 React 项目，并通过 Docker + Nginx 构建一个可复用的前端部署容器镜像，然后就可以把这个部署到懒猫微服删，当然也适用于各种 Linux + Docker 环境。</p><hr><h3 id="1-·-准备目录结构"><a href="#1-·-准备目录结构" class="headerlink" title="1 · 准备目录结构"></a>1 · 准备目录结构</h3><p>纸砚双拼是 Vue 的项目，所以直接执行这个命令打包</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">npm i</span><br><span class="line">npm run build</span><br></pre></td></tr></table></figure><p>打包完成后，项目根目录下会生成一个 <code>dist/</code> 文件夹，里面包含：</p><ul><li><code>index.html</code></li><li>静态 JS、CSS 资源</li><li>图片等其他引用资源</li></ul><p>基本所有前端工程化的流程都是部署这个 dist&#x2F;静态目录。</p><span id="more"></span><p>在开始容器化之前，我们先来整理一下项目结构。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">my-static-site/</span><br><span class="line">├── dist/                  # 前端打包后的静态资源目录</span><br><span class="line">├── nginx.conf             # Nginx 配置文件</span><br><span class="line">├── Dockerfile             # Docker 构建文件</span><br><span class="line">└── docker-compose.yml     # docker-compose 管理配置（可选）</span><br></pre></td></tr></table></figure><p>主要就是把 <code>dist/ </code> 目录映射到 Nginx 的根目录。</p><h4 id="dockerignore（防止把-node-modules-等大文件复制进镜像）"><a href="#dockerignore（防止把-node-modules-等大文件复制进镜像）" class="headerlink" title=".dockerignore（防止把 node_modules 等大文件复制进镜像）"></a><code>.dockerignore</code>（防止把 node_modules 等大文件复制进镜像）</h4><p>项目根目录建议加个 <code>.dockerignore</code> 文件，防止无用文件进入镜像、浪费空间：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">node_modules</span><br><span class="line">.git</span><br><span class="line">.vscode</span><br><span class="line">*.log</span><br><span class="line">dist</span><br></pre></td></tr></table></figure><p>这里的 <code>dist</code> 虽然是构建产物，但因为我们用的是双阶段构建，会在容器里重新生成，不需要提前放入。</p><h3 id="2-·-Dockerfile（双阶段构建：先构建，再用-Nginx-托管）"><a href="#2-·-Dockerfile（双阶段构建：先构建，再用-Nginx-托管）" class="headerlink" title="2 · Dockerfile（双阶段构建：先构建，再用 Nginx 托管）"></a>2 · Dockerfile（双阶段构建：先构建，再用 Nginx 托管）</h3><p>我们采用双阶段构建方案，能够最大限度减小最终镜像体积，只包含运行时必须的内容。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">FROM</span> node:<span class="number">18</span>-alpine AS build</span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制package.json和package-lock.json</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package*.json ./</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装依赖 - 使用npm install代替npm ci</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm install</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制源代码</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建应用</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm run build</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生产阶段</span></span><br><span class="line"><span class="keyword">FROM</span> nginx:alpine</span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制构建产物到nginx目录</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/build /usr/share/nginx/html</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制nginx配置</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> nginx.conf /etc/nginx/conf.d/default.conf</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 暴露80端口</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动nginx</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;nginx&quot;</span>, <span class="string">&quot;-g&quot;</span>, <span class="string">&quot;daemon off;&quot;</span>]</span></span><br></pre></td></tr></table></figure><blockquote><ul><li>使用 <code>node:alpine</code> 和 <code>nginx:alpine</code> 轻量镜像，构建出来的镜像体积非常小</li><li>分阶段构建，确保生产镜像中没有多余文件</li></ul></blockquote><hr><h3 id="nginx-conf（自定义-Nginx-配置）"><a href="#nginx-conf（自定义-Nginx-配置）" class="headerlink" title="nginx.conf（自定义 Nginx 配置）"></a>nginx.conf（自定义 Nginx 配置）</h3><p>Nginx 配置文件如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span>       <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span>  localhost;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">        <span class="attribute">root</span>   /usr/share/nginx/html;</span><br><span class="line">        <span class="attribute">index</span>  index.html index.htm;</span><br><span class="line">        <span class="attribute">try_files</span> <span class="variable">$uri</span> <span class="variable">$uri</span>/ /index.html;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 禁止缓存 index.html，确保始终获取最新版本（避免缓存导致更新不生效）</span></span><br><span class="line">    <span class="section">location</span> = /index.html &#123;</span><br><span class="line">        <span class="attribute">root</span>   /usr/share/nginx/html;</span><br><span class="line">        <span class="attribute">add_header</span> Cache-Control <span class="string">&quot;no-store, no-cache, must-revalidate&quot;</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="section">location</span> <span class="regexp">~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$</span> &#123;</span><br><span class="line">        <span class="attribute">root</span>   /usr/share/nginx/html;</span><br><span class="line">        <span class="attribute">expires</span> <span class="number">1d</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">error_page</span>   <span class="number">500</span> <span class="number">502</span> <span class="number">503</span> <span class="number">504</span>  /50x.html;</span><br><span class="line">    <span class="section">location</span> = /50x.html &#123;</span><br><span class="line">        <span class="attribute">root</span>   /usr/share/nginx/html;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="3-·-构建与部署"><a href="#3-·-构建与部署" class="headerlink" title="3 · 构建与部署"></a>3 · 构建与部署</h3><h4 id="3-1-本地构建测试"><a href="#3-1-本地构建测试" class="headerlink" title="3.1 本地构建测试"></a>3.1 本地构建测试</h4><p>执行以下命令构建镜像并启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build -t my-frontend .</span><br><span class="line">docker run -p 8080:80 my-frontend</span><br></pre></td></tr></table></figure><p>打开浏览器访问 <code>http://localhost:8080</code>，确认页面正常显示。如果用的是懒猫微服，可通过它的 web 浏览器或 ssh 转发方式访问容器。</p><h4 id="3-2-可选使用-docker-compose"><a href="#3-2-可选使用-docker-compose" class="headerlink" title="3.2 可选使用 docker-compose"></a>3.2 可选使用 docker-compose</h4><p>如果本地调试可以使用</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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">cors-tester:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">Dockerfile</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><h4 id="之前打包踩的坑"><a href="#之前打包踩的坑" class="headerlink" title="之前打包踩的坑"></a>之前打包踩的坑</h4><p>打包多了之后 Docker 会积累不少旧镜像、缓存和挂载卷，下面这些命令能够清理磁盘空间：</p><blockquote><p>使用 pg-docker 或者 lzc-docker 来替代</p></blockquote><h4 id="强制无缓存构建镜像"><a href="#强制无缓存构建镜像" class="headerlink" title="强制无缓存构建镜像"></a>强制无缓存构建镜像</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose build --no-cache</span><br></pre></td></tr></table></figure><blockquote><p>不使用任何缓存，适合依赖变动或调试构建问题时使用。</p></blockquote><h4 id="📦-查看磁盘空间占用详情"><a href="#📦-查看磁盘空间占用详情" class="headerlink" title="📦 查看磁盘空间占用详情"></a>📦 查看磁盘空间占用详情</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker system <span class="built_in">df</span> -v</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250603161544308.png"></p><blockquote><p>查看镜像、容器、网络和卷的占用情况，排查“空间去哪了”。</p></blockquote><h4 id="🧹-强制清理所有未使用资源（含挂载卷）"><a href="#🧹-强制清理所有未使用资源（含挂载卷）" class="headerlink" title="🧹 强制清理所有未使用资源（含挂载卷）"></a>🧹 强制清理所有未使用资源（含挂载卷）</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker system prune -af --volumes</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250603161544308.png" alt="image-20250603161544308"></p><blockquote><p>删除所有未使用的镜像、容器、网络和卷，释放最大磁盘空间。<br><strong>注意：慎用，可能会清掉你没保存的 volume 数据。</strong></p></blockquote>]]></content>
    
    
    <summary type="html">以纸砚双拼为例，演示如何快速将纯前端项目移植到懒猫微服。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十三）：懒猫穿透不只图形化，纯命令行服务器也可以</title>
    <link href="https://blog.no-claw.com/posts/3d7a2d17/"/>
    <id>https://blog.no-claw.com/posts/3d7a2d17/</id>
    <published>2025-06-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>需要解析 <code>heiyu.space</code> 这个域名就得安装客户端，突然发现懒猫微服的客户端都是图形化界面。对于服务器环境，特别是没有图形界面的服务器，我们需要纯命令行解决方案。</p><p>随着公网 IPv4 地址即将枯竭，许多云厂商的学生机也不再提供公网 IP，这迫使开发者寻找异地组网方案。虽然 Tailscale 是一个可选方案，但作为懒猫微服用户，我更希望利用懒猫自带的组网功能实现这一需求。</p><p>在 VIP 群咨询后，获得了服务端组网工具：<br><a href="https://gitee.com/lazycatcloud/hclient-cli">https://gitee.com/lazycatcloud/hclient-cli</a></p><p>和花生壳的 CLI 类似，但是比花生壳省心多了。（这里不再过多吐槽花生壳系列了）</p><h3 id="安装与配置"><a href="#安装与配置" class="headerlink" title="安装与配置"></a>安装与配置</h3><h4 id="1-下载与初始运行"><a href="#1-下载与初始运行" class="headerlink" title="1. 下载与初始运行"></a>1. 下载与初始运行</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x ./hclient-cli-<span class="variable">$arch</span> <span class="comment"># 首次启动需要添加可执行权限</span></span><br><span class="line">./hclient-cli-<span class="variable">$arch</span></span><br></pre></td></tr></table></figure><span id="more"></span><p>初始运行会提示：<br><strong>当前为非 tun 模式,仅支持通过 http 代理访问微服或其他设备资源</strong></p><p>然后就会提示<strong>当前为非 tun 模式,仅支持通过 http 代理访问微服或其他设备资源</strong>，也就是说现在是单项的组网，这肯定不是我的要求，然后 GPT O3 给了我一个答案。</p><h4 id="2-启用-TUN-模式"><a href="#2-启用-TUN-模式" class="headerlink" title="2. 启用 TUN 模式"></a>2. 启用 TUN 模式</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ./hclient-cli-<span class="variable">$arch</span> -tun <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>再启动之后，就没有那个 TUN 模式的提示了。如下</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250602191812289.png" alt="hclient-cli启动界面"></p><h4 id="3-常用命令"><a href="#3-常用命令" class="headerlink" title="3. 常用命令"></a>3. 常用命令</h4><p>然后需要使用命令添加，bname 是机器的名字，uid 和 password 是用户名和密码，这样就保证了全球唯一性质，执行完第一步的时候已有的客户端会弹出验证码，执行完第二步就会消失。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/1187b6cb50dd4eab5c211f927f5bbbd6.png" alt="1187b6cb50dd4eab5c211f927f5bbbd6"></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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加微服</span></span><br><span class="line">curl -X POST <span class="string">&#x27;http://127.0.0.1:7777/add_box?bname=%s&amp;uid=%s&amp;password=%s&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置TFA Code（两步验证码）</span></span><br><span class="line">curl -X POST <span class="string">&#x27;http://127.0.0.1:7777/add_tfa?bname=%s&amp;tfa=%s&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 列举微服</span></span><br><span class="line">curl <span class="string">&#x27;http://127.0.0.1:7777/box_list&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除微服</span></span><br><span class="line">curl -X DELETE <span class="string">&#x27;http://127.0.0.1:7777/del_box?bname=%s&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看当前客户端信息</span></span><br><span class="line">curl <span class="string">&#x27;http://127.0.0.1:7777/client_info&#x27;</span></span><br></pre></td></tr></table></figure><p>在<strong>懒猫微服设备监控</strong>中可以看到加入的设备。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250602191830359.png" alt="设备监控界面"></p><h3 id="2-访问验证"><a href="#2-访问验证" class="headerlink" title="2. 访问验证"></a>2. 访问验证</h3><p>一开始去访问我写的面食比例计算机，发现了重定向了，才想到懒猫默认给所有的应用加了一个认证。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><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="tag">&lt;<span class="name">a</span></span></span><br><span class="line"><span class="tag">  <span class="attr">href</span>=<span class="string">&quot;https://micro.heiyu.space/sys/login?redirect=https%3A%2F%2Fflour-calc.name.heiyu.space%2F&quot;</span></span></span><br><span class="line"><span class="tag">  &gt;</span>Temporary Redirect&lt;/a</span><br><span class="line">&gt;.</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250602202752227.png" alt="认证界面"></p><h3 id="3-双向访问测试"><a href="#3-双向访问测试" class="headerlink" title="3. 双向访问测试"></a>3. 双向访问测试</h3><p>商店里的 elasticsearch 放行了所有路由，这样我们在终端和 SDK 就不再需要走那个 web 的验证了，不然只能在请求头里面硬塞 cookie，但是如果是多层认证就非常的难搞。从图片中可以看到，我们从云服务器可以成功访问到家里的懒猫微服了。这个代表从云服务回到家里时没有问题的。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/ab21f6aafea87f704df434047a587c37.png" alt="云服务器访问家庭网络"></p><h3 id="4-SSH-访问测试"><a href="#4-SSH-访问测试" class="headerlink" title="4. SSH 访问测试"></a>4. SSH 访问测试</h3><p>其实更多的时候我们的异地组网是为了能够在没有公网 IP 的情况下访问节点，查一下监控设备中的域名，然后 ssh 访问进去，发现基本没什么问题。所以就能够双向访问了，这样我们组网的目的就达到了。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/e5316a84db1f258801b864c5bd18eef2.png" alt="SSH连接成功"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过懒猫微服的 CLI 工具，我们成功实现了：</p><ol><li>无图形界面服务器的穿透接入</li><li>双向网络访问</li></ol><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/06da8512-7447-496c-a5f1-669059d134cf.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服穿透服务不限于图形界面，纯命令行服务器也能用。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="组网" scheme="https://blog.no-claw.com/tags/%E7%BB%84%E7%BD%91/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十二）：用 iVentoy 打造你的 PXE 服务器</title>
    <link href="https://blog.no-claw.com/posts/9d1544e0/"/>
    <id>https://blog.no-claw.com/posts/9d1544e0/</id>
    <published>2025-06-03T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多介绍 Linux 的书籍都会在结尾推荐 PXE 装机方式，尤其是经典的 TFTP + Kickstart 自动部署方案。但在普通家庭或轻量办公环境中，这种方式显得有些繁琐。在 U 盘装机的环境中，我一般使用的 <strong>Ventoy</strong>多合一 。不过 Ventoy 团队又推出了一个支持 PXE 的装机版本，名为 <strong>iVentoy</strong>，和 WDS 类似，不过更加省心，这个应用已经上架懒猫微服的应用商店，非常适合内网多机装系统的场景。终于不用再琢磨 WDS 了。还有就是之前不小心用店家带的 U 盘把主机的所有数据都格式化了，这种手残也一去不复返了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250601202019460.png" alt="iVentoy商店界面"></p><h3 id="什么是-PXE-无盘装机？"><a href="#什么是-PXE-无盘装机？" class="headerlink" title="什么是 PXE 无盘装机？"></a>什么是 PXE 无盘装机？</h3><p>PXE，全称 <strong>Preboot eXecution Environment</strong>，是一种允许电脑在没有本地操作系统、光盘或 U 盘的情况下，通过网络从服务器下载引导程序并完成系统安装的机制。</p><span id="more"></span><p><strong>PXE 装机简化流程如下：</strong></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">1. 开机 → BIOS/UEFI 设置为从网卡启动（PXE Boot）</span><br><span class="line">       ↓</span><br><span class="line">2. 客户端通过网卡发出 DHCP 请求 → 获取 IP 和引导文件地址</span><br><span class="line">       ↓</span><br><span class="line">3. 通过 TFTP 下载启动文件（如 pxelinux.0 或 iPXE）</span><br><span class="line">       ↓</span><br><span class="line">4. 加载内核（vmlinuz）和安装器（initrd.img）</span><br><span class="line">       ↓</span><br><span class="line">5. 进入图形界面或执行自动化系统安装</span><br></pre></td></tr></table></figure><hr><h4 id="PXE-装机适用场景"><a href="#PXE-装机适用场景" class="headerlink" title="PXE 装机适用场景"></a>PXE 装机适用场景</h4><ul><li>大量办公电脑或服务器的系统部署</li><li>设备没有 USB 接口或启动盘时</li><li>多系统测试、系统重装、快速恢复环境</li><li>各类 PE 工具、Linux Live 系统启动</li></ul><hr><h3 id="懒猫微服-上-iVentoy-装机体验"><a href="#懒猫微服-上-iVentoy-装机体验" class="headerlink" title="懒猫微服 上 iVentoy 装机体验"></a>懒猫微服 上 iVentoy 装机体验</h3><p>以下是我在懒猫微服环境中使用 iVentoy 的全过程：</p><h4 id="1-上传系统镜像"><a href="#1-上传系统镜像" class="headerlink" title="1. 上传系统镜像"></a>1. 上传系统镜像</h4><p>进入懒猫的应用数据目录：<br><code>应用数据 - Iventoy - ISO</code><br>将你准备好的 Windows &#x2F; Linux ISO 文件上传到此路径。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250601203151865.png" alt="上传 ISO 镜像"></p><h4 id="2-启动-iVentoy，配置网络"><a href="#2-启动-iVentoy，配置网络" class="headerlink" title="2. 启动 iVentoy，配置网络"></a>2. 启动 iVentoy，配置网络</h4><p>确保机器在内网下有一个 <strong>有效的 IPv4 地址</strong>，然后启动 iVentoy。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250601202427884.png" alt="iVentoy 主界面"></p><h4 id="3-BIOS-设置启用-PXE"><a href="#3-BIOS-设置启用-PXE" class="headerlink" title="3. BIOS 设置启用 PXE"></a>3. BIOS 设置启用 PXE</h4><p>进入待装机设备的 BIOS，确保启用 <strong>PXE Boot &#x2F; 网络启动功能</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250601202741281.png" alt="开启 PXE 支持"></p><h4 id="4-插网线，选择-PXE-启动"><a href="#4-插网线，选择-PXE-启动" class="headerlink" title="4. 插网线，选择 PXE 启动"></a>4. 插网线，选择 PXE 启动</h4><p>启动设备，确保插入网线（无线网卡 PXE 启动通常不被支持），从网卡启动。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250601202303737.png" alt="从网络启动"></p><h4 id="5-自动进入-iVentoy-引导菜单"><a href="#5-自动进入-iVentoy-引导菜单" class="headerlink" title="5. 自动进入 iVentoy 引导菜单"></a>5. 自动进入 iVentoy 引导菜单</h4><p>如果网络配置无误，设备会自动弹出引导菜单，可以看到之前上传的 ISO 镜像。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/78714275c7adc030420ccf77b479955b.jpg" alt="iVentoy 引导界面"></p><p>选择一个系统镜像，回车进入即可，相当于将 ISO 当成 LiveCD 使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4488415be27c9e56412883cc35e95ce7.jpg" alt="选择 ISO 启动"></p><h3 id="6-成功进入桌面系统"><a href="#6-成功进入桌面系统" class="headerlink" title="6. 成功进入桌面系统"></a>6. 成功进入桌面系统</h3><p>以 Pop!_OS 为例，系统已经顺利启动，无需任何 U 盘！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8a1dc12c14c27fdc02a39db103994f5a.jpg" alt="成功进入系统桌面"></p><hr><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>这个办法还是适合炫技，把电脑接上网线，然后不用 U 盘，凭空装好系统，不过还是没有 U 盘那么丝滑，有时候不能打满千兆带宽。不过假如手头 U 盘不够用，或者要在多个系统反复装机测试就很方便了。</p>]]></content>
    
    
    <summary type="html">在懒猫微服上用 iVentoy 搭建 PXE 网络启动服务器，局域网装机更方便。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="电脑外设" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%84%91%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（五）：使用懒猫微服做Ventoy启动盘</title>
    <link href="https://blog.no-claw.com/posts/1c49be8c/"/>
    <id>https://blog.no-claw.com/posts/1c49be8c/</id>
    <published>2025-06-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>由于经常给别人装系统，所以经常做了一个多合一的系统盘。这今年基本都没用过 windows 了，都是 MacOS + Linux 的组合。但是 Ventoy 本身不支持 MacOS，以前是用 Windows 虚拟机来做这个事情，把 USB 直通进去再烧录。但是最近实在不想碰 Windows，所以想着是不是能在懒猫微服上做这个操作。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250531145017064.png" alt="image-20250531145017064"></p><h2 id="下载并解压-Ventoy"><a href="#下载并解压-Ventoy" class="headerlink" title="下载并解压 Ventoy"></a>下载并解压 Ventoy</h2><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">wget https://github.com/ventoy/Ventoy/releases/download/v1.1.05/ventoy-1.1.05-linux.tar.gz</span><br><span class="line">tar zxvf ventoy-1.1.05-linux.tar.gz</span><br><span class="line"><span class="built_in">cd</span> ventoy-1.1.05/</span><br><span class="line">./VentoyWeb.sh</span><br></pre></td></tr></table></figure><span id="more"></span><p>这个默认是启动在 localhost，如果需要外网访问改成 0.0.0.0，运行 <code>./VentoyWeb.sh</code> 后，你会看到如下提示：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">./VentoyWeb.sh</span><br><span class="line"></span><br><span class="line">===============================================================</span><br><span class="line">  Ventoy Server 1.1.05 is running ...</span><br><span class="line">  Please open your browser and visit http://127.0.0.1:24680</span><br><span class="line">===============================================================</span><br><span class="line"></span><br><span class="line"><span class="comment">################## Press Ctrl + C to exit #####################</span></span><br></pre></td></tr></table></figure><p>翻了翻脚本，是在这里改。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250531150726731.png" alt="image-20250531150726731"></p><p>因为我本地通过 dig 解析懒猫微服的域名是 IPV6，所以没办法直接访问,于是我通过 SSH 创建一个本地端口转发（Local Port Forwarding）隧道：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -N -L 24680:127.0.0.1:24680 root@lzc</span><br></pre></td></tr></table></figure><ul><li><code>ssh</code>: 启动 SSH 客户端。</li><li><code>-N</code>: 不执行远程命令，仅用于端口转发（即登录后不打开 shell）。</li><li><code>-L 24680:127.0.0.1:24680</code>: 本地端口转发规则，格式为 <code>本地端口:远程地址:远程端口</code>。解释如下：<ul><li><code>24680</code>: 本地监听的端口（你访问 <code>localhost:24680</code> 时会触发转发）。</li><li><code>127.0.0.1</code>: 这是 <strong>SSH 远程主机上</strong>的地址，指代远程主机自己。</li><li><code>24680</code>: 最终目标端口。</li></ul></li><li><code>root@lzc</code>: 使用 <code>root</code> 用户连接名为 <code>lzc</code> 的主机（可以是域名或 <code>/etc/hosts</code> 里配置的别名）。</li></ul><p>当本地访问 <code>localhost:24680</code>，会通过 SSH 加密通道转发到远程主机 <code>lzc</code> 上的 <code>127.0.0.1:24680</code>，就像直接在远程主机上访问一样。</p><p>这个就是 Ventoy 的页面了，由于默认懒猫没有 GUI，所以没办法使用类似 windows 那种客户端，还好 Venoty 提供了一个 web 端可以用来玩。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/de6b318fce8e800410c32b740cbf5a19-20250531145042728.png" alt="de6b318fce8e800410c32b740cbf5a19"></p><p>点击右侧的绿色，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/03f3620e64741a0ebc6e1195be1f7b9c.png" alt="03f3620e64741a0ebc6e1195be1f7b9c"></p><p>接着 Ventoy 会再确认一次是否格式化设备，<strong>务必确认盘符无误</strong>，这一步会清空整盘数据，后期可以无损升级。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/6341ff654a0236599214ce63240c9c17.png" alt="6341ff654a0236599214ce63240c9c17"></p><p>写盘成功的提示，后期只需要把 ISO 直接拖拽进来。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250531151451736.png" alt="image-20250531151451736"></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>懒猫微服 + Ventoy Web 是制作多合一启动盘的 “无桌面” 优雅方案。</li><li>通过 SSH 端口转发，可在不暴露服务的情况下安全地使用 Web 界面。</li><li>写盘后只需拖拽 ISO，后续增删镜像都无需重新格式化。</li></ul>]]></content>
    
    
    <summary type="html">用懒猫微服制作 Ventoy 启动盘，局域网装机更方便。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="装机" scheme="https://blog.no-claw.com/tags/%E8%A3%85%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十一）：一键换源指南：用 chsrc 开启丝滑体验</title>
    <link href="https://blog.no-claw.com/posts/eef881d4/"/>
    <id>https://blog.no-claw.com/posts/eef881d4/</id>
    <published>2025-06-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近懒猫微服的系统固件进行了更新，默认的软件源重新指向了国际源。估计是为了方便海外用户，但对于国内开发者来说，访问速度顿时大打折扣，软件安装和更新频繁卡在连接阶段，着实是一个问题。</p><p>正巧群友推荐了一个神器 —— <a href="https://gitee.com/RubyMetric/chsrc"><code>chsrc</code></a>。这个工具支持一键切换系统、Python、conda 等多个主流组件的源，简洁高效，非常适合懒猫微服这样的轻量环境使用。</p><h2 id="下载并安装-chsrc"><a href="#下载并安装-chsrc" class="headerlink" title="下载并安装 chsrc"></a>下载并安装 chsrc</h2><p>chsrc 项目在 Gitee 上提供了预编译的可执行文件，适用于不同架构的 Linux 设备。我当前使用的是 x86_64 架构，因此只需运行以下命令：</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">curl -L https://gitee.com/RubyMetric/chsrc/releases/download/pre/chsrc-x64-linux -o chsrc</span><br><span class="line"><span class="built_in">chmod</span> +x ./chsrc</span><br></pre></td></tr></table></figure><p>下载后，为了方便使用，我们通常会把它加入环境变量。但懒猫微服的一个特殊机制是：<strong>除了 <code>/root</code> 目录，其他目录在每次重启后都会被重置</strong>。这意味着如果你将 chsrc 放在 <code>/home</code> 或 <code>/usr/local/bin</code> 之类的目录，它在下次重启后可能就不见了。</p><span id="more"></span><p>所以我将 chsrc 的二进制文件移动到了 <code>/root/app</code> 下（可以新建这个目录），并在 <code>~/.bashrc</code> 中手动追加了环境变量：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=/root/app:<span class="variable">$PATH</span></span><br></pre></td></tr></table></figure><p>这样每次打开 shell 时，系统就会自动把 chsrc 所在路径加入 <code>$PATH</code>，确保我们可以直接使用 <code>chsrc</code> 命令。</p><h2 id="一键更换-Debian-软件源"><a href="#一键更换-Debian-软件源" class="headerlink" title="一键更换 Debian 软件源"></a>一键更换 Debian 软件源</h2><p>懒猫微服是基于 Debian 的发行版，因此可以直接运行：</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">chsrc <span class="built_in">set</span> debian</span><br></pre></td></tr></table></figure><p>运行后，工具会先检测系统当前源格式（支持新版 <code>deb822</code>），然后测速多个国内镜像源（如清华、中科大、阿里等），自动选择最快的源进行替换，整个过程完全自动化，再也不用去网上搜帖子找各种源了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530201828165.png" alt="换源截图"></p><h2 id="更换-Python-镜像源"><a href="#更换-Python-镜像源" class="headerlink" title="更换 Python 镜像源"></a>更换 Python 镜像源</h2><p>除了操作系统本身的源之外，也能更换 Python，node 这样的源。</p><p>我制作了一个 Miniconda 的一键安装脚本，用来替代系统自带的 Python3.11，用 conda 虚拟环境管理起来很方便。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 下载最新 Miniconda 安装脚本</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;📥 正在下载 Miniconda 安装器...&quot;</span></span><br><span class="line">MINICONDA=Miniconda3-latest-Linux-x86_64.sh</span><br><span class="line">wget https://repo.anaconda.com/miniconda/<span class="variable">$MINICONDA</span> -O /tmp/<span class="variable">$MINICONDA</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 静默安装到 ~/miniconda3</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;⚙️ 正在静默安装到 ~/miniconda3...&quot;</span></span><br><span class="line">bash /tmp/<span class="variable">$MINICONDA</span> -b -p <span class="variable">$HOME</span>/miniconda3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 添加到 PATH（写入 .bashrc）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;🔧 正在将 Miniconda 添加到 PATH...&quot;</span></span><br><span class="line">CONDA_INIT=<span class="string">&#x27;export PATH=&quot;$HOME/miniconda3/bin:$PATH&quot;&#x27;</span></span><br><span class="line"><span class="keyword">if</span> ! grep -q <span class="string">&quot;<span class="variable">$CONDA_INIT</span>&quot;</span> ~/.bashrc; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$CONDA_INIT</span>&quot;</span> &gt;&gt; ~/.bashrc</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 初始化 conda（非交互式）</span></span><br><span class="line"><span class="variable">$HOME</span>/miniconda3/bin/conda init bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 提示用户刷新 shell</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;✅ 安装完成！请运行以下命令以立即生效：&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;  source ~/.bashrc&quot;</span></span><br></pre></td></tr></table></figure><p>这个脚本的好处是全程自动化，不需要你动手点选目录，也不必一步步设置环境变量，适合懒猫开发者在新设备或系统还原后快速恢复工作环境。</p><p>安装好 Miniconda 之后，就可以利用 <code>chsrc</code> 来更换 pip 源了：</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">chsrc <span class="built_in">set</span> python</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/449f7b0d2e26f4247caf0ce47b4c1622.png" alt="chsrc 设置 Python 源"></p><p>此命令会将 pip 的默认源改为清华源或豆瓣源等国内镜像，提高模块下载速度，彻底告别“卡在安装 xx 模块”的窘境。</p><p>最后一步，把 conda 的源也换成国内的。直接执行：</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">chsrc <span class="built_in">set</span> conda</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530204151317.png" alt="换源成功"></p><p>然后需要手动更新 <code>.condarc</code> 配置文件。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这次我体验了 <code>chsrc</code> 在懒猫微服上的完整使用链路，感受可以总结为三句话：</p><ul><li><strong>能跑就行？不，要跑得快。</strong></li><li><strong>别用默认源，懒人换源有奇效。</strong></li><li><strong>系统、Python、Conda，一条龙解决卡顿。</strong></li></ul><p>如果你也在用懒猫微服、或者在其他 Debian 系的轻量服务器上摸索开发环境，强烈推荐你试试这个工具和脚本组合。毕竟，“懒得配置”不应该成为“卡在配置”的理由。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/e5fc8b99-2220-4a5a-8327-38da7b93456f.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服固件更新后软件源变慢，用 chsrc 一键切换国内镜像源。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>手把手搞定报名亚马逊科技认证</title>
    <link href="https://blog.no-claw.com/posts/4533e9f8/"/>
    <id>https://blog.no-claw.com/posts/4533e9f8/</id>
    <published>2025-06-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>亚马逊云科技认证考试为我们这些技术从业者提供了提升专业技能的机会。无论选择线上还是线下考试，每种方式都有其独特的优势和挑战。选择合适的考试方式将帮助我们更好地展示自己的技术水平。以下是我对不同考试方式的优缺点介绍，以及各科目的考试代码。希望这些信息能帮助大家在备考过程中做出最优选择。</p><span id="more"></span><h2 id="考试方式介绍"><a href="#考试方式介绍" class="headerlink" title="考试方式介绍"></a>考试方式介绍</h2><h3 id="线上考试"><a href="#线上考试" class="headerlink" title="线上考试"></a>线上考试</h3><p><strong>优点：</strong></p><ul><li><strong>方便快捷</strong>：无需通勤，考生可以根据自己的时间安排进行考试。</li><li><strong>高灵活性</strong>：考试预约灵活，基本上每天都可以进行。</li></ul><p><strong>缺点：</strong></p><ul><li><strong>环境要求较高</strong>：考试时屋内只能有考生一人，且不能有其他声音，否则监考官会关闭考试界面。</li><li><strong>网络要求高</strong>：需要连接海外的直播平台发送摄像头内容，如果监考官无法看到考生，考试也会被关闭。</li></ul><h3 id="线下考试"><a href="#线下考试" class="headerlink" title="线下考试"></a>线下考试</h3><p><strong>优点：</strong></p><ul><li><strong>环境稳定</strong>：无需担心家中环境问题，且由线下监考员监考。</li><li><strong>简便的身份验证</strong>：只需携带身份证和社保卡即可。</li></ul><p><strong>缺点：</strong></p><ul><li><strong>考点少且难预约</strong>：例如，北京的考点不多且预约困难，多数考点位于郊区，如大兴和昌平。</li><li><strong>时间限制</strong>：考点通常只在工作日开放，考生可能需要请假参加考试。</li></ul><h3 id="各科目考试代码"><a href="#各科目考试代码" class="headerlink" title="各科目考试代码"></a>各科目考试代码</h3><p>帮助大家更好地准备和选择考试内容。我总结了亚马逊云科技现在的认证：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/cc20f808fb0689bb100c04507d0ee5c1.png" alt="在这里插入图片描述"></p><h2 id="报名步骤"><a href="#报名步骤" class="headerlink" title="报名步骤"></a>报名步骤</h2><p>首先，您需要访问 <a href="https://aws.amazon.com/certification%E6%B3%A8%E5%86%8C%E4%B8%80%E4%B8%AA%E8%B4%A6%E6%88%B7%E3%80%82%E6%88%91%E5%BB%BA%E8%AE%AE%E4%BD%BF%E7%94%A8%E7%A7%81%E4%BA%BA%E9%82%AE%E7%AE%B1%E6%B3%A8%E5%86%8C%EF%BC%8C%E8%80%8C%E9%9D%9E%E5%85%AC%E5%8F%B8%E9%82%AE%E7%AE%B1%EF%BC%8C%E4%BB%A5%E7%A1%AE%E4%BF%9D%E8%B4%A6%E6%88%B7%E7%9A%84%E9%95%BF%E6%9C%9F%E5%8F%AF%E7%94%A8%E6%80%A7%E3%80%82">https://aws.amazon.com/certification注册一个账户。我建议使用私人邮箱注册，而非公司邮箱，以确保账户的长期可用性。</a></p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/b656c9ca96b06a91641d0933b7288f7f.png" alt="注册界面"></p><p>点击使用 Pearson Vue 进行安排：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/6e64c34879d1217d62ca41edc9ecb2c4.png" alt="在这里插入图片描述"></p><p>在这里可以选择线上、线下和私人访问码，一般选择前两种，第三种通常是与机构合作的链接。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/0cc9bda530f37ded10b7aabe62084a9e.png" alt="选择考试方式"></p><p>线下考试需要带好身份证，国内还需要带辅助证件，如社保卡或驾照。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/7e2b5e346cdf9163e0327c9b2aa7ba03.png" alt="线下考试准备"></p><p>线上考试需要保证良好的网络环境和独立的空间，同时准备好身份证。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/7531f6f4a0b687da297285382a06cfe2.png" alt="线上考试准备"></p><p>接下来是选择考场，这里可以同时选择三个考场，然后下一步查看考场的时间：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/d771ce1cc8f19a55caf4e62a8b6999e0.png" alt="选择考场"></p><p>可以看到最近的日期是 15 号，那么我们可以尝试预约 15 号。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/0226e6c143950fb2f5dd825e9515e9e1.png" alt="预约考试"></p><p>选好时间之后，下一步准备付款。如果有优惠码，可以在下一步输入。这里可以看到考试时间汇总。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/bdd3720da2cc95978bcca461bd058f80.png" alt="考试时间汇总"></p><p>付款完成后，可以下载 ics 文件并添加到日历中，这样的话 Apple 日历就会提前提醒我：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/a1694d9092df8b7a398f54c2542dfb02.png" alt="添加到日历"></p><h2 id="优惠和加时"><a href="#优惠和加时" class="headerlink" title="优惠和加时"></a>优惠和加时</h2><p>对于非英语母语的考生，可以申请 30 分钟的加时。虽然官网也发布了中文考生可以加时 30 分钟的消息，但尚未说明具体的加时条件。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><strong>证明身份</strong>：线上考试需要身份证或驾照，监考官会帮助新建 case。线下考试需要身份证和社保卡或驾照。</li><li><strong>平台</strong>：使用 OnVue 平台监考。OnVue 中国区负责报名选考场，如果因网络问题无法考试需要重新预约，技术问题需要联系英文支持。</li><li><strong>线上注意事项</strong>：检测 WEBRTC 连接和摄像头，考试时全程保持摄像头开启。画面静止或超出摄像头范围，监考官会让退出重进。</li><li><strong>Check-in 流程</strong>：不能有手表，检查桌面没有其他东西，监考官会询问手机放置位置。</li><li><strong>有效期</strong>：认证有效期为三年，通过 SAP 考试可自动续 SAA，DOP 可续 SOA 和 DVA，任意助理级考试可续 CLF。</li></ol><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>线上检录界面，需要做一些准备工作，上传证件，清理桌面之类的工作：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/210964768f277785d3431bdbdb6c5b5d.png" alt="线上检录界面"></p><p>这个是线上和考官的聊天界面：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/f52f4644ac8e971bff83db797ccf848c.png" alt="聊天界面"></p><p>如果需要申诉，会邮件告知新的抵扣码（原来的考试码作废）：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/56b4b91c982f81564ecaa43759d88014.png" alt="新抵扣码"></p><p>希望这些信息能帮助大家在备考过程中做出最优选择，顺利通过亚马逊云科技认证考试。</p>]]></content>
    
    
    <summary type="html">手把手教你报名 AWS 亚马逊云科技认证考试的完整流程</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>记一次Amazon Q pro的使用</title>
    <link href="https://blog.no-claw.com/posts/56bb1eb7/"/>
    <id>https://blog.no-claw.com/posts/56bb1eb7/</id>
    <published>2025-06-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>参加 AWS 的比赛申请了 Amazon Q pro，平时也在用 builderID 登陆使用免费的账户。Amazon Q pro 需要和 IAM identity center 一起用，不过比赛直接给配置好了，直接分发 IAM identity center 的账户，我们只需要注册，登陆，然后关联 Q pro。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613214705836.png" alt="image-20250613214705836"></p><p>从邮箱里给的链接注册，登录。然后绑定 MFA，这个 MFA 其实就是一个二次验证，如果账户被盗，对方没有 MFA 也是无法登录的。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613215145521.png" alt="image-20250613215145521"></p><p>我用了 2Fauth 来绑定的，当然你也可以使用 google authenticator 之类的软件，绑定六位动态码。当然比较常见的 MFA 就是短信验证码，当然还有打电话的。这边刚刚登录，这边电话马上过来。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/221ef1ff7faa7d41d0b2032bfe981928.png" alt="221ef1ff7faa7d41d0b2032bfe981928"></p><p>注册成功会有这个提示。后面需要使用这个绑定的 MFA 进行登录。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613214403296.png" alt="image-20250613214403296"></p><p>登录之后会跳转到这个门户页面，点击 Q 的图标之后会跳转到 Amazon Q 的官方文档。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/03f17da2afbfeaf936933ac5b9014db0.jpg" alt="03f17da2afbfeaf936933ac5b9014db0"></p><p>完成了登录，我们来做本地的配置：</p><p>在 VS code 商店中搜索 Amazon Q 并且安装：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613220402714.png" alt="image-20250613220402714"></p><p>Amazon Q 有免费版和 Pro 版。免费版使用 build ID 进行登录，无需 AWS 账户。而今天体验的是 Q pro 版本，还好主办方给配置好了 AWS 的账户，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613220656884.png" alt="image-20250613220656884"></p><p>安装之后右下角就有一个提示登录的弹窗，点击 Sign in，URL 输入邮箱里给的 URL。其实就是 IAM identity center 的登录链接。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/75a9cf790abee0500c03a4fef5906e0b.png" alt="75a9cf790abee0500c03a4fef5906e0b"></p><p>然后是跳转浏览器：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/08d51abf78a42aac97e2f6d3901dbc4f.png" alt="08d51abf78a42aac97e2f6d3901dbc4f"></p><p>以及提示打开浏览器的弹窗：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613221104615.png" alt="image-20250613221104615"></p><p>浏览器打开之后有获取权限的提示：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a77a557cab162ca6d22c644330b1debc.png" alt="a77a557cab162ca6d22c644330b1debc"></p><p>点击允许之后就大功告成了：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/a5eac153f41f318771e8ed90a258bb44.png" alt="a5eac153f41f318771e8ed90a258bb44"></p><p>同时 VScode 里的 Q 也会变成这样，最后变成聊天窗口</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613221132184.png" alt="image-20250613221132184"></p><p>如图是聊天模式：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250613221851367.png" alt="image-20250613221851367"></p><p>如果你使用 Ubuntu server：</p><ol><li><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">wget https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q.deb</span><br></pre></td></tr></table></figure></li><li><p>安装</p><figure class="highlight plaintext"><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">sudo apt-get install -f</span><br><span class="line">sudo dpkg -i amazon-q.deb</span><br></pre></td></tr></table></figure></li><li><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">q</span><br></pre></td></tr></table></figure></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/4e449305647468b637ebe4600479a868.png" alt="4e449305647468b637ebe4600479a868"></p><p>如果你用的是苹果系统，就更简单了。从这个链接下载，直接安装就可以了。</p><p><a href="https://desktop-release.q.us-east-1.amazonaws.com/latest/Amazon%20Q.dmg">https://desktop-release.q.us-east-1.amazonaws.com/latest/Amazon%20Q.dmg</a></p><p>然后在终端中输入 q 或者 Q chat 就可以了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/714911ccd41755315794150d4e748318.png" alt="714911ccd41755315794150d4e748318"></p>]]></content>
    
    
    <summary type="html">记录 Amazon Q Pro 的申请与实际使用体验</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（十）：通过 nmtui 设置静态 IP，接入旁路由实现科学上网</title>
    <link href="https://blog.no-claw.com/posts/b704f5f8/"/>
    <id>https://blog.no-claw.com/posts/b704f5f8/</id>
    <published>2025-05-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>日常开发中，我主要使用的是 <strong>MacBook</strong> 作为主力机。但有些软件、驱动或容器服务必须在 <strong>X86 架构环境下测试</strong>，这时懒猫微服就是一个很好的辅助平台。然而在一些网络场景下，为了访问特定服务，还需要将这台设备接入旁路由中。</p><p>不然我访问 Dockerhub 是这样的，经常会超时，只能用不是很全的镜像站。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530113416141.png" alt="image-20250530113416141"></p><p>群晖等设备提供的图形化网络面板，懒猫微服这类轻量系统没有 GUI，只能使用命令行工具。NetworkManager 提供了一个非常好用的文本界面工具：<code>nmtui</code>。</p><h2 id="什么是-nmtui"><a href="#什么是-nmtui" class="headerlink" title="什么是 nmtui"></a>什么是 <code>nmtui</code></h2><p><code>nmtui</code> 是 NetworkManager 的 TUI（Text-based User Interface）组件，界面类似简化版 GUI，操作简单，功能却很强大。通过它，我们可以方便地完成以下配置：</p><ul><li>选择并连接网络设备（有线或无线）</li><li>设置 <strong>手动 IP 地址</strong></li><li>指定 <strong>网关</strong>、<strong>DNS 服务器</strong></li><li>管理 <strong>路由策略</strong></li><li>开启或关闭 DHCP 自动获取功能<span id="more"></span>命令如下：</li></ul><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">nmtui</span><br></pre></td></tr></table></figure><hr><h2 id="设置静态-IP-接入旁路由"><a href="#设置静态-IP-接入旁路由" class="headerlink" title="设置静态 IP 接入旁路由"></a>设置静态 IP 接入旁路由</h2><p>打开 <code>nmtui</code> 后，进入“Edit a connection”，编辑你连接的网卡（如 <code>enp2s0</code>），手动设置 IP 地址、网关和 DNS。如下图所示，我将所有网络解析都指向了旁路由（比如是 OpenWrt 或其他带翻墙能力的设备）：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530113654399.png" alt="nmtui IPv4 设置"></p><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">nmcli device reapply enp2s0</span><br></pre></td></tr></table></figure><p>如果配置无误，网络将立即切换到静态 IP 并走旁路由的网关。</p><hr><h2 id="验证效果"><a href="#验证效果" class="headerlink" title="验证效果"></a>验证效果</h2><p>这样子终端就能下载 Docker image 了，其实到这一步已经完成了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530113410246.png" alt="Google 搜索页面"></p><p>接下来，可以通过懒猫微服中安装的浏览器测试网络是否生效。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530113238801.png" alt="YouTube 套娃"></p><p>继续打开 YouTube 首页，也能顺利访问并加载视频页面：</p><p>你没看错，这是 <strong>浏览器里的浏览器</strong>，实现了一个“浏览器套娃”的效果。😂</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530112240064.png" alt="image-20250530112240064"></p><p>最终，我播放了银临的《牵丝戏》，人美歌也好听～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530112250228.png" alt="银临《牵丝戏》"></p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/59ab31d4-adf8-4c8e-9775-1b37710f570a.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服通过 nmtui 设置静态 IP，接入旁路由实现科学上网的完整配置。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="网络" scheme="https://blog.no-claw.com/tags/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="代理" scheme="https://blog.no-claw.com/tags/%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（九）：给懒猫安装图形界面，使用 XRDP 实现远程桌面</title>
    <link href="https://blog.no-claw.com/posts/8e360f7e/"/>
    <id>https://blog.no-claw.com/posts/8e360f7e/</id>
    <published>2025-05-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>虽然我们日常使用 Linux 系统时，大多数操作都是通过终端完成的，比如运行服务、查看日志、编辑配置文件等。但在某些场景下，图形界面仍然是非常有用的：例如需要打开浏览器进行调试、运行带 GUI 的应用程序，或者想给不太熟悉命令行的同事一个更友好的访问方式。</p><p>因此，这一篇教程就来介绍如何在懒猫微服上安装图形桌面环境，并通过 XRDP 实现远程桌面连接。我们选择的是轻量级的 <strong>XFCE4 桌面环境</strong>，它资源占用小，运行稳定，界面风格有点像老版本 Windows，非常适合资源有限的 VPS 或微型容器环境。当然，你也可以选择 KDE Plasma、GNOME 等更现代的桌面环境，但安装包体积和资源占用会更高。</p><hr><h2 id="一键安装脚本"><a href="#一键安装脚本" class="headerlink" title="一键安装脚本"></a>一键安装脚本</h2><p>经过多次测试，我总结了一份实用的一键安装脚本，适用于基于 Debian 的系统（如 Ubuntu 或懒猫微服）：</p><span id="more"></span><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 设置 keyboard-configuration 为无交互模式&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;keyboard-configuration keyboard-configuration/layoutcode select us&#x27;</span> | debconf-set-selections</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;keyboard-configuration keyboard-configuration/modelcode select pc105&#x27;</span> | debconf-set-selections</span><br><span class="line"><span class="built_in">export</span> DEBIAN_FRONTEND=noninteractive</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 更新系统包索引&quot;</span></span><br><span class="line">apt update</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 安装桌面和远程桌面组件&quot;</span></span><br><span class="line">apt install -y xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils \</span><br><span class="line">               xrdp xorgxrdp keyboard-configuration</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 配置 .xsession 启动 XFCE&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;startxfce4&quot;</span> &gt; ~/.xsession</span><br><span class="line"><span class="built_in">chmod</span> +x ~/.xsession</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 添加 xrdp 到 ssl-cert 用户组（避免黑屏）&quot;</span></span><br><span class="line">adduser xrdp ssl-cert</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;==&gt; 启动并启用 xrdp 服务&quot;</span></span><br><span class="line">systemctl <span class="built_in">enable</span> xrdp</span><br><span class="line">systemctl restart xrdp</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;✅ 安装完成！现在可以使用 Windows 远程桌面（mstsc）登录本机 IP，使用系统用户名登录 XFCE 桌面。&quot;</span></span><br></pre></td></tr></table></figure><p>你只需要将这段脚本保存为 <code>install-xrdp-xfce.sh</code> 文件，赋予执行权限并运行即可：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x install-xrdp-xfce.sh</span><br><span class="line">./install-xrdp-xfce.sh</span><br></pre></td></tr></table></figure><p>需要注意的是, 重启之后这个配置仍然会消失.所以还需要使用 systemd –user 拉起来这个脚本.</p><h2 id="远程桌面使用说明"><a href="#远程桌面使用说明" class="headerlink" title="远程桌面使用说明"></a>远程桌面使用说明</h2><ol><li>安装完成后，在 Windows 系统中打开“远程桌面连接（mstsc）”。</li><li>输入你的懒猫主机 IP 地址。</li><li>使用系统中的用户名密码进行登录</li><li>如果一切正常，应该可以看到一个 XFCE 图形桌面。</li></ol><p>如果你连接后出现黑屏问题，通常是 <code>.xsession</code> 配置不正确或者权限不足，上述脚本中已经处理好了这个问题。</p><hr><h2 id="补充：如果你还想让它能本地显示图形界面"><a href="#补充：如果你还想让它能本地显示图形界面" class="headerlink" title="补充：如果你还想让它能本地显示图形界面"></a>补充：如果你还想让它能本地显示图形界面</h2><p>如果你希望在物理机或有显卡输出的虚拟机中直接打开图形界面（不是远程连接），可以额外添加如下配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;exec startxfce4&quot;</span> &gt; ~/.xinitrc</span><br><span class="line"><span class="built_in">chmod</span> +x ~/.xinitrc</span><br><span class="line">startx</span><br></pre></td></tr></table></figure><p>这样你在本地终端执行 <code>startx</code> 就能启动 XFCE 桌面。</p><p>通过上述脚本，我们可以非常快速地为懒猫微服安装一个可用的图形桌面环境，并通过 XRDP 实现远程访问。这在需要图形界面支持的场景下尤其方便，例如：</p><ul><li>使用浏览器调试网页；</li><li>可视化工具如 Wireshark、GParted；</li><li>为不熟悉命令行的用户开放访问权限。</li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250530112353984.png" alt="image-20250530112353984"></p><p>如果你希望安装 KDE、GNOME 等更复杂的桌面，可以将 <code>apt install</code> 中的包名替换为 <code>kde-standard</code>、<code>gnome</code> 等，并注意资源占用问题。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/d44ae9de-a552-4745-9a8e-c645ff4def87.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">给懒猫微服安装图形界面，通过 XRDP 实现远程桌面访问。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="RDP" scheme="https://blog.no-claw.com/tags/RDP/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（四）：每次重启都丢配置？用 systemctl --user 自动装回环境！</title>
    <link href="https://blog.no-claw.com/posts/95e86a3c/"/>
    <id>https://blog.no-claw.com/posts/95e86a3c/</id>
    <published>2025-05-29T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在懒猫微服中，为了防止用户误操作破坏系统，默认启用了“<strong>重启还原机制</strong>”——每次重启都会还原大部分系统改动。不过，<strong>用户主目录的数据是保留的</strong>（例如 <code>/root/</code>），这就给我们留下了一条生路。</p><p>以往每次重启后，我都要手动重新安装 <code>htop</code>、<code>sudo</code>、<code>httpie</code> 等工具，重复操作实在麻烦。之前在 VIP 群里沟通过能否允许使用 <code>systemctl</code> 自启脚本，现在终于支持了 <code>systemctl --user</code> 的开机启动功能，第一时间来体验一下！</p><hr><h2 id="💻-安装脚本-init-sh"><a href="#💻-安装脚本-init-sh" class="headerlink" title="💻 安装脚本 init.sh"></a>💻 安装脚本 init.sh</h2><p>我们把需要安装的软件统一写进一个脚本，只安装未安装的部分，避免重复浪费时间。同时也支持远程安装一些工具，例如 <code>superfile</code>。</p><span id="more"></span><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="built_in">set</span> -e  <span class="comment"># 任意步骤失败立即终止</span></span><br><span class="line"></span><br><span class="line">PACKAGES=(</span><br><span class="line">  <span class="built_in">sudo</span></span><br><span class="line">  htop</span><br><span class="line">  wget</span><br><span class="line">  build-essential</span><br><span class="line">  httpie</span><br><span class="line">  exa</span><br><span class="line">  duf</span><br><span class="line">  bat</span><br><span class="line">  <span class="comment"># 可以继续添加：docker.io nodejs ...</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">need_install=()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> pkg <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;PACKAGES[@]&#125;</span>&quot;</span>; <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">if</span> ! dpkg -s <span class="string">&quot;<span class="variable">$pkg</span>&quot;</span> &amp;&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">    need_install+=(<span class="string">&quot;<span class="variable">$pkg</span>&quot;</span>)</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (( <span class="variable">$&#123;#need_install[@]&#125;</span> )); <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;==&gt; Installing: <span class="variable">$&#123;need_install[*]&#125;</span>&quot;</span></span><br><span class="line">  apt-get update</span><br><span class="line">  DEBIAN_FRONTEND=noninteractive apt-get install -y <span class="string">&quot;<span class="variable">$&#123;need_install[@]&#125;</span>&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;==&gt; All packages already installed.&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 Superfile 工具</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">command</span> -v curl &amp;&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;==&gt; Installing Superfile (from https://superfile.netlify.app)&quot;</span></span><br><span class="line">  bash -c <span class="string">&quot;<span class="subst">$(curl -sLo- https://superfile.netlify.app/install.sh)</span>&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;❌ curl not found, skipping Superfile install&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>你可以把这个脚本保存为 <code>/root/init.sh</code>（懒猫微服会保留这个路径），并赋予执行权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x /root/init.sh</span><br></pre></td></tr></table></figure><hr><h2 id="⚙-systemd-用户服务配置"><a href="#⚙-systemd-用户服务配置" class="headerlink" title="⚙ systemd 用户服务配置"></a>⚙ systemd 用户服务配置</h2><p>由于懒猫微服现在支持 <code>systemctl --user</code>，我们就可以通过用户级 systemd 服务在登录后自动执行该脚本。</p><p>在 <code>~/.config/systemd/user/</code> 目录下创建服务文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p ~/.config/systemd/user</span><br><span class="line">vim ~/.config/systemd/user/bootstrap-packages.service</span><br></pre></td></tr></table></figure><p>内容如下：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Bootstrap Required Packages</span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">Type</span>=<span class="literal">on</span>eshot                  <span class="comment"># 关键修改！表示一次性任务</span></span><br><span class="line"><span class="attr">ExecStart</span>=/root/init.sh</span><br><span class="line"><span class="attr">RemainAfterExit</span>=<span class="literal">yes</span>           <span class="comment"># 任务完成后仍标记为 &quot;active&quot;（可选）</span></span><br><span class="line"><span class="comment"># Restart=no                 # 默认就是 no，可省略</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=default.target</span><br></pre></td></tr></table></figure><p>注意事项：</p><ul><li><code>ExecStart</code> 使用 <code>/root/init.sh</code> 是因为懒猫微服重启不会清空 root 目录；</li><li>这是一个 <strong>oneshot（一次性任务）</strong>，运行完就退出；</li><li><code>default.target</code> 是用户级别的“登录后启动”目标。</li></ul><hr><h2 id="🧪-启用和调试服务"><a href="#🧪-启用和调试服务" class="headerlink" title="🧪 启用和调试服务"></a>🧪 启用和调试服务</h2><p>配置好之后，使用以下命令启动并设置自动运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">systemctl --user daemon-reload                       <span class="comment"># 重新加载用户服务配置</span></span><br><span class="line">systemctl --user start bootstrap-packages.service    <span class="comment"># 手动运行一次（测试用）</span></span><br><span class="line">systemctl --user status bootstrap-packages.service   <span class="comment"># 查看服务状态和日志</span></span><br><span class="line">systemctl --user <span class="built_in">enable</span> bootstrap-packages.service   <span class="comment"># 设置登录后自动运行</span></span><br></pre></td></tr></table></figure><h3 id="示例运行结果："><a href="#示例运行结果：" class="headerlink" title="示例运行结果："></a>示例运行结果：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl --user start bootstrap-packages.service</span><br><span class="line">systemctl --user status bootstrap-packages.service</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">○ bootstrap-packages.service - Bootstrap Required Packages</span><br><span class="line">     Loaded: loaded (/root/.config/systemd/user/bootstrap-packages.service; enabled; preset: enabled)</span><br><span class="line">     Active: inactive (dead) since Thu 2025-05-29 20:13:58 CST; 941ms ago</span><br><span class="line">   Duration: 6.438s</span><br><span class="line">    Process: 142818 ExecStart=/root/init.sh (code=exited, status=0/SUCCESS)</span><br><span class="line">   Main PID: 142818 (code=exited, status=0/SUCCESS)</span><br><span class="line">        CPU: 350ms</span><br><span class="line"></span><br><span class="line">May 29 20:13:53 lzcbox-029c588e init.sh[142926]: Downloading superfile v1.3.1 for linux (amd64)...</span><br><span class="line">May 29 20:13:58 lzcbox-029c588e init.sh[142926]: Installing superfile...</span><br><span class="line">May 29 20:13:58 lzcbox-029c588e init.sh[142926]: 🎉 Installation complete!</span><br><span class="line">May 29 20:13:58 lzcbox-029c588e init.sh[142926]: You can type &quot;spf&quot; to start!</span><br></pre></td></tr></table></figure><p>图示效果如下（安装过程中终端自动拉起）：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529201226160.png" alt="image-20250529201226160"></p><hr><h2 id="🚀-小结"><a href="#🚀-小结" class="headerlink" title="🚀 小结"></a>🚀 小结</h2><table><thead><tr><th>步骤</th><th>命令</th></tr></thead><tbody><tr><td>设置 systemd 服务</td><td><code>vim ~/.config/systemd/user/bootstrap-packages.service</code></td></tr><tr><td>测试运行</td><td><code>systemctl --user start bootstrap-packages.service</code></td></tr><tr><td>设置登录自启</td><td><code>systemctl --user enable bootstrap-packages.service</code></td></tr><tr><td>查看运行状态</td><td><code>systemctl --user status bootstrap-packages.service</code></td></tr></tbody></table><p>搭配懒猫微服的 root 持久策略和 systemd 用户服务功能，我们终于实现了：<strong>重启自动恢复开发环境，不用每次手动装包了！</strong></p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/e8de34ec-0ad7-4b80-b518-36c786746db5.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">用 systemctl --user 解决懒猫微服重启后配置丢失的问题，自动恢复环境。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Linux" scheme="https://blog.no-claw.com/tags/Linux/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（八）：懒猫微服 × SSH Remote：打造随时随地的云端开发环境</title>
    <link href="https://blog.no-claw.com/posts/778337f4/"/>
    <id>https://blog.no-claw.com/posts/778337f4/</id>
    <published>2025-05-28T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Apple 开始换了 ARM 芯片之后，我们还处于 X86 和 ARM 交叉的阶段。所以有时候还得有一台 X86 的开发环境来测试一些兼容性问题。一开始手里面只有低配置的软路由，后来入手了懒猫微服，这个 11 代 i5 的 CPU 终于能够编译一些重型的任务了。</p><p>下面演示如何把懒猫微服配置成一台云端开发机，并分别用 <strong>VS Code</strong> 与 <strong>PyCharm</strong> 进行远程开发。</p><p>虽然懒猫微服的商店已经上架了 code-sever 可以开箱即用，除此之外我们也来探索下其他的方案。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529163903393.png" alt="image-20250529163903393"></p><hr><span id="more"></span><h2 id="1-·-准备-SSH-免密登录"><a href="#1-·-准备-SSH-免密登录" class="headerlink" title="1 · 准备 SSH 免密登录"></a>1 · 准备 SSH 免密登录</h2><p>修改 <code>~/.ssh/config</code>（若无自行创建）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">Host lzc</span><br><span class="line">    HostName server.heiyu.space      # 服务器域名 / IP</span><br><span class="line">    User root                        # 默认用户名</span><br><span class="line">    IdentityFile ~/.ssh/id_ed25519   # 私钥路径</span><br><span class="line">    Port 22                          # 默认端口</span><br><span class="line">    ServerAliveInterval 60           # 60 秒保活</span><br><span class="line">    TCPKeepAlive yes                 # TCP 保持连接</span><br><span class="line">    ForwardAgent yes                 # 允许代理转发</span><br><span class="line">    IdentitiesOnly yes               # 只尝试显式钥匙</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">ssh lzc          <span class="comment"># 应直接登录而不再提示密码</span></span><br></pre></td></tr></table></figure><hr><h2 id="2-·-VS-Code-：一键-Remote"><a href="#2-·-VS-Code-：一键-Remote" class="headerlink" title="2 · VS Code ：一键 Remote"></a>2 · VS Code ：一键 Remote</h2><blockquote><p>需要 <strong>Remote Development</strong> 三件套插件（SSH &#x2F; WSL &#x2F; Containers）。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161722643.png" alt="VS Code 插件"></p><ol><li>安装完成后，点击左下角 <strong>棕黄色</strong>的 Remote 图标。</li><li>VS Code 自动读取 <code>~/.ssh/config</code>，显示刚才的 <strong>lzc</strong> 主机。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161700856.png" alt="选择主机"></li><li>选择需要打开的文件夹，我们是 root 登录，所以基本上所有的路径都有访问权限，由于懒猫微服的设置，除了 root 目录外，其他的目标可能会被清空，所以我们尽量把代码留在 root 目录下就好。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529162340274.png" alt="选择目录"></li><li>打开之后我们就可以看到代码了，剩下的就和本地差不多了，这也就是偷懒不学 VIM 的原因吧。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161829107.png" alt="远程工作区"></li></ol><hr><h2 id="3-·-PyCharm-：Gateway-秒连"><a href="#3-·-PyCharm-：Gateway-秒连" class="headerlink" title="3 · PyCharm ：Gateway 秒连"></a>3 · PyCharm ：Gateway 秒连</h2><p>我们再来看 Pycharm，默认提供了远程开发的功能。记得最早 Jetbrains 的是这么实现的，把本地代码推送到远端的&#x2F;tmp 文件夹然后调用远端编译器执行。现在用了 Gateway 基本可以达到实时的效果了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161730354.png" alt="PyCharm Remote 入口"></p><ol><li>依次点击 <strong>New SSH Configuration → Add</strong>，填入主机、端口、用户名，并选择 <strong>私钥</strong> 或 <strong>密码</strong>。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161741726.png" alt="配置 SSH"></li><li>连接成功后，选择需要打开的服务器目录（同样建议用 <code>/root/...</code>）。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161748708.png" alt="选择工程目录"></li><li>首次连接时 PyCharm 会在服务器端安装一个 <strong>IDE Agent</strong>。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529163010706.png" alt="安装 Agent"></li><li>安装完毕即可像本地一样运行、调试、补全。<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250529161803751.png" alt="远程编辑器"></li></ol><hr><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>apple 的 M 芯片用来开发，而懒微服就作为上线环境之前的测试环境，当需要 X86 环境的时候，随时切换过去。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/31aa8fe0-4d90-4b1a-948f-c97288f47c64.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服搭配 VS Code SSH Remote，打造随时随地可用的云端开发环境。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="SSH" scheme="https://blog.no-claw.com/tags/SSH/"/>
    
    <category term="远程开发" scheme="https://blog.no-claw.com/tags/%E8%BF%9C%E7%A8%8B%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>写给懒猫微服玩家的容器小书Docker篇（五）：《多容器交响曲：Docker Compose 上场》</title>
    <link href="https://blog.no-claw.com/posts/61cfcd24/"/>
    <id>https://blog.no-claw.com/posts/61cfcd24/</id>
    <published>2025-05-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一直想写一本容器小书，真好懒猫基本都做了容器化，所以把这部分分享出来。不同的是，懒猫微服中使用 pg-docker 来替代 docker 命令，使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主，这样子既学会了 docker 知识，也能够在懒猫微服上启动 Docker 服务。</p></blockquote><h1 id="《多容器交响曲：Docker-Compose-上场》讲的是使用-Docker-Compose-统一编排多容器服务，理解-YAML-配置结构、服务依赖、网络、挂载、构建策略、变量管理、Compose-vs-K8s-初探等"><a href="#《多容器交响曲：Docker-Compose-上场》讲的是使用-Docker-Compose-统一编排多容器服务，理解-YAML-配置结构、服务依赖、网络、挂载、构建策略、变量管理、Compose-vs-K8s-初探等" class="headerlink" title="《多容器交响曲：Docker Compose 上场》讲的是使用 Docker Compose 统一编排多容器服务，理解 YAML 配置结构、服务依赖、网络、挂载、构建策略、变量管理、Compose vs K8s 初探等"></a>《多容器交响曲：Docker Compose 上场》讲的是使用 Docker Compose 统一编排多容器服务，理解 YAML 配置结构、服务依赖、网络、挂载、构建策略、变量管理、Compose vs K8s 初探等</h1><hr><h3 id="🎼-开篇：服务之间的管弦乐团"><a href="#🎼-开篇：服务之间的管弦乐团" class="headerlink" title="🎼 开篇：服务之间的管弦乐团"></a>🎼 开篇：服务之间的管弦乐团</h3><p>随着项目日益复杂，小李的服务已经不再是一个容器就能承载的了。</p><p>前端、后端、数据库、缓存、日志系统……像一个交响乐团，需要统一调度、和谐配合。</p><p>老周递给他一个新的工具：“<strong>Docker Compose</strong>——它是你的指挥棒。”</p><hr><h2 id="🎻-第一节：什么是-Docker-Compose？"><a href="#🎻-第一节：什么是-Docker-Compose？" class="headerlink" title="🎻 第一节：什么是 Docker Compose？"></a>🎻 第一节：什么是 Docker Compose？</h2><p>老周解释：</p><blockquote><p>“Docker Compose 是 Docker 的多容器编排工具，用一份 <code>docker-compose.yml</code> 文件，就能同时启动、停止、构建多个服务。”</p></blockquote><p>Compose 帮你解决：</p><ul><li>多个服务启动顺序</li><li>多容器共享网络</li><li>统一管理环境变量</li><li>配置简洁、开发者友好</li><li>跨平台部署一致</li></ul><hr><span id="more"></span><h2 id="📄-第二节：写出你的第一个-docker-compose-yml"><a href="#📄-第二节：写出你的第一个-docker-compose-yml" class="headerlink" title="📄 第二节：写出你的第一个 docker-compose.yml"></a>📄 第二节：写出你的第一个 <code>docker-compose.yml</code></h2><p>小李的项目结构如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">myapp/</span><br><span class="line">├── backend/      # Flask 应用</span><br><span class="line">│   ├── app.py</span><br><span class="line">│   └── Dockerfile</span><br><span class="line">├── frontend/     # 静态页面</span><br><span class="line">│   ├── index.html</span><br><span class="line">│   └── Dockerfile</span><br><span class="line">└── docker-compose.yml</span><br></pre></td></tr></table></figure><h3 id="docker-compose-yml-示例："><a href="#docker-compose-yml-示例：" class="headerlink" title="docker-compose.yml 示例："></a><code>docker-compose.yml</code> 示例：</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><span class="line">10</span><br><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="attr">version:</span> <span class="string">&quot;3.9&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">backend:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">./backend</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5000:5000&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./backend:/app</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_HOST=db</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">frontend:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">./frontend</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;3000:80&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">mysql:5.7</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MYSQL_ROOT_PASSWORD=123456</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MYSQL_DATABASE=mydb</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">dbdata:/var/lib/mysql</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">dbdata:</span></span><br></pre></td></tr></table></figure><blockquote><p>🔧 每个 <code>service</code> 就是一个容器定义，Compose 会为它们创建默认网络，自动 DNS 互通。</p></blockquote><hr><h2 id="🧪-第三节：Compose-命令实战速查"><a href="#🧪-第三节：Compose-命令实战速查" class="headerlink" title="🧪 第三节：Compose 命令实战速查"></a>🧪 第三节：Compose 命令实战速查</h2><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">docker-compose up -d</span><br></pre></td></tr></table></figure><p>后台启动所有服务！</p><p>其他常用命令：</p><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>构建镜像</td><td><code>docker-compose build</code></td></tr><tr><td>后台启动</td><td><code>docker-compose up -d</code></td></tr><tr><td>前台启动 + 日志输出</td><td><code>docker-compose up</code></td></tr><tr><td>停止服务</td><td><code>docker-compose down</code></td></tr><tr><td>查看容器日志</td><td><code>docker-compose logs [服务名]</code></td></tr><tr><td>重启某个服务</td><td><code>docker-compose restart 服务名</code></td></tr><tr><td>进入某个容器</td><td><code>docker-compose exec 服务名 bash</code></td></tr></tbody></table><hr><h2 id="📦-第四节：Compose-的网络与数据共享机制"><a href="#📦-第四节：Compose-的网络与数据共享机制" class="headerlink" title="📦 第四节：Compose 的网络与数据共享机制"></a>📦 第四节：Compose 的网络与数据共享机制</h2><p>老周介绍：</p><blockquote><p>“Compose 默认创建一个网络，<strong>所有服务能通过服务名互相访问</strong>。”</p></blockquote><p>在上面的例子中：</p><ul><li><code>backend</code> 容器可以用 <code>db:3306</code> 连接 MySQL</li><li><code>frontend</code> 可通过 <code>backend:5000</code> 访问后端 API</li></ul><blockquote><p>小李不再需要手动 <code>docker network create</code> 和 <code>--network</code> 参数，Compose 一切自动打通。</p></blockquote><h3 id="Volume-的挂载："><a href="#Volume-的挂载：" class="headerlink" title="Volume 的挂载："></a>Volume 的挂载：</h3><p>Compose 中的 volume 显式声明（如 <code>dbdata:</code>）会自动创建、管理。</p><p>支持：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">./data:/data</span> <span class="comment"># Bind mount</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">myvolume:/data</span> <span class="comment"># Named volume</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/custom/path:/data:ro</span> <span class="comment"># 带权限控制</span></span><br></pre></td></tr></table></figure><hr><h2 id="🌐-第五节：使用-env-管理配置变量"><a href="#🌐-第五节：使用-env-管理配置变量" class="headerlink" title="🌐 第五节：使用 .env 管理配置变量"></a>🌐 第五节：使用 <code>.env</code> 管理配置变量</h2><p>Compose 支持使用 <code>.env</code> 文件集中管理变量：</p><p><code>.env</code> 文件：</p><figure class="highlight plaintext"><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">DB_PASSWORD=123456</span><br><span class="line">DB_NAME=mydb</span><br></pre></td></tr></table></figure><p>Compose 文件中使用方式：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">environment:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">MYSQL_ROOT_PASSWORD=$&#123;DB_PASSWORD&#125;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">MYSQL_DATABASE=$&#123;DB_NAME&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>🚀 配合 CI&#x2F;CD 时 <code>.env</code> 可由流水线动态生成，便于多环境切换（dev&#x2F;stage&#x2F;prod）。</p></blockquote><hr><h2 id="🧬-第六节：高级配置技巧"><a href="#🧬-第六节：高级配置技巧" class="headerlink" title="🧬 第六节：高级配置技巧"></a>🧬 第六节：高级配置技巧</h2><h3 id="1-统一重启策略："><a href="#1-统一重启策略：" class="headerlink" title="1. 统一重启策略："></a>1. 统一重启策略：</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><p>确保服务宕机时能自动重启。</p><hr><h3 id="2-多阶段构建支持："><a href="#2-多阶段构建支持：" class="headerlink" title="2. 多阶段构建支持："></a>2. 多阶段构建支持：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">./backend</span></span><br><span class="line">  <span class="attr">dockerfile:</span> <span class="string">Dockerfile.prod</span></span><br></pre></td></tr></table></figure><p>可指定构建路径、Dockerfile 文件、构建参数等。</p><hr><h3 id="3-Healthcheck-健康检查："><a href="#3-Healthcheck-健康检查：" class="headerlink" title="3. Healthcheck 健康检查："></a>3. Healthcheck 健康检查：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">  <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;curl&quot;</span>, <span class="string">&quot;-f&quot;</span>, <span class="string">&quot;http://localhost:5000/health&quot;</span>]</span><br><span class="line">  <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">  <span class="attr">timeout:</span> <span class="string">10s</span></span><br><span class="line">  <span class="attr">retries:</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><hr><h2 id="🆚-第七节：Docker-Compose-vs-Kubernetes-简析"><a href="#🆚-第七节：Docker-Compose-vs-Kubernetes-简析" class="headerlink" title="🆚 第七节：Docker Compose vs Kubernetes 简析"></a>🆚 第七节：Docker Compose vs Kubernetes 简析</h2><table><thead><tr><th>特性</th><th>Compose</th><th>Kubernetes</th></tr></thead><tbody><tr><td>启动容器</td><td>简单</td><td>标准化</td></tr><tr><td>配置语言</td><td>YAML</td><td>YAML</td></tr><tr><td>网络</td><td>自动共享</td><td>需显式配置</td></tr><tr><td>存储</td><td>Volume</td><td>PVC + SC</td></tr><tr><td>服务发现</td><td>服务名互通</td><td>DNS&#x2F;ClusterIP</td></tr><tr><td>用途</td><td>本地开发 &#x2F; CI</td><td>集群部署 &#x2F; 云原生</td></tr><tr><td>高可用 &#x2F; 伸缩</td><td>❌</td><td>✅ 内建</td></tr><tr><td>社区生态</td><td>中小项目广泛使用</td><td>大型平台标准方案</td></tr></tbody></table><blockquote><p>小李理解了：Compose 是“轻量乐队指挥”，K8s 是“交响级 AI 指挥系统”。</p></blockquote><hr><h2 id="🔁-第八节：Compose-CI-CD-集成发布"><a href="#🔁-第八节：Compose-CI-CD-集成发布" class="headerlink" title="🔁 第八节：Compose + CI&#x2F;CD 集成发布"></a>🔁 第八节：Compose + CI&#x2F;CD 集成发布</h2><p>小李将 Compose 整合进 GitLab CI 流程：</p><p><code>.gitlab-ci.yml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">docker:dind</span></span><br><span class="line"></span><br><span class="line"><span class="attr">stages:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">build</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">deploy</span></span><br><span class="line"></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">stage:</span> <span class="string">build</span></span><br><span class="line">  <span class="attr">script:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">docker-compose</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">deploy:</span></span><br><span class="line">  <span class="attr">stage:</span> <span class="string">deploy</span></span><br><span class="line">  <span class="attr">script:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">docker-compose</span> <span class="string">up</span> <span class="string">-d</span></span><br></pre></td></tr></table></figure><p>CI 构建完镜像后，直接用 Compose 部署，既省事又稳定。</p><hr><h2 id="🧠-小李-Compose-使用技巧总结"><a href="#🧠-小李-Compose-使用技巧总结" class="headerlink" title="🧠 小李 Compose 使用技巧总结"></a>🧠 小李 Compose 使用技巧总结</h2><table><thead><tr><th>目标</th><th>技巧</th></tr></thead><tbody><tr><td>服务隔离</td><td>每个项目单独一个 Compose 文件</td></tr><tr><td>共享网络</td><td>不用写 <code>network</code>，服务名即 DNS</td></tr><tr><td>跨环境配置</td><td>使用 <code>.env</code> 动态切换变量</td></tr><tr><td>状态排查</td><td><code>docker-compose logs</code> &#x2F; <code>ps</code> &#x2F; <code>exec</code></td></tr><tr><td>多环境文件</td><td>使用 <code>docker-compose.override.yml</code></td></tr><tr><td>CI&#x2F;CD 联动</td><td>编排命令嵌入流水线任务中</td></tr></tbody></table><hr><h2 id="🎬-尾声：指挥容器交响曲的时刻"><a href="#🎬-尾声：指挥容器交响曲的时刻" class="headerlink" title="🎬 尾声：指挥容器交响曲的时刻"></a>🎬 尾声：指挥容器交响曲的时刻</h2><p>当小李敲下 <code>docker-compose up</code> 的回车键，前端、后端、数据库同时启动，日志齐鸣，服务稳定运行。</p><blockquote><p>“这就是我梦想的微服务协奏曲啊。”</p></blockquote><p>老周拍了拍他肩膀：</p><blockquote><p>“你已经能用 Compose 驾驭服务之海，下一步，是把它们部署上云、扩展弹性、自动滚动升级。”</p></blockquote><p>小李点头，望向远方的集群编排系统——Kubernetes，眼中闪着新的渴望。</p><p>好！那我们就在<strong>第五章《多容器交响曲》</strong>中，继续深入补充这四大实战技能：</p><hr><h2 id="🧩-补充一：Compose-的多环境部署实践（开发、测试、生产）"><a href="#🧩-补充一：Compose-的多环境部署实践（开发、测试、生产）" class="headerlink" title="🧩 补充一：Compose 的多环境部署实践（开发、测试、生产）"></a>🧩 补充一：Compose 的多环境部署实践（开发、测试、生产）</h2><p>小李希望同一套服务，在开发、测试、生产环境下分别使用不同的配置，比如：</p><ul><li>数据库密码不同</li><li>是否挂载本地代码</li><li>是否启用调试模式</li></ul><p>老周教他使用 Compose 的<strong>多文件配置机制</strong>：</p><hr><h3 id="✅-方案一：使用-docker-compose-override-yml"><a href="#✅-方案一：使用-docker-compose-override-yml" class="headerlink" title="✅ 方案一：使用 docker-compose.override.yml"></a>✅ 方案一：使用 <code>docker-compose.override.yml</code></h3><p>Docker Compose 默认会<strong>自动加载 <code>docker-compose.override.yml</code></strong> 并与主文件合并。</p><h4 id="文件结构："><a href="#文件结构：" class="headerlink" title="文件结构："></a>文件结构：</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker-compose.yml</span><br><span class="line">docker-compose.override.yml</span><br><span class="line">.env.dev</span><br><span class="line">.env.prod</span><br></pre></td></tr></table></figure><h4 id="主配置（docker-compose-yml）："><a href="#主配置（docker-compose-yml）：" class="headerlink" title="主配置（docker-compose.yml）："></a>主配置（docker-compose.yml）：</h4><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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8000:8000&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ENV_MODE=$&#123;MODE&#125;</span></span><br></pre></td></tr></table></figure><h4 id="开发环境覆盖文件（docker-compose-override-yml）："><a href="#开发环境覆盖文件（docker-compose-override-yml）：" class="headerlink" title="开发环境覆盖文件（docker-compose.override.yml）："></a>开发环境覆盖文件（docker-compose.override.yml）：</h4><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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./src:/app/src</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DEBUG=true</span></span><br></pre></td></tr></table></figure><p>运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MODE=development docker-compose up</span><br></pre></td></tr></table></figure><hr><h3 id="✅-方案二：按环境拆分多个-Compose-文件"><a href="#✅-方案二：按环境拆分多个-Compose-文件" class="headerlink" title="✅ 方案二：按环境拆分多个 Compose 文件"></a>✅ 方案二：按环境拆分多个 Compose 文件</h3><p>适合 CI&#x2F;CD 或部署多个 stage。</p><h4 id="示例："><a href="#示例：" class="headerlink" title="示例："></a>示例：</h4><ul><li><code>docker-compose.dev.yml</code></li><li><code>docker-compose.prod.yml</code></li></ul><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">docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d</span><br></pre></td></tr></table></figure><p>多个 <code>-f</code> 会按顺序合并，后面覆盖前面。</p><blockquote><p>📦 建议主文件写“公共配置”，子文件按环境细化。</p></blockquote><hr><h2 id="🎯-补充二：优化服务依赖启动顺序"><a href="#🎯-补充二：优化服务依赖启动顺序" class="headerlink" title="🎯 补充二：优化服务依赖启动顺序"></a>🎯 补充二：优化服务依赖启动顺序</h2><p>小李发现，即使写了 <code>depends_on</code>，后端有时候也连不上数据库。</p><p>老周摇头说：</p><blockquote><p>“<code>depends_on</code> 只是控制启动<strong>顺序</strong>，<strong>不是等服务就绪</strong>。数据库可能还没监听端口就已被标记为 ‘up’。”</p></blockquote><hr><h3 id="✅-正确姿势：服务内设置“等待就绪”"><a href="#✅-正确姿势：服务内设置“等待就绪”" class="headerlink" title="✅ 正确姿势：服务内设置“等待就绪”"></a>✅ 正确姿势：服务内设置“等待就绪”</h3><p>比如 Flask 等数据库：</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"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="keyword">until</span> nc -z db 3306; <span class="keyword">do</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;Waiting for db...&quot;</span></span><br><span class="line">  <span class="built_in">sleep</span> 1</span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line">python app.py</span><br></pre></td></tr></table></figure><p>或者使用工具包如 <a href="https://github.com/vishnubob/wait-for-it"><code>wait-for-it.sh</code></a>：</p><figure class="highlight dockerfile"><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="keyword">COPY</span><span class="language-bash"> wait-for-it.sh /wait-for-it.sh</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;/wait-for-it.sh&quot;</span>, <span class="string">&quot;db:3306&quot;</span>, <span class="string">&quot;--&quot;</span>, <span class="string">&quot;python&quot;</span>, <span class="string">&quot;app.py&quot;</span>]</span></span><br></pre></td></tr></table></figure><blockquote><p>🩺 推荐结合容器健康检查，判断服务是否真正 ready。</p></blockquote><hr><h2 id="🛠-补充三：Docker-Compose-V1-➜-V2-迁移技巧"><a href="#🛠-补充三：Docker-Compose-V1-➜-V2-迁移技巧" class="headerlink" title="🛠 补充三：Docker Compose V1 ➜ V2 迁移技巧"></a>🛠 补充三：Docker Compose V1 ➜ V2 迁移技巧</h2><p>小李的 CI 工具用的是 Compose v1，项目准备升级。</p><p>老周提醒：</p><blockquote><p>“Docker Compose v2 使用的是 <code>docker compose</code>（空格），而非 <code>docker-compose</code>（短横线）。”</p></blockquote><hr><h3 id="✅-主要变化："><a href="#✅-主要变化：" class="headerlink" title="✅ 主要变化："></a>✅ 主要变化：</h3><table><thead><tr><th>项目</th><th>v1 (<code>docker-compose</code>)</th><th>v2 (<code>docker compose</code>)</th></tr></thead><tbody><tr><td>命令格式</td><td><code>docker-compose up</code></td><td><code>docker compose up</code></td></tr><tr><td>安装方式</td><td>独立二进制</td><td>集成于 Docker CLI</td></tr><tr><td>文件格式</td><td><code>v2</code>, <code>v3</code></td><td>推荐统一 <code>v3.9</code></td></tr></tbody></table><hr><h3 id="✅-迁移建议："><a href="#✅-迁移建议：" class="headerlink" title="✅ 迁移建议："></a>✅ 迁移建议：</h3><ul><li>删除旧的 <code>docker-compose</code> 二进制</li><li>使用 <code>docker compose</code> CLI</li><li>更新脚本、CI 工具调用方式</li><li>移除 legacy 字段（如 <code>links</code>）</li><li>检查 <code>.env</code> 是否兼容（v2 更严格）</li></ul><hr><h2 id="☸️-补充四：Compose-与-Helm-的映射关系对照"><a href="#☸️-补充四：Compose-与-Helm-的映射关系对照" class="headerlink" title="☸️ 补充四：Compose 与 Helm 的映射关系对照"></a>☸️ 补充四：Compose 与 Helm 的映射关系对照</h2><p>当小李进入 Kubernetes 世界，他问老周：</p><blockquote><p>“Compose 文件和 K8s 的 YAML 有啥对应关系？”</p></blockquote><p>老周说：“很好理解，Compose 是开发者的 K8s 简化版本。”</p><hr><h3 id="对照表："><a href="#对照表：" class="headerlink" title="对照表："></a>对照表：</h3><table><thead><tr><th>Compose</th><th>Kubernetes</th></tr></thead><tbody><tr><td><code>services:</code></td><td><code>Deployment + Pod</code></td></tr><tr><td><code>volumes:</code></td><td><code>PersistentVolumeClaim</code></td></tr><tr><td><code>ports:</code></td><td><code>Service</code>（NodePort &#x2F; ClusterIP）</td></tr><tr><td><code>depends_on:</code></td><td><code>initContainers</code> 或 readinessProbe</td></tr><tr><td><code>.env</code></td><td>ConfigMap &#x2F; Secret</td></tr><tr><td><code>docker-compose.yml</code></td><td>Helm Chart (values.yaml + templates)</td></tr></tbody></table><hr><h3 id="示例：Compose-转-Helm-构思"><a href="#示例：Compose-转-Helm-构思" class="headerlink" title="示例：Compose 转 Helm 构思"></a>示例：Compose 转 Helm 构思</h3><h4 id="Compose-配置："><a href="#Compose-配置：" class="headerlink" title="Compose 配置："></a>Compose 配置：</h4><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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">myapp:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:80&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DEBUG=true</span></span><br></pre></td></tr></table></figure><h4 id="Helm-values-yaml："><a href="#Helm-values-yaml：" class="headerlink" title="Helm values.yaml："></a>Helm <code>values.yaml</code>：</h4><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></pre></td><td class="code"><pre><span class="line"><span class="attr">image:</span></span><br><span class="line">  <span class="attr">repository:</span> <span class="string">myapp</span></span><br><span class="line">  <span class="attr">tag:</span> <span class="string">latest</span></span><br><span class="line"></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">value:</span> <span class="string">&quot;true&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">service:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">  <span class="attr">targetPort:</span> <span class="number">80</span></span><br></pre></td></tr></table></figure><h4 id="Helm-deployment-yaml（模板）："><a href="#Helm-deployment-yaml（模板）：" class="headerlink" title="Helm deployment.yaml（模板）："></a>Helm <code>deployment.yaml</code>（模板）：</h4><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></pre></td><td class="code"><pre><span class="line"><span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">    <span class="attr">image:</span> &#123;&#123; <span class="string">.Values.image.repository</span> &#125;&#125;<span class="string">:&#123;&#123;</span> <span class="string">.Values.image.tag</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">env:</span></span><br><span class="line">      &#123;&#123;<span class="bullet">-</span> <span class="string">range</span> <span class="string">.Values.env</span> &#125;&#125;</span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> &#123;&#123; <span class="string">.name</span> &#125;&#125;</span><br><span class="line">        <span class="attr">value:</span> &#123;&#123; <span class="string">.value</span> &#125;&#125;</span><br><span class="line">      &#123;&#123;<span class="bullet">-</span> <span class="string">end</span> &#125;&#125;</span><br></pre></td></tr></table></figure><blockquote><p>✅ 小李意识到，Helm 是“模板化 + 分层管理”的 Compose 超集，是云原生部署的标准组件管理器。</p></blockquote><hr><h2 id="🎬-尾声：Compose-是微服务上云的跳板"><a href="#🎬-尾声：Compose-是微服务上云的跳板" class="headerlink" title="🎬 尾声：Compose 是微服务上云的跳板"></a>🎬 尾声：Compose 是微服务上云的跳板</h2><p>小李已经用 Docker Compose 实现了：</p><ul><li>开发环境热更新</li><li>测试环境集成数据库</li><li>生产环境独立配置</li><li>CI&#x2F;CD 自动部署服务</li><li>为 Kubernetes 迁移打下基础</li></ul><p>他明白了：</p><blockquote><p>“Compose 就像舞台排练，Kubernetes 才是真正的大型音乐厅。但有了排练，登台才不会慌。”</p></blockquote>]]></content>
    
    
    <summary type="html">Docker 入门第五章：用 Docker Compose 编排多容器应用</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="容器" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%AE%B9%E5%99%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>写给懒猫微服玩家的容器小书 Docker篇（四）：《数据之岛与持久化卷》</title>
    <link href="https://blog.no-claw.com/posts/8b5626cb/"/>
    <id>https://blog.no-claw.com/posts/8b5626cb/</id>
    <published>2025-05-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一直想写一本容器小书，真好懒猫基本都做了容器化，所以把这部分分享出来。不同的是，懒猫微服中使用 pg-docker 来替代 docker 命令，使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主，这样子既学会了 docker 知识，也能够在懒猫微服上启动 Docker 服务。</p></blockquote><h1 id="《数据之岛与持久化卷》讲的是-Docker-Volume-持久化数据方案、挂载宿主机目录、多个容器共享数据、自动创建卷、数据备份与恢复等"><a href="#《数据之岛与持久化卷》讲的是-Docker-Volume-持久化数据方案、挂载宿主机目录、多个容器共享数据、自动创建卷、数据备份与恢复等" class="headerlink" title="《数据之岛与持久化卷》讲的是 Docker Volume 持久化数据方案、挂载宿主机目录、多个容器共享数据、自动创建卷、数据备份与恢复等"></a>《数据之岛与持久化卷》讲的是 Docker Volume 持久化数据方案、挂载宿主机目录、多个容器共享数据、自动创建卷、数据备份与恢复等</h1><h3 id="🏝️-开篇：数据会随浪消失"><a href="#🏝️-开篇：数据会随浪消失" class="headerlink" title="🏝️ 开篇：数据会随浪消失"></a>🏝️ 开篇：数据会随浪消失</h3><p>有一天，小李运行了一个容器，里面的 Flask 项目能正常写入用户信息到 SQLite 数据库。可当容器一停止，再启动——所有数据消失了！</p><p>老周说：“你的数据，被潮水带走了。”</p><blockquote><p>“Docker 容器默认的文件系统是<strong>临时的</strong>，只要容器删除，数据也就没了。想让数据真正存活，就要登上‘<strong>数据之岛</strong>’。”</p></blockquote><hr><h2 id="🧠-基础概念：数据卷（Volume）"><a href="#🧠-基础概念：数据卷（Volume）" class="headerlink" title="🧠 基础概念：数据卷（Volume）"></a>🧠 基础概念：数据卷（Volume）</h2><p>Docker 提供了三种数据持久化方案：</p><table><thead><tr><th>方案</th><th>用法</th><th>场景</th></tr></thead><tbody><tr><td>Volume</td><td>Docker 管理的专属数据区</td><td>最推荐、安全、可多容器共享</td></tr><tr><td>Bind Mount</td><td>映射宿主机目录</td><td>更灵活，适合本地调试</td></tr><tr><td>tmpfs</td><td>临时存储在内存</td><td>适合敏感数据，重启即丢弃</td></tr></tbody></table><blockquote><p>本章重点讲解：<strong>Volume（数据卷）</strong> 与 <strong>Bind Mount（绑定挂载）</strong></p></blockquote><hr><span id="more"></span><h2 id="📦-第一节：使用-Volume-保存数据"><a href="#📦-第一节：使用-Volume-保存数据" class="headerlink" title="📦 第一节：使用 Volume 保存数据"></a>📦 第一节：使用 Volume 保存数据</h2><p>小李重新部署了 MySQL 容器，他决定为数据提供一个“保命空间”。</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">docker volume create mysql-data</span><br></pre></td></tr></table></figure><p>然后运行 MySQL 时挂载：</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">docker run -d --name my-mysql \</span><br><span class="line">  -e MYSQL_ROOT_PASSWORD=123456 \</span><br><span class="line">  -v mysql-data:/var/lib/mysql \</span><br><span class="line">  mysql:5.7</span><br></pre></td></tr></table></figure><blockquote><p><code>-v 卷名:容器内目录</code>：将卷挂载到容器内数据库文件存储位置。</p></blockquote><p>容器即使删除，数据卷依然保留！</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">docker volume <span class="built_in">ls</span></span><br></pre></td></tr></table></figure><h3 id="查看卷详细信息："><a href="#查看卷详细信息：" class="headerlink" title="查看卷详细信息："></a>查看卷详细信息：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker volume inspect mysql-data</span><br></pre></td></tr></table></figure><p>输出中可以看到 <code>Mountpoint</code>，即数据在宿主机上的物理位置。</p><hr><h2 id="🧪-第二节：自动创建匿名-具名卷"><a href="#🧪-第二节：自动创建匿名-具名卷" class="headerlink" title="🧪 第二节：自动创建匿名&#x2F;具名卷"></a>🧪 第二节：自动创建匿名&#x2F;具名卷</h2><p>小李写了个简单的服务：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /app/data</span></span><br></pre></td></tr></table></figure><p>每次 <code>docker run</code> 时，Docker 会<strong>自动生成匿名卷</strong>挂载到 <code>/app/data</code>。</p><p>但这类匿名卷难以追踪、管理，老周建议：</p><blockquote><p>“生产环境请用<strong>具名卷</strong>，并在运行时用 <code>-v</code> 显式指定。”</p></blockquote><hr><h2 id="🔗-第三节：绑定挂载宿主机目录（本地调试神器）"><a href="#🔗-第三节：绑定挂载宿主机目录（本地调试神器）" class="headerlink" title="🔗 第三节：绑定挂载宿主机目录（本地调试神器）"></a>🔗 第三节：绑定挂载宿主机目录（本地调试神器）</h2><p>开发中，小李想把宿主机的项目代码直接挂进容器，不必每次重建镜像。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name dev-nginx \</span><br><span class="line">  -p 8080:80 \</span><br><span class="line">  -v /Users/xiaoli/site:/usr/share/nginx/html \</span><br><span class="line">  nginx</span><br></pre></td></tr></table></figure><blockquote><p>本地 <code>/Users/xiaoli/site</code> 的代码实时反映在容器内网站目录，修改立刻生效！</p></blockquote><h3 id="使用-Bind-Mount-的场景："><a href="#使用-Bind-Mount-的场景：" class="headerlink" title="使用 Bind Mount 的场景："></a>使用 Bind Mount 的场景：</h3><ul><li>本地开发热更新</li><li>日志文件落盘</li><li>配置文件映射</li><li>IDE + 容器联调</li></ul><hr><h2 id="🤝-第四节：多个容器共享数据卷"><a href="#🤝-第四节：多个容器共享数据卷" class="headerlink" title="🤝 第四节：多个容器共享数据卷"></a>🤝 第四节：多个容器共享数据卷</h2><p>老周给小李展示了另一个高级玩法：</p><blockquote><p>“多个容器可以挂载同一个卷，<strong>共享数据</strong>，就像一块公共磁盘。”</p></blockquote><p>小李准备两个容器：</p><ul><li>一个容器写入日志</li><li>一个容器实时读取日志</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 写入容器</span></span><br><span class="line">docker run -d --name logger \</span><br><span class="line">  -v shared-logs:/logs \</span><br><span class="line">  busybox sh -c <span class="string">&quot;while true; do date &gt;&gt; /logs/t.log; sleep 2; done&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取容器</span></span><br><span class="line">docker run -it --name reader \</span><br><span class="line">  -v shared-logs:/logs \</span><br><span class="line">  busybox <span class="built_in">tail</span> -f /logs/t.log</span><br></pre></td></tr></table></figure><p>这两个容器在不联网的情况下，通过挂载卷实现了<strong>数据同步</strong>，让小李直呼神奇。</p><hr><h2 id="🔄-第五节：备份与恢复数据卷"><a href="#🔄-第五节：备份与恢复数据卷" class="headerlink" title="🔄 第五节：备份与恢复数据卷"></a>🔄 第五节：备份与恢复数据卷</h2><p>老周说：</p><blockquote><p>“你现在的数据安全了，但还不够。万一服务器挂了怎么办？你得学会备份。”</p></blockquote><h3 id="备份数据卷为-tar："><a href="#备份数据卷为-tar：" class="headerlink" title="备份数据卷为 .tar："></a>备份数据卷为 <code>.tar</code>：</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></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> \</span><br><span class="line">  -v mysql-data:/data \</span><br><span class="line">  -v $(<span class="built_in">pwd</span>):/backup \</span><br><span class="line">  busybox \</span><br><span class="line">  tar czf /backup/mysql-backup.tar.gz -C /data .</span><br></pre></td></tr></table></figure><blockquote><p>⛴️ 第一个挂载是数据卷，第二个挂载是宿主机当前目录，输出备份包。</p></blockquote><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></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> \</span><br><span class="line">  -v mysql-data:/data \</span><br><span class="line">  -v $(<span class="built_in">pwd</span>):/backup \</span><br><span class="line">  busybox \</span><br><span class="line">  tar xzf /backup/mysql-backup.tar.gz -C /data</span><br></pre></td></tr></table></figure><p>只需备份 <code>.tar.gz</code> 文件即可，适合迁移数据、升级、容灾。</p><hr><h2 id="🧹-第六节：清理无用卷（慎用）"><a href="#🧹-第六节：清理无用卷（慎用）" class="headerlink" title="🧹 第六节：清理无用卷（慎用）"></a>🧹 第六节：清理无用卷（慎用）</h2><p>随着实验多了，小李电脑堆满了无主卷。</p><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">docker volume <span class="built_in">ls</span></span><br></pre></td></tr></table></figure><p>清理：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker volume <span class="built_in">rm</span> 卷名</span><br></pre></td></tr></table></figure><p>清除所有未被挂载的孤立卷（慎用）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker volume prune</span><br></pre></td></tr></table></figure><blockquote><p>清理命令要慎重，别误删生产卷！</p></blockquote><hr><h2 id="📋-Volume-挂载选项速查表"><a href="#📋-Volume-挂载选项速查表" class="headerlink" title="📋 Volume 挂载选项速查表"></a>📋 Volume 挂载选项速查表</h2><table><thead><tr><th>类型</th><th>命令</th><th>特点</th></tr></thead><tbody><tr><td>匿名卷</td><td><code>-v /path</code></td><td>难追踪，系统自动命名</td></tr><tr><td>具名卷</td><td><code>-v myvol:/path</code></td><td>推荐用法，可管理</td></tr><tr><td>Bind 挂载</td><td><code>-v /host:/container</code></td><td>与宿主机文件交互，适合本地调试</td></tr><tr><td>tmpfs</td><td><code>--tmpfs /path</code></td><td>内存存储，重启即消失</td></tr><tr><td>权限控制</td><td><code>-v myvol:/path:ro</code></td><td>只读挂载</td></tr><tr><td>SELinux&#x2F;AppArmor</td><td><code>:z</code>、<code>:Z</code>（高级安全挂载）</td><td>安全增强场景</td></tr></tbody></table><hr><h2 id="🧠-小李的应用持久化策略建议"><a href="#🧠-小李的应用持久化策略建议" class="headerlink" title="🧠 小李的应用持久化策略建议"></a>🧠 小李的应用持久化策略建议</h2><table><thead><tr><th>类型</th><th>内容</th></tr></thead><tbody><tr><td>数据库</td><td>必须挂载 Volume 保持数据持久</td></tr><tr><td>日志</td><td>推荐落盘到宿主机或集中采集</td></tr><tr><td>配置文件</td><td>可用 Bind Mount 从本地同步配置</td></tr><tr><td>静态资源</td><td>静态目录挂载 + CDN</td></tr><tr><td>临时缓存</td><td>tmpfs 或容器内路径，无需持久化</td></tr></tbody></table><hr><h2 id="🎬-尾声：构建自己的数据之岛"><a href="#🎬-尾声：构建自己的数据之岛" class="headerlink" title="🎬 尾声：构建自己的数据之岛"></a>🎬 尾声：构建自己的数据之岛</h2><p>小李站在一个小岛码头，身后是一个个挂载卷，他的应用和数据终于<strong>脱离容器生命周期的束缚</strong>。</p><p>老周说：</p><blockquote><p>“真正的服务，要能容器随时销毁，数据却永存。”</p></blockquote><p>小李点头，轻轻拍了拍他那卷 MySQL 的备份包，知道自己已经拥有了构建“数据之岛”的能力。</p><hr><p><strong>继续拓展高级内容</strong>，围绕：</p><ol><li>🧪 数据卷在 CI&#x2F;CD 中的作用和实战应用</li><li>☸️ 数据卷在 Kubernetes 中的延伸 —— PVC（PersistentVolumeClaim）挂载</li></ol><p>以小李的旅程为主线，继续带你掌握更强大的容器化数据策略。</p><hr><h2 id="🔧-拓展一：数据卷在-CI-CD-中的角色与实践"><a href="#🔧-拓展一：数据卷在-CI-CD-中的角色与实践" class="headerlink" title="🔧 拓展一：数据卷在 CI&#x2F;CD 中的角色与实践"></a>🔧 拓展一：数据卷在 CI&#x2F;CD 中的角色与实践</h2><p>在一次项目发布中，小李加入了公司的 DevOps 流水线。他发现 GitLab CI 跑测试时，每次都重新构建环境，速度太慢，还会出现缓存丢失的问题。</p><p>老周告诉他：</p><blockquote><p>“在 CI&#x2F;CD 环境中，<strong>合理使用数据卷</strong>可以极大加快构建速度、保留缓存和数据状态。”</p></blockquote><hr><h3 id="✅-使用场景-1：缓存-pip-npm-依赖，加速构建"><a href="#✅-使用场景-1：缓存-pip-npm-依赖，加速构建" class="headerlink" title="✅ 使用场景 1：缓存 pip&#x2F;npm 依赖，加速构建"></a>✅ 使用场景 1：缓存 pip&#x2F;npm 依赖，加速构建</h3><p>小李把 <code>pip install</code> 改成挂载缓存目录：</p><h4 id="GitLab-Runner-示例："><a href="#GitLab-Runner-示例：" class="headerlink" title="GitLab Runner 示例："></a>GitLab Runner 示例：</h4><p><code>.gitlab-ci.yml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">cache:</span></span><br><span class="line">  <span class="attr">key:</span> <span class="string">pip-cache</span></span><br><span class="line">  <span class="attr">paths:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">.cache/pip</span></span><br><span class="line"></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">script:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">docker</span> <span class="string">run</span> <span class="string">--rm</span></span><br><span class="line">      <span class="string">-v</span> <span class="string">$CI_PROJECT_DIR:/app</span></span><br><span class="line">      <span class="string">-v</span> <span class="string">$CI_PROJECT_DIR/.cache/pip:/root/.cache/pip</span></span><br><span class="line">      <span class="string">my-builder-image</span> <span class="string">bash</span> <span class="string">-c</span> <span class="string">&quot;</span></span><br><span class="line"><span class="string">      cd /app &amp;&amp;</span></span><br><span class="line"><span class="string">      pip install -r requirements.txt &amp;&amp;</span></span><br><span class="line"><span class="string">      pytest</span></span><br><span class="line"><span class="string">      &quot;</span></span><br></pre></td></tr></table></figure><blockquote><p>🔁 依赖安装过程可被缓存，构建速度提升 50%！</p></blockquote><hr><h3 id="✅-使用场景-2：测试数据隔离"><a href="#✅-使用场景-2：测试数据隔离" class="headerlink" title="✅ 使用场景 2：测试数据隔离"></a>✅ 使用场景 2：测试数据隔离</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></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> \</span><br><span class="line">  -v test-volume:/app/test-results \</span><br><span class="line">  e2e-runner:latest</span><br></pre></td></tr></table></figure><p>测试结果可从宿主机挂载目录或卷中提取，用于后续报告生成或持久存档。</p><hr><h3 id="✅-使用场景-3：构建产物跨阶段传递"><a href="#✅-使用场景-3：构建产物跨阶段传递" class="headerlink" title="✅ 使用场景 3：构建产物跨阶段传递"></a>✅ 使用场景 3：构建产物跨阶段传递</h3><p>CI&#x2F;CD 有多阶段：构建 → 测试 → 部署<br>小李通过挂载共享卷，将编译好的前端包从构建容器传给部署容器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker run --name builder -v build-volume:/output frontend-builder</span><br><span class="line">docker run --<span class="built_in">rm</span> -v build-volume:/usr/share/nginx/html nginx</span><br></pre></td></tr></table></figure><hr><h2 id="☸️-拓展二：Kubernetes-中的数据卷演进-——-PVC-实战"><a href="#☸️-拓展二：Kubernetes-中的数据卷演进-——-PVC-实战" class="headerlink" title="☸️ 拓展二：Kubernetes 中的数据卷演进 —— PVC 实战"></a>☸️ 拓展二：Kubernetes 中的数据卷演进 —— PVC 实战</h2><p>进入云原生世界，小李不再直接使用 <code>docker run</code>，而是通过 Kubernetes 来编排容器。</p><p>在 K8s 中，数据卷概念变得更专业：</p><ul><li>Volume（临时存储，随 Pod 生命周期）</li><li>PersistentVolume（PV：管理员创建的存储资源）</li><li>PersistentVolumeClaim（PVC：用户申请的存储）</li><li>StorageClass（存储策略模板）</li></ul><hr><h3 id="🔐-场景一：部署-Stateful-服务（如-MySQL）"><a href="#🔐-场景一：部署-Stateful-服务（如-MySQL）" class="headerlink" title="🔐 场景一：部署 Stateful 服务（如 MySQL）"></a>🔐 场景一：部署 Stateful 服务（如 MySQL）</h3><p>小李写了以下 YAML：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolumeClaim</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mysql-pvc</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">resources:</span></span><br><span class="line">    <span class="attr">requests:</span></span><br><span class="line">      <span class="attr">storage:</span> <span class="string">5Gi</span></span><br><span class="line">  <span class="attr">storageClassName:</span> <span class="string">standard</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mysql</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">mysql</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">mysql</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mysql</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">mysql:5.7</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">MYSQL_ROOT_PASSWORD</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">&quot;123456&quot;</span></span><br><span class="line">          <span class="attr">volumeMounts:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">mountPath:</span> <span class="string">/var/lib/mysql</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">          <span class="attr">persistentVolumeClaim:</span></span><br><span class="line">            <span class="attr">claimName:</span> <span class="string">mysql-pvc</span></span><br></pre></td></tr></table></figure><blockquote><p>💡 PVC 是 “我要一个 5Gi 的盘”，PV 是“管理员给你一个”，Pod 中挂上它即可持久保存数据。</p></blockquote><hr><h3 id="🧠-小知识点：K8s-中-Volume-的对比"><a href="#🧠-小知识点：K8s-中-Volume-的对比" class="headerlink" title="🧠 小知识点：K8s 中 Volume 的对比"></a>🧠 小知识点：K8s 中 Volume 的对比</h3><table><thead><tr><th>类型</th><th>生命周期</th><th>是否持久</th><th>典型用途</th></tr></thead><tbody><tr><td>emptyDir</td><td>Pod 生命周期</td><td>❌</td><td>临时缓存、构建产物传递</td></tr><tr><td>hostPath</td><td>节点目录挂载</td><td>⚠️ 有风险</td><td>本地开发测试</td></tr><tr><td>PVC（推荐）</td><td>与集群存储绑定</td><td>✅</td><td>数据库存储、日志、持久缓存</td></tr></tbody></table><hr><h3 id="📦-StorageClass：自动动态分配-PVC"><a href="#📦-StorageClass：自动动态分配-PVC" class="headerlink" title="📦 StorageClass：自动动态分配 PVC"></a>📦 StorageClass：自动动态分配 PVC</h3><p>在云环境（如 EKS、GKE）中，PVC 可以自动创建对应的 PV（EBS、Ceph、NFS 等），只要指定 StorageClass 即可：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">storageClassName:</span> <span class="string">gp2</span></span><br></pre></td></tr></table></figure><p>这种方式可以实现<strong>跨节点迁移不丢数据</strong>、<strong>按需付费扩容</strong>、<strong>快照备份等高级功能</strong>。</p><hr><h2 id="✅-拓展总结：Docker-到-CI-CD，再到-Kubernetes-的数据策略演进"><a href="#✅-拓展总结：Docker-到-CI-CD，再到-Kubernetes-的数据策略演进" class="headerlink" title="✅ 拓展总结：Docker 到 CI&#x2F;CD，再到 Kubernetes 的数据策略演进"></a>✅ 拓展总结：Docker 到 CI&#x2F;CD，再到 Kubernetes 的数据策略演进</h2><table><thead><tr><th>场景</th><th>技术</th><th>推荐做法</th></tr></thead><tbody><tr><td>本地开发</td><td>Bind Mount</td><td>映射目录，实时更新</td></tr><tr><td>本地持久化</td><td>Volume</td><td>隔离性好，便于管理</td></tr><tr><td>CI&#x2F;CD 中间数据</td><td>卷 &#x2F; 缓存目录</td><td>挂载 <code>.cache</code>、<code>build</code> 等路径</td></tr><tr><td>K8s 中数据存储</td><td>PVC + StorageClass</td><td>可扩展、可备份、可跨节点持久化</td></tr></tbody></table><hr><h2 id="🎬-尾声：从数据孤岛到分布式星图"><a href="#🎬-尾声：从数据孤岛到分布式星图" class="headerlink" title="🎬 尾声：从数据孤岛到分布式星图"></a>🎬 尾声：从数据孤岛到分布式星图</h2><p>在 DevOps 流水线中，小李用数据卷构建了无缝衔接的构建流程；</p><p>在 Kubernetes 集群中，他用 PVC 实现了跨集群节点的 MySQL 数据持久化部署；</p><p>他站在云端存储的星图前，知道自己已经掌握了从 Docker 到 DevOps 再到 Kubernetes 的数据生命周期。</p><p>老周拍拍他：</p><blockquote><p>“你已经造好了船，也撑起了帆。下一步，是用 Compose 编排你的舰队。”</p></blockquote>]]></content>
    
    
    <summary type="html">Docker 入门第四章：数据持久化与 Volume 卷的使用方法</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="容器" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%AE%B9%E5%99%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服故事篇（一）：服务器宕机之后，我和前端靠懒猫微服结对编程</title>
    <link href="https://blog.no-claw.com/posts/d2e0eee4/"/>
    <id>https://blog.no-claw.com/posts/d2e0eee4/</id>
    <published>2025-05-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>故事篇分享我和懒猫微服的方方面面，这里没有高深的技术，也没有过多的讲解。我始终坚信着技术是服务于生活，因为它能够给我们带来小确幸。更多的时候，我们追求技术，有时候为了兴趣，有时候为了心目中那小小的执念。慢慢在这个过程中会成为别人眼中的哆啦 A 梦。懒猫微服是一个百宝箱，我们能拿出千变万化的道具。事情会过去，但是感动和记忆会留下，我们可以随时追忆。</p></blockquote><p>和前端同学约好晚上一起过一遍 API，主要是确认 Swagger 上的接口和字段设置。我事先把后端代码、API 和 Swagger 文档都部署在服务器上，让他先通过浏览器简单预览一遍，然后再开始写前端代码，这样能提前避免前后端格式对不上的问题。</p><p>准备开始之前，结果他突然说：‘你的 Swagger 打不开了。我登陆到管理控制台看了下，果然和之前部署 Dify 一样的问题，telnet 端口和 Ping 都正常，但是 SSH 和 Web 应用全都访问不了了。从基本监控俩看，CPU 和磁盘也没什么问题。大概率又是 OOM 了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250522074447237.png" alt="image-20250522074447237"></p><p>DDL 赶在眼前，得赶紧让他连到我的服务器或者开发机才好。第一个想到的是端口转发，把服务映射到公网上去，一方面调试的时候属于明文传输，在互联网上很容易被监听，篡改报文。另一方面，家里的公网 IP 经常出问题，总有一阵子会封禁所有的端口，所以最后采取了异地组网的方式。</p><p>我和他说，我在我的 NAS 上给你开一个账户，然后你试试来访问我的 Macbook 上的服务。你去下载一个懒猫微服，下载地址是<a href="https://lazycat.cloud/download%EF%BC%8C%E7%84%B6%E5%90%8E%E6%89%AB%E6%8F%8F%E6%88%91%E7%BB%99%E4%BD%A0%E7%9A%84%E4%BA%8C%E7%BB%B4%E7%A0%81%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E8%AE%BF%E9%97%AE%E6%88%91%E7%9A%84NAS%E4%BA%86%E3%80%82%EF%BC%88%E5%9B%9E%E7%9C%8B%E8%BF%99%E6%AE%B5%E6%84%9F%E8%A7%89%E6%9C%89%E7%82%B9%E5%83%8F%E7%94%B5%E4%BF%A1%E8%AF%88%E9%AA%97%E5%93%88%E5%93%88%EF%BC%8C%E6%84%9F%E8%B0%A2%E5%89%8D%E7%AB%AF%E5%90%8C%E5%AD%A6%E7%9A%84%E4%BF%A1%E4%BB%BB%EF%BC%89">https://lazycat.cloud/download，然后扫描我给你的二维码，这样就可以访问我的NAS了。（回看这段感觉有点像电信诈骗哈哈，感谢前端同学的信任）</a></p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250522074640499.png" alt="image-20250522074640499"></p><p>他下载 APP、注册账号，我分配权限后，确认他能正常访问微服主页，就开启了端口转发。把我的 Macbook 的地址和 Web 的端口映射出去，只允许已登录的微服客户端访问。这样就免去了被其他的人中间攻击的烦恼，尤记得第一次把服务公开到整个互联网的时候，日志里出现的 IP 真是天南地北，北欧的，中东的，非洲的……</p><p>现在用懒猫微服做异地组网，整个流程只需几分钟，再也不用担心这些琐碎的问题了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250522064604867.png" alt="image-20250522064604867"></p><hr><p>他问了我几个问题。</p><p>Q1：网址好像打不开。</p><p>A1：你打开懒猫微服试试，或者放在后台。需要靠这个软件来做 DNS 解析。</p><p>Q2：你的懒猫是什么，软件嘛？还是硬件？</p><p>A2：是一个硬件产品，我拍给你，他带了异地组网的功能。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250522064255392.png" alt="image-20250522064255392"></p><p>Q3: 还能穿透其他服务嘛？</p><p>A3: 除了线下的设备，微服商店里的服务都能穿透，比如使用 planka 来管理进度，有点 Jira 那个味道了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250522072631446.png" alt="image-20250522072631446"></p><h4 id="后面写点感悟，关于云服务器，IDC-和微服"><a href="#后面写点感悟，关于云服务器，IDC-和微服" class="headerlink" title="后面写点感悟，关于云服务器，IDC 和微服"></a>后面写点感悟，关于云服务器，IDC 和微服</h4><p>民间几种不同的说法：</p><ol><li>前期可以用云做一些 POC，然后稳定了之后就可以搬回自己的机房了。</li><li>一切的一切都可以交给云厂商来做，一方面减轻了运维人员的负担，另一方面还可以弹性伸缩，即用即付。</li></ol><p>一些传统公司或者是自己有 IDC 的机房会喜欢第一种说法，而云厂商会采取第二种说法。</p><p>这并不是否定公有云的价值，但在使用过程中，除了价格高之外，还有一个现实问题：售后支持常常跟不上。国内这些厂商追求短期效应，心思都放在大客户上，用各种加班来满足大客户的需求，当然也包括一些 24K 纯白的需求。而对于处于调研初期的大公司，小微企业，或者说技术爱好者并没有得到平等的对待，甚至的毫不关心的态度，相对于潜在的商单，他们更喜欢数着现在的钱。同样很多传统做 NAS 的厂家售后也跟不上，要么是响应时间等很长很长，或者给到完全不靠谱的结论甚至一本正经的一读乱回。需要注意的是，这里不是一棒子打死所有的云厂商和 NAS 厂家，而是当你遇到紧急的问题时候，还是有很高的概率遇到这样的冷处理。所以我们常开的一句话玩笑话：<strong>迁走</strong>。</p><p>现在觉得，如果不是生产环境必须要对互联网公开或者强制遵循一些安全上的 0 信任原则，比如 POC 环境或者个人环境，用懒猫就完全足够了，还要什么云服务器，那么贵又那么脆。</p><p>曾经曾经——我也是云计算忠实的拥护者。。。。。。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/1c0037a4-7fea-42df-a7a7-a37f6f47ae35.png" alt="77dea8a6a38817c503c379dd946fc9e4.png" title="77dea8a6a38817c503c379dd946fc9e4.png"></p>]]></content>
    
    
    <summary type="html">服务器宕机后，用懒猫微服搭建临时开发环境实现前后端结对编程的故事</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="故事" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E6%95%85%E4%BA%8B/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>写给懒猫微服玩家的容器小书 Docker篇（三）：《容器部落生活》</title>
    <link href="https://blog.no-claw.com/posts/5517bd46/"/>
    <id>https://blog.no-claw.com/posts/5517bd46/</id>
    <published>2025-05-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="《容器部落生活》讲的是-容器生命周期管理，包括容器的启动、停止、查看、交互、日志、后台运行、重启策略等操作"><a href="#《容器部落生活》讲的是-容器生命周期管理，包括容器的启动、停止、查看、交互、日志、后台运行、重启策略等操作" class="headerlink" title="《容器部落生活》讲的是 容器生命周期管理，包括容器的启动、停止、查看、交互、日志、后台运行、重启策略等操作"></a>《容器部落生活》讲的是 容器生命周期管理，包括容器的启动、停止、查看、交互、日志、后台运行、重启策略等操作</h1><blockquote><p>一直想写一本容器小书，真好懒猫基本都做了容器化，所以把这部分分享出来。不同的是，懒猫微服中使用 pg-docker 来替代 docker 命令，使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主，这样子既学会了 docker 知识，也能够在懒猫微服上启动 Docker 服务。</p></blockquote><p>镜像旅馆的旅途告一段落，小李的下一站是<strong>容器部落</strong>。</p><p>老周牵着他走过一条闪烁着数字光芒的通道，一排排运行中的应用像帐篷一样排列着。有人在调试日志，有人在重启服务，还有人用 <code>bash</code> 正在某个容器里“打补丁”。</p><p>老周说：</p><blockquote><p>“镜像只是静态的模板，<strong>容器才是它们的生命</strong>。容器是镜像运行出来的真实世界。”</p></blockquote><hr><h2 id="🧠-技术基础：容器-vs-镜像"><a href="#🧠-技术基础：容器-vs-镜像" class="headerlink" title="🧠 技术基础：容器 vs 镜像"></a>🧠 技术基础：容器 vs 镜像</h2><table><thead><tr><th>项目</th><th>镜像（Image）</th><th>容器（Container）</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>实际运行中的程序环境</td></tr><tr><td>状态</td><td>不运行</td><td>可运行、停止、销毁</td></tr></tbody></table><hr><span id="more"></span><h2 id="🛫-第一节：容器的启动方式"><a href="#🛫-第一节：容器的启动方式" class="headerlink" title="🛫 第一节：容器的启动方式"></a>🛫 第一节：容器的启动方式</h2><p>小李想运行他的 Flask 应用镜像。</p><p>老周告诉他：</p><blockquote><p>“运行镜像的命令是 <code>docker run</code>，容器就像是用镜像盖起来的一顶帐篷。”</p></blockquote><h3 id="1-最常见方式（临时-前台）："><a href="#1-最常见方式（临时-前台）：" class="headerlink" title="1. 最常见方式（临时 + 前台）："></a>1. 最常见方式（临时 + 前台）：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run my-flask-app</span><br></pre></td></tr></table></figure><p>但程序一执行完就退出了。小李困惑。</p><p>老周解释：</p><blockquote><p>“容器会在主进程结束后自动退出，比如 <code>CMD [&quot;python&quot;, &quot;main.py&quot;]</code> 一旦结束，容器就结束了。”</p></blockquote><hr><h3 id="2-保持后台运行（常用）"><a href="#2-保持后台运行（常用）" class="headerlink" title="2. 保持后台运行（常用）"></a>2. 保持后台运行（常用）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name flask-app -p 5000:5000 my-flask-app</span><br></pre></td></tr></table></figure><p>解释：</p><ul><li><code>-d</code>：detached 模式，后台运行</li><li><code>--name</code>：给容器取个名字，方便管理</li><li><code>-p 宿主端口:容器端口</code>：端口映射，把容器内部 5000 暴露到外部</li></ul><blockquote><p>小李可以在浏览器里访问 <code>http://localhost:5000</code>，服务在跑！</p></blockquote><hr><h3 id="3-设置环境变量"><a href="#3-设置环境变量" class="headerlink" title="3. 设置环境变量"></a>3. 设置环境变量</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d -e ENV=production my-flask-app</span><br></pre></td></tr></table></figure><p>在容器内可通过 <code>os.environ[&#39;ENV&#39;]</code> 访问。</p><hr><h3 id="4-设置自动重启策略（生产强烈建议）"><a href="#4-设置自动重启策略（生产强烈建议）" class="headerlink" title="4. 设置自动重启策略（生产强烈建议）"></a>4. 设置自动重启策略（生产强烈建议）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --restart=always my-flask-app</span><br></pre></td></tr></table></figure><p>可选策略：</p><table><thead><tr><th>策略名</th><th>说明</th></tr></thead><tbody><tr><td><code>no</code></td><td>不自动重启（默认）</td></tr><tr><td><code>on-failure</code></td><td>出错时重启</td></tr><tr><td><code>always</code></td><td>永远重启</td></tr><tr><td><code>unless-stopped</code></td><td>除非人为停止</td></tr></tbody></table><hr><h2 id="🧭-第二节：查看容器状态"><a href="#🧭-第二节：查看容器状态" class="headerlink" title="🧭 第二节：查看容器状态"></a>🧭 第二节：查看容器状态</h2><p>老周说：“运行中的容器就像是火堆，你得学会看它们是否还在烧。”</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">docker ps</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">docker ps -a</span><br></pre></td></tr></table></figure><p>输出示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CONTAINER ID   IMAGE           STATUS         NAMES</span><br><span class="line">f123abc456     my-flask-app    Up 3 minutes   flask-app</span><br></pre></td></tr></table></figure><hr><h2 id="🔄-第三节：容器的停止与重启"><a href="#🔄-第三节：容器的停止与重启" class="headerlink" title="🔄 第三节：容器的停止与重启"></a>🔄 第三节：容器的停止与重启</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></pre></td><td class="code"><pre><span class="line">docker stop flask-app</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">docker start flask-app</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">docker restart flask-app</span><br></pre></td></tr></table></figure><blockquote><p>⚠️ 容器停止后并不会删除，除非显式用 <code>docker rm</code></p></blockquote><hr><h2 id="🧰-第四节：进入容器内“远程调试”"><a href="#🧰-第四节：进入容器内“远程调试”" class="headerlink" title="🧰 第四节：进入容器内“远程调试”"></a>🧰 第四节：进入容器内“远程调试”</h2><p>有一次小李发现容器里缺了个配置文件，他想进去看看。</p><h3 id="使用-exec-进入运行中的容器："><a href="#使用-exec-进入运行中的容器：" class="headerlink" title="使用 exec 进入运行中的容器："></a>使用 exec 进入运行中的容器：</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it flask-app /bin/bash</span><br></pre></td></tr></table></figure><ul><li><code>-i</code>：保持输入</li><li><code>-t</code>：分配终端</li><li><code>/bin/bash</code>：使用 bash shell（Alpine 镜像可能要用 <code>/bin/sh</code>）</li></ul><blockquote><p>现在他能像 SSH 进服务器一样，在容器里操作文件、日志、环境变量。</p></blockquote><hr><h2 id="📜-第五节：查看容器日志"><a href="#📜-第五节：查看容器日志" class="headerlink" title="📜 第五节：查看容器日志"></a>📜 第五节：查看容器日志</h2><p>某天应用崩溃了，小李要调日志。</p><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">docker logs flask-app</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">docker logs --<span class="built_in">tail</span> 100 flask-app</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">docker logs -f flask-app</span><br></pre></td></tr></table></figure><hr><h2 id="🧽-第六节：删除容器"><a href="#🧽-第六节：删除容器" class="headerlink" title="🧽 第六节：删除容器"></a>🧽 第六节：删除容器</h2><p>小李尝试重建容器时，系统提示名字重复。</p><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">docker <span class="built_in">rm</span> flask-app</span><br></pre></td></tr></table></figure><p>如果容器还在运行，先 <code>stop</code> 再 <code>rm</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">docker <span class="built_in">rm</span> -f flask-app</span><br></pre></td></tr></table></figure><hr><h2 id="🔄-第七节：容器生命周期一览表"><a href="#🔄-第七节：容器生命周期一览表" class="headerlink" title="🔄 第七节：容器生命周期一览表"></a>🔄 第七节：容器生命周期一览表</h2><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>创建 + 运行容器</td><td><code>docker run</code></td></tr><tr><td>后台运行</td><td><code>docker run -d</code></td></tr><tr><td>设置名字</td><td><code>docker run --name name</code></td></tr><tr><td>设置端口映射</td><td><code>-p 外:内</code></td></tr><tr><td>查看容器</td><td><code>docker ps [-a]</code></td></tr><tr><td>停止容器</td><td><code>docker stop name</code></td></tr><tr><td>启动容器</td><td><code>docker start name</code></td></tr><tr><td>重启容器</td><td><code>docker restart name</code></td></tr><tr><td>删除容器</td><td><code>docker rm [-f] name</code></td></tr><tr><td>查看日志</td><td><code>docker logs [-f] name</code></td></tr><tr><td>进入容器</td><td><code>docker exec -it name /bin/bash</code></td></tr></tbody></table><hr><h2 id="🧪-小李的实战练习任务"><a href="#🧪-小李的实战练习任务" class="headerlink" title="🧪 小李的实战练习任务"></a>🧪 小李的实战练习任务</h2><p>老周布置了一个练习：</p><blockquote><p>“请你写一个脚本，构建镜像，运行容器，设置环境变量和端口，再用日志确认 Flask 成功启动。”</p></blockquote><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">docker build -t flask-test .</span><br><span class="line">docker run -d --name flask-run -p 8080:5000 -e MODE=debug flask-test</span><br><span class="line">docker logs -f flask-run</span><br><span class="line">docker <span class="built_in">exec</span> -it flask-run /bin/bash</span><br></pre></td></tr></table></figure><hr><h2 id="🧬-衍生补充：容器的状态码解释"><a href="#🧬-衍生补充：容器的状态码解释" class="headerlink" title="🧬 衍生补充：容器的状态码解释"></a>🧬 衍生补充：容器的状态码解释</h2><p>容器退出时，可以用 <code>docker ps -a</code> 查看 <code>STATUS</code> 栏：</p><ul><li><code>Exited (0)</code>：正常退出</li><li><code>Exited (1)</code>：出错退出</li><li><code>Up</code>：正在运行</li></ul><p>调试时很有用。</p><hr><h2 id="🎬-尾声：容器部落里的秩序"><a href="#🎬-尾声：容器部落里的秩序" class="headerlink" title="🎬 尾声：容器部落里的秩序"></a>🎬 尾声：容器部落里的秩序</h2><p>小李现在已经能够用容器完成：</p><ul><li>应用部署</li><li>环境隔离</li><li>日志查看</li><li>重启容错</li><li>在线修复问题</li></ul><p>他看着容器部落中灯火通明的帐篷，知道自己终于从一个“只会打包镜像”的新人，成长为能驾驭容器生命周期的工程师。</p><p>老周拍拍他肩膀说：</p><blockquote><p>“你已经能掌控容器的生死了，下一步——掌控它们的数据。”</p></blockquote><p>好嘞，我们来拓展<strong>第三章《容器部落生活》</strong>，新增三大实战内容：</p><blockquote><p>🚀 Nginx 容器部署<br>🔗 多个容器配合运行（应用 + 数据库）<br>🩺 容器的服务探针（健康检查）</p></blockquote><p>这部分会继续以<strong>故事+实战命令+讲解</strong>的方式展开，帮助你更贴近真实工作中的容器部署流程。</p><hr><h2 id="🧱-实战一：Nginx-容器部署小站点"><a href="#🧱-实战一：Nginx-容器部署小站点" class="headerlink" title="🧱 实战一：Nginx 容器部署小站点"></a>🧱 实战一：Nginx 容器部署小站点</h2><p>小李想搭个静态站点测试，老周给他出了一个练习：</p><blockquote><p>“用 Nginx 容器部署一个 HTML 网站，只通过 Docker 完成。”</p></blockquote><h3 id="1-准备文件结构："><a href="#1-准备文件结构：" class="headerlink" title="1. 准备文件结构："></a>1. 准备文件结构：</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">nginx-site/</span><br><span class="line">├── index.html</span><br><span class="line">└── Dockerfile</span><br></pre></td></tr></table></figure><p><code>index.html</code> 示例：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hello from Nginx<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello Docker + Nginx!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-Dockerfile-内容（基于官方-Nginx）："><a href="#2-Dockerfile-内容（基于官方-Nginx）：" class="headerlink" title="2. Dockerfile 内容（基于官方 Nginx）："></a>2. Dockerfile 内容（基于官方 Nginx）：</h3><figure class="highlight dockerfile"><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="keyword">FROM</span> nginx:alpine</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> index.html /usr/share/nginx/html/index.html</span></span><br></pre></td></tr></table></figure><blockquote><p>✅ 把自己的网页文件覆盖掉默认首页。</p></blockquote><h3 id="3-构建-运行："><a href="#3-构建-运行：" class="headerlink" title="3. 构建 + 运行："></a>3. 构建 + 运行：</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">docker build -t nginx-site .</span><br><span class="line">docker run -d --name my-nginx -p 8080:80 nginx-site</span><br></pre></td></tr></table></figure><p>打开浏览器访问 <code>http://localhost:8080</code>，小李看到页面显示成功，笑得像个孩子。</p><hr><h2 id="🔗-实战二：应用容器-数据库容器组合运行"><a href="#🔗-实战二：应用容器-数据库容器组合运行" class="headerlink" title="🔗 实战二：应用容器 + 数据库容器组合运行"></a>🔗 实战二：应用容器 + 数据库容器组合运行</h2><p>接着老周出了第二个任务：</p><blockquote><p>“把你的 Flask 应用和一个 MySQL 数据库用两个容器跑起来，实现数据连接。”</p></blockquote><h3 id="方法一：手动网络-多容器连接"><a href="#方法一：手动网络-多容器连接" class="headerlink" title="方法一：手动网络 + 多容器连接"></a>方法一：手动网络 + 多容器连接</h3><h4 id="1-创建网络（容器间通信）："><a href="#1-创建网络（容器间通信）：" class="headerlink" title="1. 创建网络（容器间通信）："></a>1. 创建网络（容器间通信）：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network create flask-net</span><br></pre></td></tr></table></figure><h4 id="2-启动-MySQL-容器："><a href="#2-启动-MySQL-容器：" class="headerlink" title="2. 启动 MySQL 容器："></a>2. 启动 MySQL 容器：</h4><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">docker run -d --name my-mysql \</span><br><span class="line">  --network flask-net \</span><br><span class="line">  -e MYSQL_ROOT_PASSWORD=root123 \</span><br><span class="line">  -e MYSQL_DATABASE=mydb \</span><br><span class="line">  mysql:5.7</span><br></pre></td></tr></table></figure><h4 id="3-启动-Flask-应用容器（连接到-MySQL）："><a href="#3-启动-Flask-应用容器（连接到-MySQL）：" class="headerlink" title="3. 启动 Flask 应用容器（连接到 MySQL）："></a>3. 启动 Flask 应用容器（连接到 MySQL）：</h4><p>假设 Flask 连接数据库时使用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">host = <span class="string">&#x27;my-mysql&#x27;</span>  <span class="comment"># 容器名就是主机名</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></pre></td><td class="code"><pre><span class="line">docker run -d --name flask-app \</span><br><span class="line">  --network flask-net \</span><br><span class="line">  -e DB_HOST=my-mysql \</span><br><span class="line">  -p 5000:5000 \</span><br><span class="line">  my-flask-app</span><br></pre></td></tr></table></figure><blockquote><p>🔗 容器间在同一个网络中，通过名字直接通信，像局域网一样！</p></blockquote><hr><h3 id="方法二：使用-docker-compose（下一章会详细讲）"><a href="#方法二：使用-docker-compose（下一章会详细讲）" class="headerlink" title="方法二：使用 docker-compose（下一章会详细讲）"></a>方法二：使用 <code>docker-compose</code>（下一章会详细讲）</h3><hr><h2 id="🩺-实战三：为容器添加“健康探针”"><a href="#🩺-实战三：为容器添加“健康探针”" class="headerlink" title="🩺 实战三：为容器添加“健康探针”"></a>🩺 实战三：为容器添加“健康探针”</h2><p>老周问小李：“如果你的服务挂了，但容器还在运行，你怎么知道？”</p><blockquote><p>小李摇头：这不就是“僵尸容器”吗？</p></blockquote><p>老周笑了：</p><blockquote><p>“那就给它<strong>加一个健康探针（HEALTHCHECK）</strong>，定期检测服务状态。”</p></blockquote><h3 id="为-Flask-容器加健康检查："><a href="#为-Flask-容器加健康检查：" class="headerlink" title="为 Flask 容器加健康检查："></a>为 Flask 容器加健康检查：</h3><p>修改 <code>Dockerfile</code>：</p><figure class="highlight dockerfile"><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="keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="built_in">timeout</span>=5s --start-period=5s --retries=3 \</span></span><br><span class="line"><span class="language-bash">  CMD curl -f http://localhost:5000/health || <span class="built_in">exit</span> 1</span></span><br></pre></td></tr></table></figure><p>在 Flask 代码中添加一个健康检查路由：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/health&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">health</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;OK&quot;</span>, <span class="number">200</span></span><br></pre></td></tr></table></figure><p>构建镜像，运行容器后，通过命令查看健康状态：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker inspect flask-app | grep -i health</span><br></pre></td></tr></table></figure><p>输出示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&quot;Health&quot;: &#123;</span><br><span class="line">  &quot;Status&quot;: &quot;healthy&quot;,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果 <code>/health</code> 接口返回失败状态码，容器会标记为 <code>&quot;unhealthy&quot;</code>，可配合自动重启策略联动修复。</p><hr><h2 id="🧭-实战拓展总结表"><a href="#🧭-实战拓展总结表" class="headerlink" title="🧭 实战拓展总结表"></a>🧭 实战拓展总结表</h2><table><thead><tr><th>场景</th><th>命令 &#x2F; 技术</th></tr></thead><tbody><tr><td>部署静态站点</td><td>Nginx + COPY index.html</td></tr><tr><td>容器间通信</td><td><code>docker network create</code> + <code>--network</code> 参数</td></tr><tr><td>多容器组合</td><td>应用容器连接数据库容器</td></tr><tr><td>服务探针</td><td>Dockerfile 添加 <code>HEALTHCHECK</code>，应用实现 <code>/health</code></td></tr><tr><td>探针状态查看</td><td>&#96;docker inspect 容器名</td></tr></tbody></table><hr><h2 id="🎬-尾声-·-容器集群的微光"><a href="#🎬-尾声-·-容器集群的微光" class="headerlink" title="🎬 尾声 · 容器集群的微光"></a>🎬 尾声 · 容器集群的微光</h2><p>小李站在容器部落的山丘上，看着成百上千个容器像城市灯火一样运转。</p><p>Nginx 做前端代理，Flask 作为后端逻辑，MySQL 管理数据，每个服务都是一块积木，有秩序、有协作。</p><p>老周淡淡说：</p><blockquote><p>“这只是单机的容器调度，真正的战场——在云上。”</p></blockquote><p>小李的眼里闪起光芒：“那我下次要学的就是——Compose，K8s，还有 CI&#x2F;CD，对吧？”</p><p>老周点头：“没错，下一站——数据卷与共享、Compose 编排，再之后……你就要去打云原生的战了。”</p>]]></content>
    
    
    <summary type="html">Docker 入门第三章：容器的创建、运行与管理操作详解</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="容器" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%AE%B9%E5%99%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（三）：一台机器跑三套 Docker？深入解析懒猫容器的共存机制（下）</title>
    <link href="https://blog.no-claw.com/posts/b853f136/"/>
    <id>https://blog.no-claw.com/posts/b853f136/</id>
    <published>2025-05-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在上一期里，我们剖析了懒猫微服原生的三套 Docker 共存方案，这次我们来看看怎么在懒猫微服上启动新的 dev-docker 引擎，既能拓展使用，但又不与现有环境相互污染。</p><blockquote><p><strong>核心思路</strong></p><ol><li>独立 <code>daemon.json</code> 指定专属数据目录 &#x2F; Socket</li><li>一个包装脚本 <code>dev-docker</code> 让你照常敲 <code>docker</code> 命令</li><li>需要时随时启用，不用时一条命令即卸载</li></ol></blockquote><h3 id="目录规划"><a href="#目录规划" class="headerlink" title="目录规划"></a>目录规划</h3><p>我目前是在 root 目录下新建了一个 dev 目录，新的容器所有数据都在这个目录下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── dev/</span><br><span class="line">│   ├── data/      # 镜像层、容器元数据</span><br><span class="line">│   ├── exec/      # 运行时文件</span><br><span class="line">│   └── daemon.json</span><br><span class="line">└── dev-docker      # 包装脚本，照样敲 `docker`</span><br></pre></td></tr></table></figure><span id="more"></span><blockquote><p><strong>提示</strong>：<code>docker.sock</code>、<code>docker.pid</code> 会在启动时自动生成到 <code>dev/</code> 里。</p></blockquote><hr><h3 id="生成-daemon-json"><a href="#生成-daemon-json" class="headerlink" title="生成 daemon.json"></a>生成 <code>daemon.json</code></h3><p>这个是主要的文件，定义了 dev-docker 的数据目录，以及命名空间的隔离。</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"><span class="comment"># 先拿到绝对路径，避免 dockerd 报相对路径错误</span></span><br><span class="line">DDIR=$(<span class="built_in">realpath</span> ./dev)</span><br><span class="line"></span><br><span class="line"><span class="built_in">cat</span> &gt; <span class="variable">$DDIR</span>/daemon.json &lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">&#123;</span></span><br><span class="line"><span class="string">  &quot;data-root&quot;: &quot;$DDIR/data&quot;,</span></span><br><span class="line"><span class="string">  &quot;exec-root&quot;: &quot;$DDIR/exec&quot;,</span></span><br><span class="line"><span class="string">  &quot;pidfile&quot;: &quot;$DDIR/docker.pid&quot;,</span></span><br><span class="line"><span class="string">  &quot;hosts&quot;: [&quot;unix://$DDIR/docker.sock&quot;],</span></span><br><span class="line"><span class="string">  &quot;containerd-namespace&quot;: &quot;dev-docker&quot;,</span></span><br><span class="line"><span class="string">  &quot;containerd-plugins-namespace&quot;: &quot;dev-docker&quot;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><h3 id="启动-dev-docker-引擎"><a href="#启动-dev-docker-引擎" class="headerlink" title="启动 dev-docker 引擎"></a>启动 dev-docker 引擎</h3><p>使用 dockerd 指定配置文件启动 dev-docker，然后放在后台进行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dockerd --config-file=<span class="variable">$DDIR</span>/daemon.json --log-level=info &amp;</span><br></pre></td></tr></table></figure><p>执行之后得到如下的结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">INFO[2025-05-20T12:55:02.072949048Z] detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: /run/systemd/resolve/resolv.conf</span><br><span class="line">INFO[2025-05-20T12:55:02.157745008Z] Loading containers: start.</span><br><span class="line">INFO[2025-05-20T12:55:02.331021502Z] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address</span><br><span class="line">INFO[2025-05-20T12:55:02.394567874Z] Loading containers: done.</span><br><span class="line">INFO[2025-05-20T12:55:02.413944743Z] Docker daemon                                 commit=&quot;26.1.3-0ubuntu1~24.04.1&quot; containerd-snapshotter=false storage-driver=overlay2 version=26.1.3</span><br><span class="line">INFO[2025-05-20T12:55:02.414179613Z] Daemon has completed initialization</span><br><span class="line">INFO[2025-05-20T12:55:02.471933824Z] API listen on /home/ubuntu/ddd/dev/docker.sock</span><br></pre></td></tr></table></figure><hr><h3 id="一个-dev-docker-包装脚本"><a href="#一个-dev-docker-包装脚本" class="headerlink" title="一个 dev-docker 包装脚本"></a>一个 <code>dev-docker</code> 包装脚本</h3><p>这个脚本就是仿照懒猫微幅其他的 docker 实现：</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"><span class="built_in">cat</span> &gt; ./dev-docker &lt;&lt;<span class="string">&#x27;EOF&#x27;</span></span><br><span class="line"><span class="comment">#!/usr/bin/env bash</span></span><br><span class="line"><span class="built_in">export</span> DOCKER_HOST=unix://$(<span class="built_in">realpath</span> ./dev/docker.sock)</span><br><span class="line"><span class="built_in">exec</span> docker <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line"><span class="built_in">chmod</span> +x ./dev-docker</span><br></pre></td></tr></table></figure><p>设定<code>DOCKER_HOST=unix://$(realpath ./dev/docker.sock)</code>，然后用 <code>exec docker &quot;$@&quot;</code> 把收到的全部参数原封不动交给真实的 <code>docker</code> 命令执行。</p><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">./dev-docker ps</span><br><span class="line">./dev-docker run -d -p 8080:80 nginx</span><br></pre></td></tr></table></figure><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2025/05/21/1747794620218-4fbf858f-b94b-4648-983e-a04964b4ebae.png"></p><p>系统自带的 <code>docker</code> 仍在 <code>/var/run/docker.sock</code> 上工作，互不打扰。</p><hr><h3 id="将-dev-docker-放入全局-PATH"><a href="#将-dev-docker-放入全局-PATH" class="headerlink" title="将 dev-docker 放入全局 PATH"></a>将 <code>dev-docker</code> 放入全局 PATH</h3><p>如果想全局生效，运行下方命令。但注意：懒猫微服重启后 <code>/usr/local/bin</code> 会被还原。”</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> install -m 755 ./dev-docker /usr/local/bin/</span><br></pre></td></tr></table></figure><h3 id="一键化脚本"><a href="#一键化脚本" class="headerlink" title="一键化脚本"></a>一键化脚本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="comment"># init-docker-dev.sh</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"><span class="built_in">mkdir</span> dev</span><br><span class="line">BASE=$(<span class="built_in">realpath</span> <span class="string">&quot;./dev&quot;</span>)</span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="string">&quot;<span class="variable">$BASE</span>&quot;</span>/&#123;data,<span class="built_in">exec</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">cat</span> &gt; <span class="string">&quot;<span class="variable">$BASE</span>/daemon.json&quot;</span> &lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">&#123;</span></span><br><span class="line"><span class="string">  &quot;data-root&quot;: &quot;$BASE/data&quot;,</span></span><br><span class="line"><span class="string">  &quot;exec-root&quot;: &quot;$BASE/exec&quot;,</span></span><br><span class="line"><span class="string">  &quot;pidfile&quot;: &quot;$BASE/docker.pid&quot;,</span></span><br><span class="line"><span class="string">  &quot;hosts&quot;: [&quot;unix://$BASE/docker.sock&quot;],</span></span><br><span class="line"><span class="string">  &quot;containerd-namespace&quot;: &quot;dev-docker&quot;,</span></span><br><span class="line"><span class="string">  &quot;containerd-plugins-namespace&quot;: &quot;dev-docker&quot;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line">dockerd --config-file=<span class="string">&quot;<span class="variable">$BASE</span>/daemon.json&quot;</span> --log-level=info &amp;</span><br><span class="line"></span><br><span class="line"><span class="built_in">cat</span> &gt; <span class="string">&quot;./dev-docker&quot;</span> &lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">#!/usr/bin/env bash</span></span><br><span class="line"><span class="string">export DOCKER_HOST=unix://$BASE/docker.sock</span></span><br><span class="line"><span class="string">exec docker &quot;\$@&quot;</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"><span class="built_in">chmod</span> +x ./dev-docker</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;🎉 Dev Docker 已就绪，使用 ./dev-docker 访问！&quot;</span></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></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x init-docker-dev.sh   <span class="comment"># 赋可执行权限（若脚本是下载的）</span></span><br><span class="line">./init-docker-dev.sh</span><br></pre></td></tr></table></figure><p>运行完脚本后，后续就在当前目录直接敲 <code>./dev-docker &lt;command&gt;</code> 即可；<br>如果之前已将 <code>dev-docker</code> 安装到 PATH，全局也可以直接 <code>dev-docker ps</code></p><p>脚本执行记录如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">🎉 Dev Docker 已就绪，使用 ./dev-docker 访问！</span><br><span class="line">ubuntu@ip-172-31-29-78:~$ INFO[2025-05-20T12:55:02.071795870Z] Starting up</span><br><span class="line">INFO[2025-05-20T12:55:02.072949048Z] detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: /run/systemd/resolve/resolv.conf</span><br><span class="line">INFO[2025-05-20T12:55:02.157745008Z] Loading containers: start.</span><br><span class="line">INFO[2025-05-20T12:55:02.331021502Z] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address</span><br><span class="line">INFO[2025-05-20T12:55:02.394567874Z] Loading containers: done.</span><br><span class="line">INFO[2025-05-20T12:55:02.413944743Z] Docker daemon                                 commit=&quot;26.1.3-0ubuntu1~24.04.1&quot; containerd-snapshotter=false storage-driver=overlay2 version=26.1.3</span><br><span class="line">INFO[2025-05-20T12:55:02.414179613Z] Daemon has completed initialization</span><br><span class="line">INFO[2025-05-20T12:55:02.471933824Z] API listen on /home/ubuntu/ddd/dev/docker.sock</span><br></pre></td></tr></table></figure><p><strong>sudo .&#x2F;dev-docker info</strong>查看信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">sudo ./dev-docker info</span><br><span class="line"></span><br><span class="line">Client:</span><br><span class="line"> Version:    26.1.3</span><br><span class="line"> Context:    default</span><br><span class="line"> Debug Mode: false</span><br><span class="line"></span><br><span class="line">Server:</span><br><span class="line"> Containers: 0</span><br><span class="line">  Running: 0</span><br><span class="line">  Paused: 0</span><br><span class="line">  Stopped: 0</span><br><span class="line"> Images: 0</span><br><span class="line"> Server Version: 26.1.3</span><br><span class="line"> Storage Driver: overlay2</span><br><span class="line">  Backing Filesystem: extfs</span><br><span class="line">  Supports d_type: true</span><br><span class="line">  Using metacopy: false</span><br><span class="line">  Native Overlay Diff: true</span><br><span class="line">  userxattr: false</span><br><span class="line"> Logging Driver: json-file</span><br><span class="line"> Cgroup Driver: systemd</span><br><span class="line"> Cgroup Version: 2</span><br><span class="line"> Plugins:</span><br><span class="line">  Volume: local</span><br><span class="line">  Network: bridge host ipvlan macvlan null overlay</span><br><span class="line">  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog</span><br><span class="line"> Swarm: inactive</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br><span class="line"> Init Binary: docker-init</span><br><span class="line"> containerd version:</span><br><span class="line"> runc version:</span><br><span class="line"> init version:</span><br><span class="line"> Security Options:</span><br><span class="line">  apparmor</span><br><span class="line">  seccomp</span><br><span class="line">   Profile: builtin</span><br><span class="line">  cgroupns</span><br><span class="line"> Kernel Version: 6.8.0-1024-aws</span><br><span class="line"> Operating System: Ubuntu 24.04.2 LTS</span><br><span class="line"> OSType: linux</span><br><span class="line"> Architecture: aarch64</span><br><span class="line"> CPUs: 2</span><br><span class="line"> Total Memory: 1.8GiB</span><br><span class="line"> Name: ip-172-31-29-78</span><br><span class="line"> ID: b6f661de-2099-4b23-aff8-1a55e35833d9</span><br><span class="line"> Docker Root Dir: /home/ubuntu/ddd/dev/data</span><br><span class="line"> Debug Mode: false</span><br><span class="line"> Experimental: false</span><br><span class="line"> Insecure Registries:</span><br><span class="line">  127.0.0.0/8</span><br><span class="line"> Live Restore Enabled: false</span><br></pre></td></tr></table></figure><p><strong>.&#x2F;dev-docker pull ubuntu</strong> 下载 images：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@ip-172-31-29-78:~$ sudo ./dev-docker pull ubuntu</span><br><span class="line">Using default tag: latest</span><br><span class="line">latest: Pulling from library/ubuntu</span><br><span class="line">2f074dc76c5d: Pull complete</span><br><span class="line">Digest: sha256:6015f66923d7afbc53558d7ccffd325d43b4e249f41a6e93eef074c9505d2233</span><br><span class="line">Status: Downloaded newer image for ubuntu:latest</span><br><span class="line">docker.io/library/ubuntu:latest</span><br></pre></td></tr></table></figure><p><strong>检查 docker 版本：</strong></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">ubuntu@ip-172-31-29-78:~$ dev-docker  --version</span><br><span class="line">Docker version 26.1.3, build 26.1.3-0ubuntu1~24.04.1</span><br><span class="line">ubuntu@ip-172-31-29-78:~$ docker --version</span><br><span class="line">Docker version 26.1.3, build 26.1.3-0ubuntu1~24.04.1</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="不使用的时候如何卸载？"><a href="#不使用的时候如何卸载？" class="headerlink" title="不使用的时候如何卸载？"></a>不使用的时候如何卸载？</h3><h5 id="办法-1-ps-aux-grep-dockerd-查看-docker-进程的-PID-号，然后删除"><a href="#办法-1-ps-aux-grep-dockerd-查看-docker-进程的-PID-号，然后删除" class="headerlink" title="办法 1: ps aux | grep dockerd 查看 docker 进程的 PID 号，然后删除"></a>办法 1: ps aux | grep dockerd 查看 docker 进程的 PID 号，然后删除</h5><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">ps aux | grep dockerd</span><br><span class="line">root         470  0.8  0.3 2653088 100248 ?      Ssl  07:42   0:11 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock</span><br><span class="line">root        2226  6.6  0.6 7246472 227108 ?      Ssl  07:42   1:37 /usr/bin/dockerd --config-file /lzcsys/etc/docker/daemon.json</span><br><span class="line">root       27520  0.0  0.2 2874220 90788 ?       Ssl  07:46   0:00 /usr/bin/dockerd --config-file /lzcsys/var/playground/daemon.json</span><br><span class="line">root      127241  0.5  0.2 2636632 92720 pts/1   Sl   07:52   0:04 dockerd --config-file=/root/dev/daemon.json --log-level=info</span><br><span class="line">root      405552  0.0  0.0   3748  2048 pts/1    S+   08:06   0:00 grep --colour=auto dockerd</span><br><span class="line">---</span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># kill -15 127241</span></span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># INFO[2025-05-21T08:10:58.184799932+08:00] Processing signal &#x27;terminated&#x27;</span></span><br><span class="line">INFO[2025-05-21T08:10:58.198235413+08:00] stopping event stream following graceful shutdown  error=<span class="string">&quot;&lt;nil&gt;&quot;</span> module=libcontainerd namespace=dev-docker</span><br><span class="line">INFO[2025-05-21T08:10:58.203590577+08:00] Daemon shutdown complete</span><br><span class="line">---</span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># ps aux | grep dockerd</span></span><br><span class="line">root         470  0.7  0.3 2653088 100212 ?      Ssl  07:42   0:13 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock</span><br><span class="line">root        2226  7.8  0.7 7246472 233080 ?      Ssl  07:42   2:18 /usr/bin/dockerd --config-file /lzcsys/etc/docker/daemon.json</span><br><span class="line">root       27520  0.0  0.2 2874220 92644 ?       Ssl  07:46   0:00 /usr/bin/dockerd --config-file /lzcsys/var/playground/daemon.json</span><br><span class="line">root      568622  0.0  0.0   3748  2048 pts/1    S+   08:11   0:00 grep --colour=auto dockerd</span><br></pre></td></tr></table></figure><h5 id="办法-2-pkill-f-‘-dev-daemon-json’-指定文件删除："><a href="#办法-2-pkill-f-‘-dev-daemon-json’-指定文件删除：" class="headerlink" title="办法 2: pkill -f ‘.&#x2F;dev&#x2F;daemon.json’ 指定文件删除："></a>办法 2: pkill -f ‘.&#x2F;dev&#x2F;daemon.json’ 指定文件删除：</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">pkill -f <span class="string">&#x27;./dev/daemon.json&#x27;</span></span><br><span class="line">INFO[2025-05-21T08:14:06.721816466+08:00] Processing signal <span class="string">&#x27;terminated&#x27;</span></span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># INFO[2025-05-21T08:14:06.728822927+08:00] stopping event stream following graceful shutdown  error=&quot;&lt;nil&gt;&quot; module=libcontainerd namespace=dev-docker</span></span><br><span class="line">INFO[2025-05-21T08:14:06.734923834+08:00] Daemon shutdown complete</span><br><span class="line"></span><br><span class="line">[1]+  Done                    dockerd --config-file=<span class="string">&quot;./dev/daemon.json&quot;</span> --log-level=info</span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># ps aux | grep dockerd</span></span><br><span class="line">root         470  0.7  0.3 2653088 100340 ?      Ssl  07:42   0:14 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock</span><br><span class="line">root        2226  8.3  0.7 7246472 234356 ?      Ssl  07:42   2:41 /usr/bin/dockerd --config-file /lzcsys/etc/docker/daemon.json</span><br><span class="line">root       27520  0.0  0.2 2874220 92500 ?       Ssl  07:46   0:01 /usr/bin/dockerd --config-file /lzcsys/var/playground/daemon.json</span><br><span class="line">root      663902  0.0  0.0   3748  1792 pts/1    S+   08:14   0:00 grep --colour=auto dockerd</span><br></pre></td></tr></table></figure><p><strong>清除数据</strong></p><figure class="highlight plaintext"><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">rm -rf ./dev                   # 删数据目录</span><br><span class="line">sudo rm -f /usr/local/bin/dev-docker   # 若装过 PATH</span><br></pre></td></tr></table></figure><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>再多一套 Docker，不是为了炫技，而是给开发或者测试环境一个“随时可重置、天然隔离、低成本回收”的保险箱。学会这一招，你就能在懒猫微服乃至任何 Linux 服务器上，放心大胆地尝鲜新内核、新 runtime，甚至复刻生产 bug —— 然后一句 <code>pkill</code> + <code>rm -rf dev/</code>，世界瞬间清爽如初。祝玩得尽兴！</p>]]></content>
    
    
    <summary type="html">深入解析懒猫微服三套 Docker 引擎共存机制（下篇），容器网络与存储隔离。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>写给懒猫微服玩家的容器小书 Docker篇（二）：《镜像旅馆的秘密》</title>
    <link href="https://blog.no-claw.com/posts/e0565a55/"/>
    <id>https://blog.no-claw.com/posts/e0565a55/</id>
    <published>2025-05-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一直想写一本容器小书，真好懒猫基本都做了容器化，所以把这部分分享出来。不同的是，懒猫微服中使用 pg-docker 来替代 docker 命令，使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主，这样子既学会了 docker 知识，也能够在懒猫微服上启动 Docker 服务。</p></blockquote><h1 id="《镜像旅馆的秘密》讲的是-Docker-镜像的原理、分层结构、生命周期、Docker-Hub-上传与下载、常见镜像命令详解"><a href="#《镜像旅馆的秘密》讲的是-Docker-镜像的原理、分层结构、生命周期、Docker-Hub-上传与下载、常见镜像命令详解" class="headerlink" title="《镜像旅馆的秘密》讲的是 Docker 镜像的原理、分层结构、生命周期、Docker Hub 上传与下载、常见镜像命令详解"></a>《镜像旅馆的秘密》讲的是 Docker 镜像的原理、分层结构、生命周期、Docker Hub 上传与下载、常见镜像命令详解</h1><h3 id="🏰-开篇：进入镜像旅馆"><a href="#🏰-开篇：进入镜像旅馆" class="headerlink" title="🏰 开篇：进入镜像旅馆"></a>🏰 开篇：进入镜像旅馆</h3><p>自从小李用 Docker 成功打包并运行了自己的 Flask 项目，他的开发效率飞快提高。</p><p>某天，老周带他来到一座巨大的数字建筑——<strong>Docker 镜像旅馆</strong>。</p><p>“这是你所有镜像的家，”老周说，“也是全球程序员共享旅程资源的中转站。”</p><p>镜像旅馆里，层层叠叠地存放着成千上万个镜像，就像一栋模块化的高楼大厦。</p><hr><h3 id="🧱-镜像的本质：一层一层搭起来的文件系统"><a href="#🧱-镜像的本质：一层一层搭起来的文件系统" class="headerlink" title="🧱 镜像的本质：一层一层搭起来的文件系统"></a>🧱 镜像的本质：一层一层搭起来的文件系统</h3><p>老周告诉小李：</p><blockquote><p>“镜像（Image）其实是一个<strong>只读的分层文件系统</strong>。你写的每一条 Dockerfile 指令，都会构成一层 Layer。”</p></blockquote><p>比如这个简单的 Dockerfile：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">FROM</span> python:<span class="number">3.11</span>-slim</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . /app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip install -r requirements.txt</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;main.py&quot;</span>]</span></span><br></pre></td></tr></table></figure><span id="more"></span><p>对应的镜像层如下：</p><ol><li><code>FROM</code> → 拉了一个基础镜像层（Python 3.11）</li><li><code>WORKDIR</code> → 添加一个设置工作目录的 Layer</li><li><code>COPY</code> → 拷贝代码文件的 Layer</li><li><code>RUN</code> → 安装依赖的新 Layer</li><li><code>CMD</code> → 容器入口（不是 Layer，但存配置）</li></ol><blockquote><p>💡 小知识：Docker 会尽量缓存和复用前面的 Layer，节省时间和存储。</p></blockquote><hr><h3 id="🧪-镜像命令全攻略"><a href="#🧪-镜像命令全攻略" class="headerlink" title="🧪 镜像命令全攻略"></a>🧪 镜像命令全攻略</h3><p>小李打开终端，开始探索这些镜像的日常操作。</p><h4 id="1-查看本地镜像："><a href="#1-查看本地镜像：" class="headerlink" title="1. 查看本地镜像："></a>1. 查看本地镜像：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker images</span><br></pre></td></tr></table></figure><p>输出示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">REPOSITORY     TAG        IMAGE ID       CREATED          SIZE</span><br><span class="line">my-flask-app   latest     123abc456def   2 minutes ago    125MB</span><br><span class="line">python         3.11-slim  789xyz654hij   3 days ago       40MB</span><br></pre></td></tr></table></figure><p>解释：</p><ul><li><code>REPOSITORY</code>：镜像名</li><li><code>TAG</code>：标签（版本号）</li><li><code>IMAGE ID</code>：镜像唯一标识符</li><li><code>SIZE</code>：镜像大小</li></ul><h4 id="2-查看镜像历史构建过程（看每层）："><a href="#2-查看镜像历史构建过程（看每层）：" class="headerlink" title="2. 查看镜像历史构建过程（看每层）："></a>2. 查看镜像历史构建过程（看每层）：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">history</span> my-flask-app</span><br></pre></td></tr></table></figure><h4 id="3-删除镜像："><a href="#3-删除镜像：" class="headerlink" title="3. 删除镜像："></a>3. 删除镜像：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker rmi my-flask-app</span><br></pre></td></tr></table></figure><p>（⚠️ 若有容器在运行该镜像，需先停止并删除容器）</p><hr><h3 id="🗂-镜像仓库：Docker-Hub"><a href="#🗂-镜像仓库：Docker-Hub" class="headerlink" title="🗂 镜像仓库：Docker Hub"></a>🗂 镜像仓库：Docker Hub</h3><p>老周指了指旅馆大堂里的一个巨大电梯：</p><blockquote><p>“这是 Docker Hub，全球最大的镜像共享仓库。”</p></blockquote><p>在这里，小李能下载成千上万的开源镜像，也能上传自己的。</p><h4 id="登录-Docker-Hub："><a href="#登录-Docker-Hub：" class="headerlink" title="登录 Docker Hub："></a>登录 Docker Hub：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker login</span><br></pre></td></tr></table></figure><p>（需要先注册账号）</p><h4 id="下载镜像："><a href="#下载镜像：" class="headerlink" title="下载镜像："></a>下载镜像：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull nginx</span><br></pre></td></tr></table></figure><p>这会从 Docker Hub 拉取最新版本的 <code>nginx</code> 镜像</p><h4 id="指定版本拉取："><a href="#指定版本拉取：" class="headerlink" title="指定版本拉取："></a>指定版本拉取：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull redis:6.2</span><br></pre></td></tr></table></figure><blockquote><p>如果 docker run&#x2F;pull 有问题，那么可以通过<code>lzc-cli appstore copy-image your-images</code>来使用懒猫的镜像仓库。</p></blockquote><p>（相当于拉取 <code>redis</code> 仓库中 tag 为 <code>6.2</code> 的镜像）</p><h4 id="上传镜像（先打标签）："><a href="#上传镜像（先打标签）：" class="headerlink" title="上传镜像（先打标签）："></a>上传镜像（先打标签）：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker tag my-flask-app yourdockerhubname/my-flask-app:1.0</span><br><span class="line">docker push yourdockerhubname/my-flask-app:1.0</span><br></pre></td></tr></table></figure><hr><h3 id="📦-镜像-Tag-与版本控制"><a href="#📦-镜像-Tag-与版本控制" class="headerlink" title="📦 镜像 Tag 与版本控制"></a>📦 镜像 Tag 与版本控制</h3><p>老周问：“小李，你知道为什么镜像都有个 <code>:latest</code> 吗？”</p><p>小李说：“这是默认版本号吧？”</p><p>“对，但我们不能依赖它。<strong>开发、测试、生产应使用明确版本号，比如 1.0、20240321 等</strong>。”</p><p>Docker 镜像是通过 <code>tag</code> 来区分版本的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build -t myapp:1.0 .</span><br><span class="line">docker build -t myapp:latest .</span><br></pre></td></tr></table></figure><p>你可以为同一个镜像打多个标签，对应不同场景使用。</p><hr><h3 id="🔍-镜像体积优化技巧"><a href="#🔍-镜像体积优化技巧" class="headerlink" title="🔍 镜像体积优化技巧"></a>🔍 镜像体积优化技巧</h3><p>小李注意到镜像越来越大了，占了很多硬盘空间。</p><p>老周给了他几点建议：</p><ol><li><p>使用轻量级基础镜像：</p><ul><li>比如 <code>python:3.11-slim</code> 代替 <code>python:3.11</code></li></ul></li><li><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">RUN</span><br></pre></td></tr></table></figure><p>命令，减少层数：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt update &amp;&amp; apt install -y git &amp;&amp; <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br></pre></td></tr></table></figure></li><li><p>删除临时文件：</p><ul><li>安装后清理缓存，避免垃圾文件残留</li></ul></li><li><p>多阶段构建（进阶）：</p><ul><li>构建和运行使用不同的镜像阶段</li></ul></li></ol><hr><h3 id="📂-镜像保存与迁移"><a href="#📂-镜像保存与迁移" class="headerlink" title="📂 镜像保存与迁移"></a>📂 镜像保存与迁移</h3><p>后来小李想把自己的镜像传给另一位没有 Docker Hub 的同事。</p><p>他用到了镜像导出与导入：</p><h4 id="导出镜像为-tar-文件："><a href="#导出镜像为-tar-文件：" class="headerlink" title="导出镜像为 .tar 文件："></a>导出镜像为 <code>.tar</code> 文件：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker save my-flask-app &gt; myapp.tar</span><br></pre></td></tr></table></figure><h4 id="导入镜像："><a href="#导入镜像：" class="headerlink" title="导入镜像："></a>导入镜像：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker load &lt; myapp.tar</span><br></pre></td></tr></table></figure><p>镜像打包成离线文件，便于携带与备份。</p><hr><h3 id="🔍-深入-Layer-实战：查看镜像内容"><a href="#🔍-深入-Layer-实战：查看镜像内容" class="headerlink" title="🔍 深入 Layer 实战：查看镜像内容"></a>🔍 深入 Layer 实战：查看镜像内容</h3><p>小李很好奇，镜像到底长什么样？</p><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">docker run -it --<span class="built_in">rm</span> my-flask-app /bin/bash</span><br></pre></td></tr></table></figure><p>这样他就能直接进入容器的 Linux 环境，像在服务器上一样查看文件结构：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> /</span><br><span class="line"><span class="built_in">cd</span> /app</span><br><span class="line"><span class="built_in">cat</span> requirements.txt</span><br></pre></td></tr></table></figure><p>他终于明白，每个镜像就像是一个“静态快照”，而容器才是“它的动态运行副本”。</p><hr><h3 id="📊-镜像生命周期总结表"><a href="#📊-镜像生命周期总结表" class="headerlink" title="📊 镜像生命周期总结表"></a>📊 镜像生命周期总结表</h3><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>查看本地镜像</td><td><code>docker images</code></td></tr><tr><td>构建新镜像</td><td><code>docker build -t name .</code></td></tr><tr><td>删除镜像</td><td><code>docker rmi 镜像名</code></td></tr><tr><td>下载镜像</td><td><code>docker pull 镜像名[:tag]</code></td></tr><tr><td>上传镜像</td><td><code>docker push 镜像名[:tag]</code></td></tr><tr><td>镜像打包导出</td><td><code>docker save &gt; xxx.tar</code></td></tr><tr><td>镜像导入还原</td><td><code>docker load &lt; xxx.tar</code></td></tr><tr><td>镜像历史层查看</td><td><code>docker history 镜像名</code></td></tr></tbody></table><hr><h3 id="🎬-尾声：镜像旅馆的门票"><a href="#🎬-尾声：镜像旅馆的门票" class="headerlink" title="🎬 尾声：镜像旅馆的门票"></a>🎬 尾声：镜像旅馆的门票</h3><p>小李现在拥有了多个镜像，搭配不同的版本、依赖、语言，像积木一样可以快速组合各种环境。</p><p>“这就像 Minecraft 的世界地图，每张都是一个镜像。”小李说。</p><p>老周点点头：“没错，镜像只是开始，真正的冒险——是容器运行起来后的世界。”</p><hr><h2 id="🧭-第二章小结"><a href="#🧭-第二章小结" class="headerlink" title="🧭 第二章小结"></a>🧭 第二章小结</h2><ul><li>镜像是构建环境的基础模板，支持版本控制、缓存加速、快速构建</li><li>可以上传到 Docker Hub 或导出 <code>.tar</code> 进行离线传输</li><li>管理命令要熟练掌握：<code>build</code>、<code>pull</code>、<code>push</code>、<code>rmi</code>、<code>tag</code>、<code>history</code></li><li>优化镜像大小要用 slim 基础镜像、合并命令、清理缓存</li></ul><h2 id="✨-增补内容：镜像的高级技能与实战应用"><a href="#✨-增补内容：镜像的高级技能与实战应用" class="headerlink" title="✨ 增补内容：镜像的高级技能与实战应用"></a>✨ 增补内容：镜像的高级技能与实战应用</h2><hr><h3 id="🧪-多阶段构建：精致分工，极限瘦身"><a href="#🧪-多阶段构建：精致分工，极限瘦身" class="headerlink" title="🧪 多阶段构建：精致分工，极限瘦身"></a>🧪 多阶段构建：精致分工，极限瘦身</h3><p>有一次，小李需要构建一个使用 <code>npm</code> 打包前端、Python 启动后端的项目。打包工具很多、依赖也重，他担心镜像太大。</p><p>老周说：“你要学会<strong>多阶段构建（multi-stage build）</strong>，把构建阶段和运行阶段分开。”</p><blockquote><p>多阶段构建的目标是：<strong>编译用谁都行，最终镜像要最小。</strong></p></blockquote><h4 id="示例：Node-构建-nginx-托管"><a href="#示例：Node-构建-nginx-托管" class="headerlink" title="示例：Node 构建 + nginx 托管"></a>示例：Node 构建 + nginx 托管</h4><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 第一阶段：使用 node 构建前端</span></span><br><span class="line"><span class="keyword">FROM</span> node:<span class="number">18</span> AS builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm install &amp;&amp; npm run build</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二阶段：用 nginx 托管打包后的静态文件</span></span><br><span class="line"><span class="keyword">FROM</span> nginx:alpine</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /app/dist /usr/share/nginx/html</span></span><br></pre></td></tr></table></figure><ul><li>第一阶段装依赖、打包代码</li><li>第二阶段只取编译结果，<strong>不用带上 node&#x2F;npm 等工具</strong></li></ul><p>小李一测试，镜像体积从 300MB 降到 25MB，部署速度快了 10 倍！</p><hr><h3 id="🧩-使用-dockerignore：镜像防垃圾机制"><a href="#🧩-使用-dockerignore：镜像防垃圾机制" class="headerlink" title="🧩 使用 .dockerignore：镜像防垃圾机制"></a>🧩 使用 <code>.dockerignore</code>：镜像防垃圾机制</h3><p>构建时，小李发现镜像中夹杂了 <code>.git</code>、<code>node_modules</code>、<code>__pycache__</code>……</p><p>老周摇头道：“你忘了 <code>.dockerignore</code> 文件。”</p><p>就像 <code>.gitignore</code> 一样，<code>.dockerignore</code> 告诉 Docker 哪些文件在构建镜像时要排除。</p><h4 id="示例："><a href="#示例：" class="headerlink" title="示例："></a>示例：</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">__pycache__/</span><br><span class="line">.git/</span><br><span class="line">node_modules/</span><br><span class="line">.env</span><br><span class="line">*.log</span><br></pre></td></tr></table></figure><p>这个文件放在 Dockerfile 同目录下，<strong>能显著加快构建速度和减小镜像大小</strong>。</p><hr><h3 id="📦-自建私有镜像仓库（Registry）"><a href="#📦-自建私有镜像仓库（Registry）" class="headerlink" title="📦 自建私有镜像仓库（Registry）"></a>📦 自建私有镜像仓库（Registry）</h3><p>当公司禁止使用 Docker Hub 时，小李开始尝试搭建自己的镜像库。</p><p>老周带他部署了一个本地私有镜像仓库（基于 Docker 官方镜像）：</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">docker run -d -p 5000:5000 --restart=always --name registry registry:2</span><br></pre></td></tr></table></figure><p>现在他可以：</p><ul><li><p>推送到私库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker tag myapp localhost:5000/myapp</span><br><span class="line">docker push localhost:5000/myapp</span><br></pre></td></tr></table></figure></li><li><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">docker pull localhost:5000/myapp</span><br></pre></td></tr></table></figure></li></ul><p>适合公司内部使用，搭配 Nexus、Harbor 可实现更完善的权限、审计、镜像管理等功能。（比如懒猫的 copy-image）</p><h3 id="🧠-镜像调试技巧：如何从镜像中探查问题？"><a href="#🧠-镜像调试技巧：如何从镜像中探查问题？" class="headerlink" title="🧠 镜像调试技巧：如何从镜像中探查问题？"></a>🧠 镜像调试技巧：如何从镜像中探查问题？</h3><p>如果小李的镜像出错了，他可以通过两种方式“探测”镜像内部：</p><h4 id="方法-1：运行一个交互式-shell-容器"><a href="#方法-1：运行一个交互式-shell-容器" class="headerlink" title="方法 1：运行一个交互式 shell 容器"></a>方法 1：运行一个交互式 shell 容器</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -it myapp /bin/bash</span><br></pre></td></tr></table></figure><p>（如果 bash 不存在，可以用 <code>/bin/sh</code>）</p><h4 id="方法-2：打开已有容器的终端"><a href="#方法-2：打开已有容器的终端" class="headerlink" title="方法 2：打开已有容器的终端"></a>方法 2：打开已有容器的终端</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it container_id /bin/bash</span><br></pre></td></tr></table></figure><p>通过 <code>ls</code>、<code>cat</code>、<code>which</code>、<code>env</code> 命令，可以检查：</p><ul><li>文件有没有 COPY 进去？</li><li><code>pip install</code> 是否安装成功？</li><li>环境变量是否丢失？</li></ul><hr><h3 id="🔐-镜像安全：不要把密码打包进镜像！"><a href="#🔐-镜像安全：不要把密码打包进镜像！" class="headerlink" title="🔐 镜像安全：不要把密码打包进镜像！"></a>🔐 镜像安全：不要把密码打包进镜像！</h3><p>小李曾在 Dockerfile 里写了：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ENV</span> DB_PASSWORD=<span class="number">123456</span></span><br></pre></td></tr></table></figure><p>老周当场拍桌：“你这是把钥匙写死进容器了！”</p><p>最佳做法：</p><ul><li>在容器运行时注入环境变量（例如使用 <code>.env</code> 文件 + <code>--env</code> 参数）</li><li>使用 <code>docker secret</code> 或 KMS 管理</li><li>使用 BuildKit 的 <code>--secret</code> 机制加密构建时参数（高级用法）</li></ul><hr><h3 id="🧾-镜像标签管理规范建议"><a href="#🧾-镜像标签管理规范建议" class="headerlink" title="🧾 镜像标签管理规范建议"></a>🧾 镜像标签管理规范建议</h3><p>小李准备上线，他开始给镜像打各种 tag：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build -t myapp:1.0.0 .</span><br><span class="line">docker tag myapp:1.0.0 myapp:latest</span><br></pre></td></tr></table></figure><p>老周说：</p><blockquote><p>“tag 是镜像的版本名，不要用 <code>latest</code> 作为生产环境唯一标识。”</p></blockquote><p>推荐命名规范：</p><table><thead><tr><th>标签</th><th>含义</th></tr></thead><tbody><tr><td><code>myapp:1.0.0</code></td><td>语义化版本控制</td></tr><tr><td><code>myapp:20240324</code></td><td>构建时间戳</td></tr><tr><td><code>myapp:prod</code></td><td>环境标识</td></tr><tr><td><code>myapp:feature-login</code></td><td>功能分支测试</td></tr></tbody></table><hr><h3 id="🔁-镜像缓存失效调试技巧"><a href="#🔁-镜像缓存失效调试技巧" class="headerlink" title="🔁 镜像缓存失效调试技巧"></a>🔁 镜像缓存失效调试技巧</h3><p>有时候构建镜像时，小李发现修改了某个文件，Docker 却好像没更新。</p><p>老周点拨他：“那是缓存搞的鬼。”</p><h4 id="方法一：强制跳过缓存"><a href="#方法一：强制跳过缓存" class="headerlink" title="方法一：强制跳过缓存"></a>方法一：强制跳过缓存</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build --no-cache -t myapp .</span><br></pre></td></tr></table></figure><h4 id="方法二：注意-COPY-顺序影响缓存命中"><a href="#方法二：注意-COPY-顺序影响缓存命中" class="headerlink" title="方法二：注意 COPY 顺序影响缓存命中"></a>方法二：注意 COPY 顺序影响缓存命中</h4><p>Docker 会从上到下按顺序缓存。如果把变化频繁的文件 COPY 太早，就会导致缓存失效：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><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">COPY</span><span class="language-bash"> requirements.txt .      <span class="comment"># OK，变动少，适合先复制</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip install -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .                     <span class="comment"># 后复制代码，避免频繁无效重建</span></span></span><br></pre></td></tr></table></figure><blockquote><p>✨ 技巧：越是稳定的文件，越早 COPY，利于缓存复用。</p></blockquote><hr><h2 id="📘-第二章-·-补充总结更新版"><a href="#📘-第二章-·-补充总结更新版" class="headerlink" title="📘 第二章 · 补充总结更新版"></a>📘 第二章 · 补充总结更新版</h2><table><thead><tr><th>技术点</th><th>命令 &#x2F; 说明</th></tr></thead><tbody><tr><td>多阶段构建</td><td><code>FROM ... AS builder</code> + <code>COPY --from=builder</code></td></tr><tr><td>忽略文件</td><td><code>.dockerignore</code> 文件</td></tr><tr><td>镜像上传私库</td><td><code>docker push localhost:5000/myapp</code></td></tr><tr><td>开启 BuildKit</td><td><code>DOCKER_BUILDKIT=1 docker build ...</code></td></tr><tr><td>进入镜像内调试</td><td><code>docker run -it 镜像 /bin/bash</code></td></tr><tr><td>镜像版本管理建议</td><td>避免乱用 <code>latest</code>，使用语义化 tag</td></tr><tr><td>跳过缓存构建</td><td><code>docker build --no-cache ...</code></td></tr></tbody></table><hr><p>小李站在镜像旅馆的屋顶，看着一层层高楼像乐高积木一样堆叠而起。</p><p>他感到激动——他已经不再为“部署”苦恼，而是拥有了一个随时可打包、可还原的开发宇宙。</p><p>老周说：“你的旅程才刚刚开始，容器的世界比镜像更复杂。”</p>]]></content>
    
    
    <summary type="html">Docker 入门第二章：深入理解 Docker 镜像的原理与使用</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="容器" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%AE%B9%E5%99%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（七）：超越蒲公英SD-WAN，用懒猫转发服务，Ipad随时远程家里window</title>
    <link href="https://blog.no-claw.com/posts/59f4c56d/"/>
    <id>https://blog.no-claw.com/posts/59f4c56d/</id>
    <published>2025-05-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近懒猫用懒猫的穿透服务突然有一些想法，既然每个客户端都可以用域名连接到微服，那么有没有可能从客户端之间可以互相访问呢？于是抱着这个目的试了一下，确实可以。异地组网的原因主要有几个，没有公网 IP，传输链路容易被截获以及不同办公室需要逻辑上的组网。</p><p>说干就干。</p><p>在懒猫论坛上看<strong>玄兴梦影</strong>的文章，<a href="https://playground.lazycat.cloud/#/guideline/478">《懒猫微服无缝连接你所有的设备》</a>和<a href="https://playground.lazycat.cloud/#/guideline/486">《懒猫微服助我生活工作穿梭无忧》</a>这两篇让我受益颇丰。主要是借用懒猫微服的中继服务，用来自建 Rust Desk，还有 RDP 到局域网的其他设备上。</p><p>先说 Rust Desk，这是一度被推举为 Todesk 的替代的软件，在懒猫上安装服务端拿到域名和密钥，然后在客户端上填入就可以了,具体操作可以看前面推荐的文章，里面有细致的讲解。</p><p>我的需求就是，在外边除了能够访问家里的微服之外，还访问其他的设备，比如群晖，威联通，甚至 windows。这样子以后带个 Ipad 出门就可以了，MacbookPro 的受害者表示笔记本太沉了。</p><p>所以这本质上是一个异地组网的问题，在上一个版本的 HomeLab 中是使用的蒲公英的 P5 盒子，这东西卖点是旁路由，直接 POE 接入局域网就好了，还能共享打印机。但是吧，商业产品还有很多限制，比如组网只能三个设备，带宽有限制，在多次的和售后拉扯而且案例没有在规定时间内回复也没有按照服务水平协议赔偿之后，索性退坑。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517082107797.png" alt="image-20250517082107797"></p><p>还有一次很玄学的事情，蒲公英盒子升级固件和家里跳闸的时间高度重合，开案例询问之后，传了日志，客服也只是贴了文档表示绝对不会有电流突增的问题，再问就再也没有回复了。然后会自动结单给五星好评，还不能重新打开继续问问题。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517082200302.png" alt="image-20250517082200302"></p><p>蒲公英在国内是没有什么竞品的，论企业服务远远不及思科之类的产品，而个人用户又一种丝毫不在意的态度。而且会一直推荐买最新的 X5 PRO，而卖完之后改完静态路由不能组网，客服又一副到底你懂不懂的样子。哎，蒲公英是彻底疯转黑了。那不买他们硬件用 OrayOS 呢？请先看免责声明：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517081919953.png" alt="image-20250517081919953"></p><p>所以一直想找其他 SD-WAN 或者异地组网的方案。主要需求嘛，就是既能从外面访问，也能转发其他的设备的流量，能做权限控制就更好了。</p><p>懒猫微服目前没有自己的虚拟机系统，所以是用的 webvirtcloud，但是商店里也有很多上架的系统，比如直接点点鼠标就可以开 windows 虚拟机了，这个有点云计算的味道了。不过就是我用的机械硬盘，开 windows 实在是有点差强人意。所以后面弄了一个局域网的机器专门拿来跑 windows，就是后面要提到的局域网转发。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517082649041.png" alt="image-20250517082649041"></p><p>从商店下载的 app 都有一个特定的域名。</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">&lt;应用名&gt;.&lt;设备名&gt;.heiyu.space</span><br></pre></td></tr></table></figure><p>以商店里的虚拟机为例，直接在 RDP 里输入这个机器的 URL 就就行，以前的 Windows remote desktop 现在改名叫做了 Win APP，同时也能在国内的 app store 上搜索到了。（前提登录懒猫 APP）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517174746816.png" alt="image-20250517174746816"></p><p>首先设置密码，虽然没有密码能够打开远程登录，但是实际怎么都连不上。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517175028685.png" alt="image-20250517175028685"></p><p>然后在系统设置打开远程桌面，在上面那个搜索栏的中直接搜就好。 然后我们就可以连接了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517175037735.png" alt="image-20250517175037735"></p><p>那两台登录微服的设备如何互联呢？这里有个很赞的工具，可以获取客户端的信息。商店搜索：<strong>懒猫微服在线设备获取</strong></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517175505231.png" alt="image-20250517175505231"></p><p>在 APP 中我们能够看到每个设备的域名：（隐私保护，我这里已经隐去）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517175013329.png" alt="image-20250517175013329"></p><p>那在脑洞一下，是不是可以做到之前说的旁路由的效果呢？</p><p>这里有个局域网转发工具。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517174925872.png" alt="image-20250517174925872"></p><p>猜测可能是 iptables 一类转发的吧。可以把局域网的设备映射出去（不需要安装懒猫客户端），还要什么旁路由。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517182205234.png" alt="image-20250517182205234"></p><p>现在远程连接的是我这台局域网一台刚刚装好的 windows，不需要在被控端安装安装任何软件（包括懒猫 APP），看到也能通过懒猫的域名组网了，开心～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250517182334136.png" alt="image-20250517182334136"></p><p><strong>内网穿透有了！</strong></p><p><strong>旁路路模式转发有了！</strong></p><p><strong>出门带 ipad 不用带笔记本了～</strong></p>]]></content>
    
    
    <summary type="html">用懒猫微服转发服务替代蒲公英 SD-WAN，iPad 随时远程访问家里的 Windows 电脑。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="组网" scheme="https://blog.no-claw.com/tags/%E7%BB%84%E7%BD%91/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（二）：一台机器跑三套 Docker？深入解析懒猫容器的共存机制（上）</title>
    <link href="https://blog.no-claw.com/posts/1d9319fb/"/>
    <id>https://blog.no-claw.com/posts/1d9319fb/</id>
    <published>2025-05-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文仅代表个人视角对懒猫 Docker 的拆解分析，内容为基于现象的倒推推测，不代表懒猫官方实现方式。</p></blockquote><p>拿到任何 NAS 的第一件事是开启 SSH 功能，第二步就是用 Docker 启动容器。</p><p>懒猫微服这个 docker 还不太一样，一个有三个 Docker：</p><blockquote><p>docker : 运行系统组件</p><p>pg-docker： 普通的 docker，让我们拿来玩</p><p>lzc-docker：运行懒猫商店的 docker</p></blockquote><h3 id="三套-Docker-引擎初探"><a href="#三套-Docker-引擎初探" class="headerlink" title="三套 Docker 引擎初探"></a>三套 Docker 引擎初探</h3><p>我们先来看看这三套 docker 引擎跑了些什么，从 ps 看起：</p><span id="more"></span><h5 id="docker-ps"><a href="#docker-ps" class="headerlink" title="docker ps"></a>docker ps</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">CONTAINER ID   IMAGE                                                                              COMMAND                  CREATED      STATUS                PORTS     NAMES</span><br><span class="line">1838d4f379e5   registry.lazycat.cloud/lzc/lzcsys:latest                                           &quot;/sspk/bin/pd-service&quot;   9 days ago   Up 9 days                       lzc-runtime-peripheral-device-1</span><br><span class="line">c2f6d791181b   registry.lazycat.cloud/lzc/lzcsys:latest                                           &quot;/sspk/bin/lzc-ingre…&quot;   9 days ago   Up 9 days (healthy)             lzc-runtime-ingress-control-1</span><br><span class="line">9699c428d2b0   registry.lazycat.cloud/dexidp/dex:v2.42.0-alpine                                   &quot;/usr/local/bin/dock…&quot;   9 days ago   Up 9 days                       lzc-runtime-dex</span><br><span class="line">57952c3e4ba5   registry.lazycat.cloud/lzc/lzcsys:latest                                           &quot;/sspk/bin/lzc-apise…&quot;   9 days ago   Up 9 days (healthy)             lzc-runtime-api-servers-1</span><br><span class="line">cde0eba62fd2   registry.lazycat.cloud/lzc/lzcsys:latest                                           &quot;/sspk/bin/lzc-pkgm&quot;     9 days ago   Up 9 days (healthy)             lzc-runtime-pkgm-1</span><br><span class="line">8e9c780c012c   registry.corp.lazycat.cloud/homecloud/lzc-registry-proxy:v0.0.0-2887-gd16c7f25.m   &quot;/bin/sh -c /lzc-reg…&quot;   9 days ago   Up 9 days             80/tcp    lzc-registry-proxy</span><br><span class="line">59d3803ef304   registry.corp.lazycat.cloud/homecloud/lzc-installer:v0.0.0-2887-gd16c7f25.m        &quot;/docker-entrypoint.…&quot;   9 days ago   Up 9 days                       lzc-installer</span><br><span class="line">c7192a7fd471   registry.corp.lazycat.cloud/homecloud/lzc-hal:v0.0.0-2887-gd16c7f25.m              &quot;/bin/sh -c /sspk/bi…&quot;   9 days ago   Up 9 days                       lzc-hal</span><br><span class="line">1d194e975117   registry.corp.lazycat.cloud/homecloud/lzc-recovery:v0.0.0-2887-gd16c7f25.m         &quot;/docker-entrypoint.…&quot;   9 days ago   Up 9 days                       lzc-recovery</span><br><span class="line">8338ce6a5c17   registry.corp.lazycat.cloud/homecloud/lzc-recovery:v0.0.0-2887-gd16c7f25.m         &quot;/sspk/bin/entrypoin…&quot;   9 days ago   Up 9 days</span><br></pre></td></tr></table></figure><p>这里可以看到，系统级组件都跑在默认的 <code>docker</code> 下。</p><h5 id="pg-docker-ps"><a href="#pg-docker-ps" class="headerlink" title="pg-docker ps"></a>pg-docker ps</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CONTAINER ID   IMAGE                                                                     COMMAND                  CREATED      STATUS        PORTS                                     NAMES</span><br><span class="line">d0ae10b8fc8f   registry.lazycat.cloud/u04123229/qilinzhu/ql-play:fbf2e99a00ef9a7f        &quot;sh /app/start.sh&quot;       3 days ago   Up 26 hours                                             ql-play</span><br><span class="line">0cb9ec655c16   registry.lazycat.cloud/u04123229/cloudsmithy/shuangpin:2a8ede2b23c38be8   &quot;/docker-entrypoint.…&quot;   6 days ago   Up 6 days     0.0.0.0:5004-&gt;80/tcp, [::]:5004-&gt;80/tcp   unruffled_lichterman</span><br></pre></td></tr></table></figure><blockquote><p><code>pg-docker</code> 实际上就是日常部署、测试容器最常用的那一套运行时环境， Dockge 默认连接的运行时也是这个。只是这里为了区分系统 docker 做了改名，playground 就是随便玩的意思。</p></blockquote><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250520102656467.png" alt="image-20250520102656467"></p><h5 id="lzc-docker-ps"><a href="#lzc-docker-ps" class="headerlink" title="lzc-docker ps"></a>lzc-docker ps</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">lzc-docker ps</span><br><span class="line">CONTAINER ID   IMAGE                                                                                                                              COMMAND                  CREATED        STATUS                  PORTS                                         NAMES</span><br><span class="line">80c88ae6aa8b   registry.lazycat.cloud/app-tv-controller:1.0                                                                                       &quot;/lzcinit/cloud.lazy…&quot;   16 hours ago   Up 16 hours (healthy)                                                 cloudlazycatapplzctvcontroller-app-1</span><br><span class="line">fdb2211b210e   registry.lazycat.cloud/lzc/tvos-release:v0.1.219                                                                                   &quot;/home/tvos/run.sh&quot;      16 hours ago   Up 16 hours             5500/tcp                                      cloudlazycatapplzctvcontroller-tvos-1</span><br></pre></td></tr></table></figure><p>这个是懒猫商店的 Docker，实测在客户端中停止应用是是把对应的 docker 删除了，无论是从<code>docker ps -a | grep auth</code>还是可视化工具看来。这也很符合使用容器的习惯，不需要的时候就删除，随用随启动，但是数据仍然还在。</p><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></pre></td><td class="code"><pre><span class="line">lzcbox-029c588e ~ <span class="comment"># docker --version</span></span><br><span class="line">Docker version 27.5.1, build 9f9e405</span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># pg-docker --version</span></span><br><span class="line">Docker version 27.5.1, build 9f9e405</span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># lzc-docker --version</span></span><br><span class="line">Docker version 27.5.1, build 9f9e405</span><br></pre></td></tr></table></figure><p>再看看存储后端，那是不是有什么魔改呢？看的出来后端都是 containerd。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">lzcbox-029c588e ~ # docker info | grep -i &#x27;Runtimes\|Default Runtime&#x27;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br><span class="line">lzcbox-029c588e ~ # pg-docker info | grep -i &#x27;Runtimes\|Default Runtime&#x27;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br><span class="line">lzcbox-029c588e ~ # lzc-docker info | grep -i &#x27;Runtimes\|Default Runtime&#x27;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br></pre></td></tr></table></figure><p>甚至连 containerd 的版本都一样</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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"># docker info | grep -i &quot;containerd&quot;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb</span><br><span class="line">lzcbox-029c588e ~ # pg-docker info | grep -i &quot;containerd&quot;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb</span><br><span class="line">lzcbox-029c588e ~ # lzc-docker info | grep -i &quot;containerd&quot;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb</span><br><span class="line">lzcbox-029c588e ~ #</span><br></pre></td></tr></table></figure><p>一开始以为是魔改看了下我的 mac 运行的 Orbstack 的配置，好像也没啥差别。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">docker info | grep -i &#x27;Runtimes\|Default Runtime&#x27;</span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">docker info | grep -i &quot;containerd&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> containerd version: 06b99ca80cdbfbc6cc8bd567021738c9af2b36ce</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="多引擎共存的实现方式"><a href="#多引擎共存的实现方式" class="headerlink" title="多引擎共存的实现方式"></a>多引擎共存的实现方式</h3><h4 id="DOCKER-HOST-的封装"><a href="#DOCKER-HOST-的封装" class="headerlink" title="DOCKER_HOST 的封装"></a>DOCKER_HOST 的封装</h4><p>既然三个 docker 都出奇的一致，到底是类似命名空间的隔离嘛？</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">lzcbox-029c588e ~ # which docker</span><br><span class="line">/usr/bin/docker</span><br><span class="line">lzcbox-029c588e ~ # which pg-docker</span><br><span class="line">/lzcsys/bin/pg-docker</span><br><span class="line">lzcbox-029c588e ~ # which lzc-docker</span><br><span class="line">/lzcsys/bin/lzc-docker</span><br><span class="line">lzcbox-029c588e ~ # file $(which docker)</span><br><span class="line">/usr/bin/docker: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a41bb12cfd0c306a6ede40f41cfc107b2045371, for GNU/Linux 3.2.0, with debug_info, not stripped</span><br><span class="line">lzcbox-029c588e ~ # file $(which pg-docker)</span><br><span class="line">/lzcsys/bin/pg-docker: Bourne-Again shell script, ASCII text executable</span><br><span class="line">lzcbox-029c588e ~ # file $(which lzc-docker)</span><br><span class="line">/lzcsys/bin/lzc-docker: Bourne-Again shell script, ASCII text executable</span><br></pre></td></tr></table></figure><p>这就可以发现问题了，docker 是原来的 docker，但是 pg-docker 和 lzc-docker 是封装的脚本，来看一下：</p><blockquote><p>所以我们可以得出一个关键点：懒猫并不是运行了三套完全独立的 Docker 服务，而是通过 shell 脚本封装，复用同一个 <code>docker</code> 客户端，切换不同的 socket 实现了“环境隔离”。这个脚本的作用相当于把 <code>pg-docker</code> 当成 <code>docker</code> 命令使用，还自动附带了环境变量 <code>DOCKER_HOST=...</code>。</p></blockquote><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">lzcbox-029c588e ~ <span class="comment"># cat $(which pg-docker)</span></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> DOCKER_HOST=unix:///data/playground/docker.sock</span><br><span class="line"><span class="built_in">exec</span> docker <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure><p>这设置了 <code>DOCKER_HOST</code> 环境变量，使得之后执行的 <code>docker</code> 命令会连接到 <code>/data/playground/docker.sock</code> 这个 Unix Socket，而**不是默认的 <code>/var/run/docker.sock</code>**。</p><p><code>exec</code> 是一个 shell 内建命令，它会用新的进程替换当前脚本的进程。</p><p><code>&quot;$@&quot;</code> 表示把脚本接收到的所有参数（比如 <code>pg-docker ps -a</code>）<strong>原样传递</strong>给 <code>docker</code> 命令。</p><p>之前想上架一个 Docker 可视化工具用来，但是总不知道需要映射哪个 docker.sock，这下子全都清楚了，有了这个就能在 docker 里使用宿主机的 Docker API 了。</p><h4 id="daemon-json-配置详解"><a href="#daemon-json-配置详解" class="headerlink" title="daemon.json 配置详解"></a>daemon.json 配置详解</h4><p>当然与之对应的还有 daemon.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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">lzcbox-029c588e /data/playground/data <span class="comment"># cat /lzcsys/var/playground/daemon.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;bridge&quot;</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  <span class="string">&quot;containerd-namespace&quot;</span>: <span class="string">&quot;playground-docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;containerd-plugins-namespace&quot;</span>: <span class="string">&quot;playground-docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;data-root&quot;</span>: <span class="string">&quot;/data/playground/data/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;default-address-pools&quot;</span>: [</span><br><span class="line"></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;exec-root&quot;</span>: <span class="string">&quot;/data/playground/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;hosts&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;unix:///data/playground/docker.sock&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;insecure-registries&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;registry.lazycat.cloud&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;pidfile&quot;</span>: <span class="string">&quot;/data/playground/docker.pid&quot;</span></span><br></pre></td></tr></table></figure><p>这样多个 Docker 环境就能共存了，例如：</p><ul><li>系统默认的 <code>/var/run/docker.sock</code></li><li>一个沙箱环境 <code>/data/playground/docker.sock</code></li></ul><p>这么设置好之后可以快速切换上下文，而不用每次都手动设置 <code>DOCKER_HOST</code>。</p><p>在我开发的容器可视化面板总，看到已经可以指定 docker sock 作为连接了,参考：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="attr">services:</span></span><br><span class="line">  <span class="attr">containly:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">registry.lazycat.cloud/u04123229/cloudsmithy/containly:30e4e3279afe9a52</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">5003</span><span class="string">:5000</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/data/playground/docker.sock:/var/run/docker.sock</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250520104141112.png" alt="image-20250520104141112"></p><h4 id="三套-daemon-json-对比分析"><a href="#三套-daemon-json-对比分析" class="headerlink" title="三套 daemon.json 对比分析"></a>三套 daemon.json 对比分析</h4><p>从实际查找结果来看，懒猫为三套 Docker 引擎配置了不同的 daemon.json 文件和运行时环境：</p><ul><li><strong>系统组件专用</strong>（docker）：<code>/etc/docker/daemon.json</code></li><li><strong>用户 playground 环境</strong>（pg-docker）：<code>/lzcsys/var/playground/daemon.json</code></li><li><strong>懒猫商店环境</strong>（lzc-docker）：<code>/lzcsys/etc/docker/daemon.json</code></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">sudo find / -type f -name daemon.json 2&gt;/dev/null</span><br><span class="line"></span><br><span class="line">/etc/docker/daemon.json</span><br><span class="line">/run/lzcsys/boot/lzc-os-init/var/playground/daemon.json</span><br><span class="line">/run/lzcsys/boot/lzc-os-overlay/lowerdir/lzcsys/etc/docker/daemon.json</span><br><span class="line">/run/lzcsys/boot/lzc-os-overlay/lowerdir/lzcsys/lzcsys/etc/docker/daemon.json</span><br><span class="line">/lzcsys/etc/docker/daemon.json</span><br><span class="line">/lzcsys/var/playground/daemon.json</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每个配置文件中都指定了独立的：</p><ul><li><code>data-root</code></li><li><code>exec-root</code></li><li><code>pidfile</code></li><li><code>hosts（即 sock 文件路径）</code></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 默认的docker引擎</span></span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># cat /etc/docker/daemon.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;registry-mirrors&quot;</span>: [</span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;insecure-registries&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;registry.lazycat.cloud&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;log-driver&quot;</span>: <span class="string">&quot;journald&quot;</span>,</span><br><span class="line">  <span class="string">&quot;cgroup-parent&quot;</span>: <span class="string">&quot;sys_docker.slice&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 商店的docker引擎</span></span><br><span class="line">lzcbox-029c588e ~ <span class="comment"># cat /lzcsys/etc/docker/daemon.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;bridge&quot;</span>: <span class="string">&quot;none&quot;</span>,</span><br><span class="line">  <span class="string">&quot;insecure-registries&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;registry.lazycat.cloud&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;default-address-pools&quot;</span>: [</span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;ipv6&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&quot;hosts&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;unix:///lzcsys/run/lzc-docker/docker.sock&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;containerd-namespace&quot;</span>: <span class="string">&quot;lzc-docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;containerd-plugins-namespace&quot;</span>: <span class="string">&quot;lzc-docker-plugins&quot;</span>,</span><br><span class="line">  <span class="string">&quot;exec-root&quot;</span>: <span class="string">&quot;/lzcsys/run/lzc-docker/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;pidfile&quot;</span>: <span class="string">&quot;/lzcsys/run/lzc-docker/docker.pid&quot;</span>,</span><br><span class="line">  <span class="string">&quot;data-root&quot;</span>: <span class="string">&quot;/lzcsys/run/data/system/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;cgroup-parent&quot;</span>: <span class="string">&quot;lzc_docker.slice&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># playground的docker引擎</span></span><br><span class="line"><span class="built_in">cat</span> /lzcsys/var/playground/daemon.json</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;bridge&quot;</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  <span class="string">&quot;containerd-namespace&quot;</span>: <span class="string">&quot;playground-docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;containerd-plugins-namespace&quot;</span>: <span class="string">&quot;playground-docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;data-root&quot;</span>: <span class="string">&quot;/data/playground/data/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;default-address-pools&quot;</span>: [],</span><br><span class="line">  <span class="string">&quot;exec-root&quot;</span>: <span class="string">&quot;/data/playground/docker&quot;</span>,</span><br><span class="line">  <span class="string">&quot;hosts&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;unix:///data/playground/docker.sock&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;insecure-registries&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;registry.lazycat.cloud&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;pidfile&quot;</span>: <span class="string">&quot;/data/playground/docker.pid&quot;</span></span><br></pre></td></tr></table></figure><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>懒猫微服总会给我惊讶，除了极客风格的外壳，性能突出的硬件外，里面的软件设计也同样优秀，这个设计让我对 docker 有了更加深刻的认识。</p><p>三套 docker 共存不是表面上的魔改，而是通过 containerd 的 namespace 配合脚本封装，在容器之上再抽象一层运行时，把 playground、系统和商店隔离成三界，却又共用一套内核，很好玩。</p>]]></content>
    
    
    <summary type="html">深入解析懒猫微服一台机器运行三套 Docker 引擎的共存机制（上篇）。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>如何关闭 AWS root 账号的 MFA 认证</title>
    <link href="https://blog.no-claw.com/posts/f4ce40dc/"/>
    <id>https://blog.no-claw.com/posts/f4ce40dc/</id>
    <published>2025-05-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong>警告</strong>：关闭 root 账号的 MFA 认证存在较高风险。AWS 通常不建议此操作，因为 root 账号拥有对所有 AWS 资源的完全控制权限。如果密码或账号信息泄露，可能导致严重的安全事故。</p><h2 id="关闭步骤"><a href="#关闭步骤" class="headerlink" title="关闭步骤"></a>关闭步骤</h2><ol><li><p>使用 root 账号登录 AWS 管理控制台</p></li><li><p>点击右上角账号名称</p></li><li><p>选择”安全凭证”选项</p></li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250716105410922.png" alt="image-20250716105410922"></p><ol><li>在页面中找到”多重身份认证(MFA)”部分</li><li>点击”删除”即可</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250716105355108.png" alt="image-20250716105355108"></p><p>然后会收到邮件提醒：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250716105716435.png" alt="image-20250716105716435"></p><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">To complete this action, please ensure that you are authenticated with an MFA device that is enabled for this user.</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">详解如何关闭 AWS root 账号的 MFA 多因素认证</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/AWS/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
    <category term="MFA" scheme="https://blog.no-claw.com/tags/MFA/"/>
    
  </entry>
  
  <entry>
    <title>写给懒猫微服玩家的容器小书 Docker篇（一）：《无法部署的诅咒》</title>
    <link href="https://blog.no-claw.com/posts/effe7a9a/"/>
    <id>https://blog.no-claw.com/posts/effe7a9a/</id>
    <published>2025-05-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一直想写一本容器小书，真好懒猫基本都做了容器化，所以把这部分分享出来。不同的是，懒猫微服中使用 pg-docker 来替代 docker 命令，使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主，这样子既学会了 docker 知识，也能够在懒猫微服上启动 Docker 服务。</p></blockquote><h1 id="《无法部署的诅咒》讲的是-Docker-的起源与作用、镜像与容器概念、安装入门"><a href="#《无法部署的诅咒》讲的是-Docker-的起源与作用、镜像与容器概念、安装入门" class="headerlink" title="《无法部署的诅咒》讲的是 Docker 的起源与作用、镜像与容器概念、安装入门"></a>《无法部署的诅咒》讲的是 Docker 的起源与作用、镜像与容器概念、安装入门</h1><h3 id="🏙️-开篇：代码在电脑上运行良好，部署却频频翻车"><a href="#🏙️-开篇：代码在电脑上运行良好，部署却频频翻车" class="headerlink" title="🏙️ 开篇：代码在电脑上运行良好，部署却频频翻车"></a>🏙️ 开篇：代码在电脑上运行良好，部署却频频翻车</h3><p>小李是一名后端工程师，刚完成一个用 Flask 编写的内部管理系统。他信心满满地将代码提交，交给测试工程师老赵部署。</p><p>可没想到——</p><blockquote><p>“报错了！你是不是少传文件了？”</p><p>“我这边 Python 是 3.6，怎么你这代码用了 <code>match case</code>？”</p><p>“你数据库呢？你 Redis 配了？环境变量在哪？”</p></blockquote><p>这一刻，小李陷入了一个叫「部署地狱」的世界。</p><p>这个世界里，每一台服务器都是独一无二的“宠物”，需要手动配置、手动部署、手动踩坑，稍有不慎就会陷入版本冲突、依赖缺失、环境不一致的深渊。</p><p>小李心里想：<strong>有没有一种办法，能让我把代码和环境一起打包起来，无论在哪运行都能保持一致？</strong></p><h2 id=""><a href="#" class="headerlink" title=""></a><span id="more"></span></h2><h3 id="🌀-变故：神秘的程序员大叔与魔法容器"><a href="#🌀-变故：神秘的程序员大叔与魔法容器" class="headerlink" title="🌀 变故：神秘的程序员大叔与魔法容器"></a>🌀 变故：神秘的程序员大叔与魔法容器</h3><p>在公司茶水间，小李邂逅了一个带着黑框眼镜的大叔，大家都叫他老周。</p><p>老周是个技术老炮，在各种部署大灾难中杀出血路。他听完小李的吐槽后，轻轻地推了一份文档过来，只写了五个字母：</p><blockquote><p><code>Docker</code></p></blockquote><p>“这是个<strong>魔法容器</strong>，”老周说，“把你代码和环境都打包进去，哪怕放在火星，也能跑。”</p><blockquote><p>“以后别再‘它在我电脑上可以跑’了，Docker 能让所有环境变得一致。”</p></blockquote><p>小李的眼睛亮了。</p><hr><h3 id="🔧-技术讲解-Part-1：什么是-Docker？"><a href="#🔧-技术讲解-Part-1：什么是-Docker？" class="headerlink" title="🔧 技术讲解 Part 1：什么是 Docker？"></a>🔧 技术讲解 Part 1：什么是 Docker？</h3><p>Docker 是一个<strong>开源的容器化平台</strong>，它允许开发者将应用及其所有依赖打包成一个“容器”，保证在任何平台上都可以一致运行。</p><p>你可以把 Docker 想象成：</p><blockquote><p><strong>程序员的打包箱子</strong>：把你写的程序、环境、库、配置都放进去，打包成一个“镜像”；</p><p><strong>程序员的快递服务</strong>：运行镜像就像打开快递，内容和你寄出时一模一样。</p></blockquote><h4 id="🚀-为什么要用-Docker？"><a href="#🚀-为什么要用-Docker？" class="headerlink" title="🚀 为什么要用 Docker？"></a>🚀 为什么要用 Docker？</h4><ul><li>✅ <strong>跨平台运行</strong>：一次构建，到处运行（Run anywhere）</li><li>✅ <strong>快速部署</strong>：秒级启动，适合 CI&#x2F;CD</li><li>✅ <strong>环境一致性</strong>：不再“你电脑能跑我电脑不行”</li><li>✅ <strong>轻量隔离</strong>：不像虚拟机那么重，不需要整个操作系统</li><li>✅ <strong>资源占用少</strong>：用起来更像一个进程，而不是一台虚拟机</li></ul><blockquote><p>☑️ 一句话总结：<strong>Docker 解决了“在我电脑上能跑”的问题。</strong></p></blockquote><hr><h3 id="🛠️-技术讲解-Part-2：如何安装-Docker？"><a href="#🛠️-技术讲解-Part-2：如何安装-Docker？" class="headerlink" title="🛠️ 技术讲解 Part 2：如何安装 Docker？"></a>🛠️ 技术讲解 Part 2：如何安装 Docker？</h3><p>老周拍了拍小李的肩膀，说：“先装起来，动手最重要。”</p><h4 id="🧑‍💻-在-Mac-Windows-上："><a href="#🧑‍💻-在-Mac-Windows-上：" class="headerlink" title="🧑‍💻 在 Mac &#x2F; Windows 上："></a>🧑‍💻 在 Mac &#x2F; Windows 上：</h4><ul><li>访问官网：<a href="https://www.docker.com/products/docker-desktop">https://www.docker.com/products/docker-desktop</a></li><li>下载并安装 Docker Desktop</li><li>安装后打开终端（Terminal），输入：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker --version</span><br></pre></td></tr></table></figure><p>如果看到 Docker 的版本信息，说明安装成功。</p><h4 id="🧑‍💻-在-Linux（Ubuntu）上："><a href="#🧑‍💻-在-Linux（Ubuntu）上：" class="headerlink" title="🧑‍💻 在 Linux（Ubuntu）上："></a>🧑‍💻 在 Linux（Ubuntu）上：</h4><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="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install docker.io</span><br><span class="line"><span class="built_in">sudo</span> systemctl start docker</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> docker</span><br></pre></td></tr></table></figure><p>🔐 <em>建议将当前用户加入 docker 组，以免每次都要用 sudo：</em></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> usermod -aG docker <span class="variable">$USER</span></span><br></pre></td></tr></table></figure><hr><h3 id="🧠-技术讲解-Part-3：Docker-的核心概念"><a href="#🧠-技术讲解-Part-3：Docker-的核心概念" class="headerlink" title="🧠 技术讲解 Part 3：Docker 的核心概念"></a>🧠 技术讲解 Part 3：Docker 的核心概念</h3><table><thead><tr><th>概念</th><th>说明</th></tr></thead><tbody><tr><td>镜像（Image）</td><td>应用和其依赖的静态快照模板（如：打包好的 Python 环境 + Flask 项目）</td></tr><tr><td>容器（Container）</td><td>镜像运行出来的实例，是真正“活着”的程序</td></tr><tr><td>Dockerfile</td><td>编写镜像的“配方”，定义如何构建镜像</td></tr><tr><td>Docker Hub</td><td>类似 GitHub 的公共镜像仓库，可上传&#x2F;下载别人做好的镜像</td></tr><tr><td><code>docker</code> 命令</td><td>Docker 的主命令工具，用于操作镜像、容器、网络、卷等</td></tr></tbody></table><h4 id="📌-镜像-vs-容器"><a href="#📌-镜像-vs-容器" class="headerlink" title="📌 镜像 vs 容器"></a>📌 镜像 vs 容器</h4><ul><li>镜像是“模具”，容器是“实物”</li><li>镜像不可变，容器是可运行的环境</li><li>一个镜像可以运行多个容器</li></ul><hr><h3 id="🧪-技术实践：Hello-Docker-世界"><a href="#🧪-技术实践：Hello-Docker-世界" class="headerlink" title="🧪 技术实践：Hello Docker 世界"></a>🧪 技术实践：Hello Docker 世界</h3><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">docker run hello-world</span><br></pre></td></tr></table></figure><blockquote><p>如果 docker run&#x2F;pull 有问题，那么可以通过<code>lzc-cli appstore copy-image your-images</code>来使用懒猫的镜像仓库。</p></blockquote><p>这个命令会做三件事：</p><ol><li>自动从 Docker Hub 拉取一个 <code>hello-world</code> 镜像（如果本地没有）</li><li>基于镜像运行容器</li><li>容器运行后输出信息，然后自动退出</li></ol><p>这是验证 Docker 是否正常运行的“点灯测试”。</p><hr><h3 id="🧱-小李的第一个真实容器：Flask-Web-项目打包实战"><a href="#🧱-小李的第一个真实容器：Flask-Web-项目打包实战" class="headerlink" title="🧱 小李的第一个真实容器：Flask Web 项目打包实战"></a>🧱 小李的第一个真实容器：Flask Web 项目打包实战</h3><p>老周笑着说：“现在，把你那个 Flask 管理系统也丢进 Docker 试试。”</p><p>小李在项目根目录下写了一个 Dockerfile：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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"># 使用 Python 官方基础镜像</span></span><br><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.11</span>-slim</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置工作目录</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制项目文件</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装依赖</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip install -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置启动命令</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;main.py&quot;</span>]</span></span><br></pre></td></tr></table></figure><h4 id="构建镜像："><a href="#构建镜像：" class="headerlink" title="构建镜像："></a>构建镜像：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build -t my-flask-app .</span><br></pre></td></tr></table></figure><h4 id="启动容器："><a href="#启动容器：" class="headerlink" title="启动容器："></a>启动容器：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 5000:5000 my-flask-app</span><br></pre></td></tr></table></figure><p>现在访问浏览器 <code>http://localhost:5000</code>，你的项目上线了！</p><hr><h3 id="💡-小知识快闪"><a href="#💡-小知识快闪" class="headerlink" title="💡 小知识快闪"></a>💡 小知识快闪</h3><ul><li>Docker 容器内部不包含完整操作系统，只包含必要的运行库；</li><li>Dockerfile 的每一行都是一层（Layer），构建时会缓存复用；</li><li><code>docker ps</code> 查看运行中容器，<code>docker images</code> 查看已有镜像；</li><li>可以通过 <code>.dockerignore</code> 文件忽略不想加入镜像的文件，比如 <code>.git</code> 和日志。</li></ul><hr><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></pre></td><td class="code"><pre><span class="line">docker run -p 5000:5000 my-flask-app</span><br></pre></td></tr></table></figure><p>老赵只运行了一行命令，项目便神奇地跑了起来。</p><blockquote><p>“不改配置？不用装 Python？数据库也连上了？”<br>“你这是什么魔法！”</p></blockquote><p>小李笑了笑，第一次感觉部署是件简单的事。</p><hr><h2 id="🧭-第一章总结知识点："><a href="#🧭-第一章总结知识点：" class="headerlink" title="🧭 第一章总结知识点："></a>🧭 第一章总结知识点：</h2><table><thead><tr><th>技术点</th><th>命令</th></tr></thead><tbody><tr><td>安装 Docker</td><td>Mac&#x2F;Win 下载 Docker Desktop &#x2F; Ubuntu 安装 <code>docker.io</code></td></tr><tr><td>查看版本</td><td><code>docker --version</code></td></tr><tr><td>运行测试容器</td><td><code>docker run hello-world</code></td></tr><tr><td>编写 Dockerfile</td><td>FROM &#x2F; COPY &#x2F; RUN &#x2F; CMD</td></tr><tr><td>构建镜像</td><td><code>docker build -t name .</code></td></tr><tr><td>运行容器</td><td><code>docker run -p 宿主:容器 镜像名</code></td></tr></tbody></table>]]></content>
    
    
    <summary type="html">Docker 入门第一章：理解为什么需要容器化部署</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="容器" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%AE%B9%E5%99%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服炫技篇（一）：使用懒猫微服的穿透服务，车机使用DS music 远程听歌，黑群晖不用再洗白</title>
    <link href="https://blog.no-claw.com/posts/12695041/"/>
    <id>https://blog.no-claw.com/posts/12695041/</id>
    <published>2025-05-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>声明：炫技篇不是最佳实践，只是为了记录过程和那颗折腾的心。虽然很多时候可以用钱来升级或者多忍一忍就好了。但是折腾的心始终是不安分的，技术在职场中证明不了什么，但是在生活中可以。当晦涩的理论不仅仅存在于书本，才能给我们带来更大的价值。这个过程中能够找到我们到底是为了什么才需要这项技术，问题的痛点，以及这技术的前世今生。。。。。。</p></blockquote><p>前些天给老旧的车载导航升级了 WIFI 功能，大致就是出厂的时候锁了 wifi 的驱动和 UI，然后通过 ADB 解锁的，然后通过路由器 Mesh 升级了老旧的 APP 以及导航数据，这次又带来了全新的功能，给普通车机升级 Carplay。</p><p>毕竟我的初衷，就是想在车机上<strong>安装懒猫 APP</strong> 而已啊。</p><h3 id="Part-1："><a href="#Part-1：" class="headerlink" title="Part 1："></a>Part 1：</h3><p>下载懒猫微服 APK，通过 U 盘导入安装，报错：<strong>解析错误，解析软件包时出现问题。</strong></p><p>于是和懒猫相关技术进行确认，很快 CEO 给到了答复，目前只支持比亚迪和华为。毕竟国产车嘛，路子野一点也正常。</p><p>那我如果不用安卓，换 Carplay 呢？是不是就能用 IOS 的客户端直接投过去了？</p><span id="more"></span><h3 id="Part-2："><a href="#Part-2：" class="headerlink" title="Part 2："></a>Part 2：</h3><p>开始改 Carplay。</p><p>车载导航是当时赠送的，安卓 6.0。朋友推荐说可以购买 Carplay 盒子，于是疯狂搜索，现在的 Carplay 盒子分为两种。</p><ol><li>有线 Carplay 改无线 Carplay</li><li>安卓车机改无线 Carplay</li></ol><p>我的安卓车机没有 Carplay APP，方向盘也没有语音控制按钮。所以一开始能不能支持 Carplay，我是比较有疑问的。直到搜到了一篇夸赞车连易的帖子，大概说是最早做这个方向的公司，产品比较稳定一类的。</p><p>于是买回来调试，需要先安装他们的 autokit 软件检测（买之前需要用 Iphone 检测），这个 APP 后续当 Carplay APP 使用。（这里有个小插曲，之前 adb 调试的时候在改了工厂模式，开了 USB 调试，导致后来 USB 无法识别任何设备，包括 U 盘，2.4G 发射器，手机，以及 Carplay 盒子）对照之前的照片，关了就好了。然后我就拥有了人生第一台 Carplay 车机，盒子一点不烫，就是手机哗哗掉电。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250519062925067.png" alt="image-20250519062925067"></p><p>开心之余还不忘记去找懒猫 APP，屏幕上有钉钉，百度网盘，网易云，甚至连 Home Assistant 都有？那我的懒猫微服 APP 呢？原来是 Apple 的白名单限制，美其名曰为了安全。GPT 搜下了，基本上除了导航和音乐一类的设备其他都没有了。那我高速服务区追个剧？好歹让投个屏嘛。。。。。。</p><p>同时和微服以及 Carplay 盒子的厂家确认，得到了一份寥寥无几的 APP 名单。</p><p>梦碎？</p><p>GPT 的解释如下：</p><p>以下内容基于 Apple 开发文档（2024 年最新版）及最近一次 iOS 18.4 更新的公开信息整理而成，可作为「CarPlay 现阶段 <em>官方</em> 支持的 App 类型、代表性应用，以及常见限制」速查表。(<a href="https://developer.apple.com/carplay/documentation/CarPlay-App-Programming-Guide.pdf">Apple Developer</a>)</p><hr><h5 id="1-CarPlay-允许上车的-8-大官方类别"><a href="#1-CarPlay-允许上车的-8-大官方类别" class="headerlink" title="1 | CarPlay 允许上车的 8 大官方类别"></a>1 | CarPlay 允许上车的 8 大官方类别</h5><table><thead><tr><th>类别</th><th>典型代表 App</th><th>主要交互模板</th><th>备注</th></tr></thead><tbody><tr><td><strong>导航</strong></td><td>Apple 地图、Google Maps、Waze、Gaode&#x2F;高德</td><td>地图、仪表盘、列表</td><td>iOS 18.4 起可设 <strong>默认导航</strong>（EU 地区率先落地）(<a href="https://www.the-sun.com/motors/14129485/apple-car-play-update-drivers-improvement-maps/?utm_source=chatgpt.com">The US Sun</a>)</td></tr><tr><td><strong>音频</strong></td><td>Apple Music、Spotify、网易云音乐、Podcast、Audible、DS Music（Synology）</td><td>播放中、网格、列表</td><td>仅音频；禁止歌词滚动、视频封面等会分散注意力</td></tr><tr><td><strong>通信</strong></td><td>电话、信息、WhatsApp、钉钉、Teams</td><td>列表、语音</td><td>文本内容不得直接显示，只能通过 Siri 朗读</td></tr><tr><td><strong>停车</strong></td><td>EasyPark、停简单</td><td>列表</td><td>提供空位、计费等信息</td></tr><tr><td><strong>EV 充电</strong></td><td>Tesla、ChargePoint、小桔充电</td><td>列表、地图</td><td>可筛选功率、空闲桩等</td></tr><tr><td><strong>加油 &#x2F; 加氢</strong></td><td>BPme、壳牌加油</td><td>列表、地图</td><td>仅提供站点 &amp; 价格；支付流程需遵循「停车后才允许」规则</td></tr><tr><td><strong>快餐外卖</strong></td><td>Dunkin’、星巴克</td><td>列表</td><td>只能选择常用餐点并一键复购，禁做复杂自定义</td></tr><tr><td><strong>驾驶辅助 &#x2F; 任务</strong></td><td>扫盲测试、行车记录提示类</td><td>网格、信息</td><td>聚焦安全提醒、里程统计等（不含 ADAS 改装）</td></tr></tbody></table><blockquote><p><strong>车企自家 App</strong>（空调、座椅、360 环视等）属于「Automaker Apps」，与上表并列但须由车厂提交。(<a href="https://developer.apple.com/carplay/?utm_source=chatgpt.com">Apple Developer</a>)</p></blockquote><hr><h5 id="2-为什么某些-App（视频、文件管理等）上不了-CarPlay？"><a href="#2-为什么某些-App（视频、文件管理等）上不了-CarPlay？" class="headerlink" title="2 | 为什么某些 App（视频、文件管理等）上不了 CarPlay？"></a>2 | 为什么某些 App（视频、文件管理等）上不了 CarPlay？</h5><table><thead><tr><th>场景</th><th>限制要点</th><th>开发侧原因</th></tr></thead><tbody><tr><td><strong>视频&#x2F;娱乐（Netflix、哔哩哔哩）</strong></td><td>行车安全：禁止播放分散注意力的可视内容</td><td>CarPlay 模板不提供视频区域；审核会拒绝</td></tr><tr><td><strong>文件管理（ES 文件浏览器、NAS 探索）</strong></td><td>无对应类别；操作路径过复杂</td><td>无可用模板且易诱导司机操作</td></tr><tr><td><strong>社交媒体（微博、X、抖音）</strong></td><td>不得显示滚动 Feed、评论等</td><td>CarPlay 指南明令「No social networking」(<a href="https://developer.apple.com/carplay/documentation/CarPlay-App-Programming-Guide.pdf">Apple Developer</a>)</td></tr><tr><td><strong>游戏 &#x2F; 浏览器</strong></td><td>驾驶分心 &amp; 没有类别</td><td>同上</td></tr></tbody></table><hr><h5 id="3-作为开发者，要满足哪些门槛？"><a href="#3-作为开发者，要满足哪些门槛？" class="headerlink" title="3 | 作为开发者，要满足哪些门槛？"></a>3 | 作为开发者，要满足哪些门槛？</h5><ol><li><strong>申请 CarPlay Entitlement</strong><ul><li>向 Apple 提交 App ID、类别说明、演示视频；获批后才能编译带 CarPlay 功能的版本。(<a href="https://developer.apple.com/documentation/carplay/requesting-carplay-entitlements?utm_source=chatgpt.com">Apple Developer</a>)</li></ul></li><li><strong>使用系统提供的模板</strong><ul><li>只能调用列表、网格、地图、Now Playing 等固定 UI；不可自绘按钮或随意布局。(<a href="https://developer.apple.com/carplay/documentation/CarPlay-App-Programming-Guide.pdf">Apple Developer</a>)</li></ul></li><li><strong>全部操作无需拿起 iPhone</strong><ul><li>登录、付费等「复杂流程」必须设计为<strong>停车后</strong>才可完成，或通过 SiriKit 语音处理。</li></ul></li><li><strong>严格的内容审核</strong><ul><li>不得插入横幅广告，禁止收集车辆数据做非核心用途，消息不可明文显示等。</li></ul></li></ol><hr><h5 id="4-对普通用户而言的使用限制"><a href="#4-对普通用户而言的使用限制" class="headerlink" title="4 | 对普通用户而言的使用限制"></a>4 | 对普通用户而言的使用限制</h5><table><thead><tr><th>维度</th><th>具体表现</th></tr></thead><tbody><tr><td><strong>设备要求</strong></td><td>iPhone 6s 及以上（iOS 17+ 建议），Lightning 有线或支持无线 CarPlay。</td></tr><tr><td><strong>同时运行数量</strong></td><td>iOS 18.4 支持「三排图标」；实际显示取决于车机分辨率。(<a href="https://www.the-sun.com/motors/14129485/apple-car-play-update-drivers-improvement-maps/?utm_source=chatgpt.com">The US Sun</a>)</td></tr><tr><td><strong>地区差异</strong></td><td>某些导航&#x2F;支付&#x2F;餐饮 App 仅在特定国家可上车，例如中国区暂不开放「Apple 钱包加油」。</td></tr><tr><td><strong>多任务</strong></td><td>非导航类 App 在后台仅获有限音频&#x2F;定位权限；切回主屏超过 8 分钟或手动关闭即结束会话。</td></tr></tbody></table><h5 id="常见-Q-A"><a href="#常见-Q-A" class="headerlink" title="常见 Q&amp;A"></a>常见 Q&amp;A</h5><table><thead><tr><th>问题</th><th>解答</th></tr></thead><tbody><tr><td><strong>能在 CarPlay 打开 NAS 上的影片吗？</strong></td><td>不行，现阶段只支持「音频类」DS Music；视频需停车使用原车 USB&#x2F;HDMI 或车企自带系统。</td></tr><tr><td><strong>想在车机上用 VSCode、ES 文件浏览器？</strong></td><td>属于生产力 &#x2F; 文件管理场景，CarPlay 无对应类别，无法过审。</td></tr><tr><td><strong>越狱或使用 CarBridge 能装任何 App 吗？</strong></td><td>理论可行但高风险：系统不稳定、Apple Pay 安全受损、保修被拒，不建议在主力机尝试。</td></tr></tbody></table><hr><ul><li><em>官方</em> 仅开放 8 大类别，核心目标是「行车安全 + 切实刚需」。</li><li>想把新 App 带到 CarPlay？先判断是否符合这 8 类，然后按模板开发并申请 entitlement。</li><li>作为用户，若某款 App 还未上车，说服力最大的途径是：去 App Store 给开发者留言催更，而不是等「万能破解」。</li></ul><h3 id="Part3"><a href="#Part3" class="headerlink" title="Part3"></a>Part3</h3><p>小红书上搜到了 DS audio 的攻略，竟然是群晖套件？评论区还有人推荐 DS music，颜值很高。下载之后我也很喜欢。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250519063025998.png" alt="image-20250519063025998"></p><p>不就是群晖嘛，这年头谁还没一个黑群晖了。公网 IP 不定时被封端口，那懒猫转发了解一下？</p><p>于是把群晖的端口映射出来，就在 APP 中可以远程连接使用了，这不比 Synology QuickConnect 还省事嘛。。。。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250519063938730.png" alt="image-20250519063938730"></p><p>各类软件测试如下</p><ul><li>DS audio 一直提示登录，尽管手机端已经一切正常</li><li>DS music 可以正常访问和听歌，这个软件颜值爱了</li><li>DS music+ 一直加载失败，</li><li>DS cloud 和 DS player 在打开时候好像是一样的 UI，能找到歌曲，但是点击没反应</li></ul><p>最后喜欢的还是 DS music，功能正常还好看啊。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250519064454486.png" alt="image-20250519064454486"></p><p>除了 DS 系列，好像 Q 系列也行，不过吃灰很久很久了。（具体连接待测试）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250519063556595.png" alt="image-20250519063556595"></p><p>用懒猫微服解决了公网转发的问题，再也不担心被运行商封端口了，还有车载大屏（除了有点掉帧），也算圆了一个梦吧。</p><p>你的车有 Carplay 吗？</p>]]></content>
    
    
    <summary type="html">借助懒猫微服穿透服务，让车机远程播放群晖 DS music，免洗白方案</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="炫技" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%82%AB%E6%8A%80/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（六）：使用2fauth共享你的MFA</title>
    <link href="https://blog.no-claw.com/posts/b6b74923/"/>
    <id>https://blog.no-claw.com/posts/b6b74923/</id>
    <published>2025-05-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>标题有点绕口，甚至听起来有点反直觉。</p><p>故事的背景是这样的，去参加了 AWS 的活动给的账户强制开 MFA，但是我们还想团队内部 share 使用，于是产生了这个需求。</p><p>登录到 AWS 的控制台强制开了 MFA，而且在第一次注册的时候强制绑定多因子验证。这也就意味着，其他人如果想登录这个账户就得随时 call 我，然后我去发给他数据验证码，这实在很不方便，所以想到了共享的 MFA 的需求。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516164159950.png" alt="image-20250516164159950"></p><p>头几天逛商店看到的，觉得项目有点意思就下载了，没想到这么快用到了。懒猫商店，一键部署很方便，当成 Saas 服务来用，完全不考虑部署运维的事情。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516163824579.png" alt="image-20250516163824579"></p><p>之前给小伙伴开了懒猫微服的账户，共享了 planka 来看项目进度，这次把 2fauth 的权限也添加给他。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516163911970.png" alt="image-20250516163911970"></p><p>首先我这边先注册管理员的账户，默认是登录页面，需要切换一下。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516164749502.png" alt="image-20250516164749502"></p><p>登录之后会提示绑定一下这个账户的 MFA，我就是为了不在手机上安装 MFA 软件才用这个的，就不要套娃了。反正外面还有懒猫的验证系统，那个还有 TLS 加密，安全码验证。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516164845425.png" alt="image-20250516164845425"></p><p>选择不绑定设备之后，在这里导入需要设置的 MFA，这可以用摄像头或者导入二维码文件。我用的电脑端，所以直接在应用处截图，然后导入到这里了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516164821161.png" alt="image-20250516164821161"></p><p>点击最下面的导入，然后选择二维码 - 上传 就可以了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165311672.png" alt="image-20250516165311672"></p><p>导入之后是这样的，可以二次确认签发机构。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165259782.png" alt="image-20250516165259782"></p><p>然后把生成的 6 位数字填写到 aws 控制台上，就可以成功验证了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165435265.png" alt="image-20250516165435265"></p><p>在 2fauth 控制台上是这样的，点开就可以查看 6 位数字验证码。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165711375.png" alt="image-20250516165711375"></p><p>那么回到一开始的话题，怎么共享给其他账户呢？点击下方 - 管理员 - 用户 ，然后我们来新建一个普通用户。步骤基本和前面的一致。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165543137.png" alt="image-20250516165543137"></p><p>本来以为有用户组一类的概念，把两个用户和 MFA 放在一个组里达到 share 的目的，结果发现这个分组完全是用来区分的 TAG。也没有找到把用户加到组里的操作。那就从管理员导出，再从下一个用户导入吧。</p><p>首先试了二维码，但是导入的时候就提示 server error。于是查了了 wiki，都是其他 MFA 软件导入 2fauth 的。无奈只能只能导出配置文件。名字叫做 2fauth_export.json</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516170143799.png" alt="image-20250516170143799"></p><p>登录新用户的时候新建，然后选择文本文件。导入刚才的配置文件就可以了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250516165311672.png" alt="image-20250516165311672"></p><p>配置文件基本长这样：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">&#123;</span><br><span class="line">  &quot;app&quot;: &quot;2fauth_v5.3.2&quot;,</span><br><span class="line">  &quot;schema&quot;: 1,</span><br><span class="line">  &quot;datetime&quot;: &quot;2025-05-16T08:35:07.676665Z&quot;,</span><br><span class="line">  &quot;data&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;otp_type&quot;: &quot;totp&quot;,</span><br><span class="line">      &quot;account&quot;: &quot;Q&quot;,</span><br><span class="line">      &quot;service&quot;: &quot;AWS SSO&quot;,</span><br><span class="line">      &quot;icon&quot;: null,</span><br><span class="line">      &quot;icon_mime&quot;: null,</span><br><span class="line">      &quot;icon_file&quot;: null,</span><br><span class="line">      &quot;secret&quot;: &quot;secretsss&quot;,</span><br><span class="line">      &quot;digits&quot;: 6,</span><br><span class="line">      &quot;algorithm&quot;: &quot;sha1&quot;,</span><br><span class="line">      &quot;period&quot;: 30,</span><br><span class="line">      &quot;counter&quot;: null,</span><br><span class="line">      &quot;legacy_uri&quot;: &quot;otpauth://totp/&quot;</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>整个过程有点绕，有人说每个人手机安装 google authenticator 扫一下不就好了吗？</p><p><strong>为什么采取这个方案？</strong></p><ol><li><p>之前用手机安装类似软件，每次去三里屯维修的时候都说返厂要把数据抹掉，下次还得重新绑定，还有一些软件只认 MFA 不认人。</p><p>这过程不光折腾的够呛，而且 Apple 本身的问题还要 MFA 来买单。</p></li><li><p>起初是想做一个类似于团队共享 MFA 的场景的，类似于 RBAC，控制起来很灵活，但是实际体验下来是没有达到的。</p></li><li><p>把最早的 MFA 二维码截图 share 出去也能扫，但是不确定有效时间。</p></li></ol>]]></content>
    
    
    <summary type="html">在懒猫微服上部署 2FAuth，实现多人共享 MFA 两步验证码。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="MFA" scheme="https://blog.no-claw.com/tags/MFA/"/>
    
  </entry>
  
  <entry>
    <title>深度长文：NAS大降价的年代，我为何接受溢价来购买懒猫微服（附送回本攻略）</title>
    <link href="https://blog.no-claw.com/posts/d6c5e7b2/"/>
    <id>https://blog.no-claw.com/posts/d6c5e7b2/</id>
    <published>2025-05-15T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最早知道懒猫微服，是去年的时候，那个时候最直观的感觉就是价格比同类型产品要贵一些，但是很极客风，不过硬件配置比传统 NAS 要高出很多。但在现在各种小主机盛行的年代，这台机器就显得性价比不高，甚至有些人认为有割韭菜的嫌疑。</p><p>今年朋友又过来推荐，说是售后很好，可以根据自己的需求来答疑，比如把三方监控放在存在 NAS 里，比如想用私服搭建游戏服务器等等，而网上不管怎么样，还有说情绪价值一定给拉满的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515205050488.png" alt="最早的技术答疑，咨询上架应用"></p><p>第一次咨询的时候，是和 CEO 通了个电话，抱着将新将疑的态度购买回来，拆箱，测评。相信其实很多人即使没听过王勇，也一定听过或者用过 Deepin。大学的时候使用过一段时间的 deepin，很多细节确实符合国人的使用习惯。这个背书对于技术人来说，实在是一下子路转粉。想想自己在电话里还跟对方说，其实专业的技术人员，不用和我说这么直白的词，再想想王总在 Deepin 以及 Emacs 方面的贡献， 实在是有些惭愧。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515212527871.png" alt="和 CEO 的售前沟通"></p><p>我本身是开发者，问题是技术细节相关的，比如为何这个实现和群晖类不一样，对某处设计比较反常识的地方询问和拆解，总的来说，像是上了侠客岛一样，平时自认为是开发人员里面最懂 Infra 的，结果到这里谁的 Linux 都比我玩的好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515201725726.png" alt="售后秒杀难题"></p><p>目前重度使用了一周多，每天都会在 VIP 群里问问题。主要把维护的问题解决出来，好像附送了一个终身的云厂商支持一样。从他们的宣传来看，7 _ 18 的支持显得更加实在一些，服务相比海底捞有过之无不及。我本身用过不少 7 _ 24 小时支持的云厂商，要么低峰时候找不到人，而 24 小时支持又何尝不是对技术从业者的压榨呢？比如随时 on-call，倒班机制是我本人深恶痛绝的，有些厂家号称是 7 * 24 小时支持，但是经常已读乱回要么不回，或者干脆说这个问题和他们产品的交叉是涉及第三方，然后索性不管了。有意思的是，当使用两个公司交叉的业务时，都要让我去找对方。但是在懒猫这里就不会发生这样的问题，之前用的商店里的 dify 有问题，他们去找移植应用的人去修改了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515201839290.png" alt="商店应用反馈"></p><p>还有一个卖点，是硬件终身售后，甚至包括磁盘和后续的数据恢复（前提是不加密），有些推吞吐量高要求的情况甚至可以做 Raid0，然后外接 NAS 或者硬盘仓备份，所以这不是一款后端存储的产品，而是放在存储和用户之间的加速器，作为家庭的边缘算力，前面接 MBP 后面接存储池这样子。</p><p>商店目前上架了 1000+的应用，虽然官方的应用不多，但是很多三方应用都是他们的开发人员移植的，于是后来才有了越来越多的开发者也跟着移植的过程，在移植的过程中，可以学 docker-compose 的用法，以及跨架构打包 docker image ，还有单点登录的集成。这些都是我的兴趣点，而且也想学一学里面设计的机制，大学毕业的时候我想设计一款 NAS，那也仅仅是基于 centos 做了一些改动，后来买了威联通，虽然不常开案例问问题，但问的也仅仅是关于这个产品本身的东西，包括专业程度和响应级别都不是能够一概而论的。甚至连 trouble shooting 上传日志都很方便。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515201517917.png" alt="上传日志"></p><p>全容器化的服务以及对操作系统的修改，可以看出沿袭了当年在 deepin 的风骨。包括应用商店在内，很多系统组件都完全采用用容器托管。还有自己的单点登录系统，而对于 OIDC 的支持其实很多企业都没有做到。还有一点不得不提的，开发者的社区很活跃（主要指的是微信群和上架应用商店），每天都会有几位开发人员默默的上架应用和攻略。慢慢的我也熟悉了把 docker images 转换成为懒猫商店的模式。也上架了自己的几个应用，有原创的，也有把喜欢的开源项目移植过来。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515212732003.png" alt="image-20250515212732003"></p><p>最喜欢的原生应用是网盘和清单，首先说网盘曾经有一篇为什么没有人去做网盘的帖子，讲述了网盘研发成本高，就连曾经宣称用不限速的阿里云盘也变节了。改善 NAS 生态是刀山火海、暗礁遍布，却仍要做那一股清流；研发与售后成本明摆在前，却仍坚持全线自研，把服务做到极致。清单有种小清新的感觉，极简风格，日常记录一些 todo，主要同步之后多平台编辑实在很舒服。打破了关于以前产品自带的软件都很烂的固有观念。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250515212827408.png" alt="image-20250515212827408"></p><p>想起来《琅琊榜》中的一句话拿来形容创始人，“如此愚蠢，却又如此有胆识的人，已经很久没见到了。”</p><p>我一直相信技术是服务生活的，但慢慢的变成了炫技以及慢慢变成了改需求以及最后变成了漫长的牛马生涯，在学生时代一直有一个远景，做一款全平台的软件自己用，后来发现学习成本巨大，而且也没有资金外包出去，虽然这几年接触了 flutter，但也没有构建一个全新的跨平台产品出来。</p><p>有了懒猫微服之后，这一切都解决了，只需要打包好 docker image，如果可以的话就上架商店给其他人用。用公网访问，TLS 证书卸载这些都一步搞定。当我们默默的喷绿联，极空间丢数据，群晖如何守旧不肯升级 CPU，以及限制磁盘认证的问题。曾经我们还忽略了这样一款从操作系统，软件生态，甚至应用商店。这款机器比我之前 DIY 构想的还要完善，完美。</p><p>我想去拆解他的技术细节。但是不想再出来一个商业竞品来扰乱这份宁静。在我看来这是一款充满着技术者热情和情怀的产品。</p><p><strong>附送：懒猫微服社区激励机制一览</strong></p><table><thead><tr><th>贡献类别</th><th>具体动作</th><th>奖励金额</th><th>备注条件</th></tr></thead><tbody><tr><td><strong>应用移植</strong></td><td>成功将一款高质量的自托管应用移植并上架商店</td><td><strong>100 元&#x2F;款</strong></td><td>- 必须功能正常- 开源应用需标注上游作者- 若多人移植同一应用，仅首位上架者得奖</td></tr><tr><td><strong>对接账户系统 &#x2F; 网盘右键菜单</strong></td><td>在移植基础上完成接口对接</td><td><strong>+50 元&#x2F;款</strong></td><td>- 自己移植并对接：共 <strong>150 元&#x2F;款</strong>- Fork 他人应用并补充对接：<strong>50 元&#x2F;款</strong></td></tr><tr><td><strong>应用攻略编写</strong></td><td>发布含截图且经验证可行的攻略，并关联商店应用</td><td><strong>50 元&#x2F;篇</strong></td><td>鼓励分享使用经验，惠及社区</td></tr><tr><td><strong>不予奖励的应用类型</strong></td><td>纯网页游戏、离线 Web App、纯数据库软件等</td><td>——</td><td>可自由上传，但暂无红包激励</td></tr></tbody></table><blockquote><p><strong>核心要点</strong></p><ol><li><strong>先到先得</strong>：同款应用仅首位合规上架者获奖。</li><li><strong>质量至上</strong>：功能正常、信息完整方可审核通过。</li><li><strong>额外加成</strong>：完成账户系统&#x2F;右键菜单对接可叠加奖励。</li><li><strong>知识共享</strong>：高质量攻略同样有奖，鼓励经验传播。</li></ol></blockquote><p>社区激励机制：<a href="https://developer.lazycat.cloud/store-rule.html">https://developer.lazycat.cloud/store-rule.html</a></p><p>懒猫打金服：<a href="https://playground.lazycat.cloud/#/guideline/448">https://playground.lazycat.cloud/#/guideline/448</a></p>]]></content>
    
    
    <summary type="html">深度分析 NAS 降价潮中选择懒猫微服的理由，附回本攻略</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（五）：文件上传到懒猫网盘，SMB 电视盒子观影</title>
    <link href="https://blog.no-claw.com/posts/b62d86a5/"/>
    <id>https://blog.no-claw.com/posts/b62d86a5/</id>
    <published>2025-05-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为一个合格的 NAS，肯定要有文件共享的功能，一般我们常用的是 SMB，NFS 和 WebDav 这三种，然后需要设置共享目录和用户权限。</p><p>懒猫网盘提供了一个开箱即用的方案，直接通过 APP 把网盘的文件夹映射自动挂载到本地，不需要像 Linux 那样 mount，也不需像 window 一样新建磁盘映射：</p><p>我们看看以前要挂载一个盘有多麻烦:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># Debian/Ubuntu</span><br><span class="line">sudo apt install cifs-utils</span><br><span class="line">sudo mkdir /mnt/smb_share</span><br><span class="line">sudo mount -t cifs //SERVER_IP_OR_NAME/SHARE_NAME /mnt/smb_share -o username=SMB_USER,password=SMB_PASSWORD,domain=WORKGROUP</span><br><span class="line"></span><br></pre></td></tr></table></figure><span id="more"></span><p>如果需要开机自动挂载，还得改&#x2F;etc&#x2F;fstab 里面的条目。但是，懒猫网盘可以开箱即用，不管你是用浏览器，APP，还是用访达挂载 SMB 都访问都可以。属实是解放了 Mac 党的电脑空间。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514104946937.png" alt="image-20250514104946937"></p><p>在网盘中点击自己的头像，然后<strong>设置</strong> - <strong>网络服务</strong>这里，可以看到设置。甚至点击起开内网服务，还会给一个 IP 地址的 SMB 地址：</p><p><code>smb://ip/user-name</code>，电视盒子不能安装懒猫 app，但是有了 IP 地址之后就可以连接 SMB 了～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514105241675.png"></p><p>然后就是当贝盒子这里啦，如果你是小米盒子或者其他的盒子，只要文件管理器支持 SMB 就 OK</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113806260.png" alt="image-20250514113806260"></p><p>进入文件管理器，选择 <strong>局域网共享连接</strong>。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113737029.png" alt="image-20250514113737029"></p><p>然后输入懒猫微服的 IP 地址，用户名密码就是微服 APP 的， 这一套有点 AD 域的感觉了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113708677.png" alt="image-20250514113708677"></p><p>如果你的文件管理器默认没有 SMB 也没有关系，还可以使用第三方应用进行 SMB 连接，比如这个 Github 项目，可以从 release 中下载 APK 进行安装。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113634937.png" alt="image-20250514113634937"></p><p>连接成功后，可对文件进行扫描和管理。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113612422.png" alt="图片"></p><p>通过以上配置，就可以在电视盒子上通过 SMB 连接 NAS，开心的观看的 4K 电影了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250514113307833.png" alt="image-20250514113307833"></p>]]></content>
    
    
    <summary type="html">文件上传到懒猫网盘，通过 SMB 协议在电视盒子上直接观影。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="Samba" scheme="https://blog.no-claw.com/tags/Samba/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服进阶心得（一）：M芯片移植懒猫应用构建Docker镜像的常见问题排查及解决方案</title>
    <link href="https://blog.no-claw.com/posts/126ed22c/"/>
    <id>https://blog.no-claw.com/posts/126ed22c/</id>
    <published>2025-05-13T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是使用 Apple silicon 的 MacOS 移植到懒猫商店的踩坑记录，希望能够给大家带来帮助</p></blockquote><p>Apple silicon 很好，在这年几乎带起来 ARM 的生态。但这也拉开了 ARM 和 X86 之战，用户在两大生态中穿梭，只能增加自己应用的兼容性。就比如说用来打包的 Docker image，尽管编程语言和操作系统都在底层屏蔽了硬件架构，但是容器还得用相同架构的。</p><p>这是之前移植开源项目时候忘记打包不同架构的 image 而直接推送到懒猫镜像仓库导致的问题。MacOS 默认打包了 ARMv8 架构的镜像，在 X86 上也无法运行。</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">pg-docker run -p 5000:5500 registry.lazycat.cloud/u04123229/you/doudizhu-scorer:d1d9085174c0bf8c</span><br><span class="line">WARNING: The requested image<span class="string">&#x27;s platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v4) and no specific platform was requested</span></span><br></pre></td></tr></table></figure><span id="more"></span><p>由于打包的时候容器一直在反复重启，所以在 dozzle 上也没有什么明显的报错，所以有个办法就是 ssh 进去用终端<strong>pg-docker run</strong>，这样所见即所得。dozzle 地址：<a href="https://dev/">https://dev</a>.<name>.heiyu.space&#x2F;dozzle&#x2F;</p><p>于是重新打包，跨架构打包时候需要使用 buildx，当然前提是需要里面的运行时和代码也是跨平台的。</p><p>我们先来看概念和原理：</p><p><code>buildx</code> 是 Docker 提供的一种扩展功能，它基于 <strong>BuildKit</strong> 引擎，目的是为 Docker 提供更强大的构建功能，包括：</p><ul><li><strong>跨平台构建</strong>：支持在一种平台上构建适用于多种平台的 Docker 镜像。</li><li><strong>缓存管理</strong>：支持高效的缓存管理机制，能够减少重复构建的时间。</li><li><strong>多阶段构建</strong>：支持复杂的多阶段构建流程。</li></ul><p><code>buildx</code> 使 Docker 能够生成多平台的镜像，这意味着你可以在一个平台上（例如 ARM 或 x86）构建适用于其他平台（如 <code>x86_64</code>、<code>arm64</code>、<code>armv7</code> 等）的 Docker 镜像。</p><p><code>docker buildx build</code> 通过指定 <code>--platform</code> 参数来告诉 Docker 在构建时要生成哪些平台的镜像。例如，<code>linux/amd64</code> 和 <code>linux/arm64</code> 就分别对应 x86 和 ARM 架构。</p><p>在 <code>buildx</code> 构建完成后，你得到的不是一个单独的镜像，而是一个支持多平台的 <strong>manifest list</strong>，这个列表包含了不同架构的镜像。这个列表可以推送到 Docker Hub 等镜像仓库，客户端在拉取时，会根据自己的硬件架构自动选择合适的镜像。</p><p>这意味着，我们可以通过同一个镜像标签（如 <code>your_image_name</code>）来支持多个平台的 Docker 镜像，而用户在拉取时会自动选择适合自己平台的镜像。</p><ol><li><strong>准备构建环境</strong>：<br>Docker Buildx 会首先准备并选择一个构建器（builder）。这个构建器负责在指定的平台上执行构建任务。</li><li><strong>选择平台</strong>：<br>使用 <code>--platform</code> 参数来选择目标平台，Docker 会通过 QEMU 模拟器或者本地平台来执行构建。</li><li><strong>构建镜像</strong>：<br>在选择平台后，Buildx 会根据 Dockerfile 和其他构建上下文开始构建镜像。它会处理平台特定的依赖和构建步骤。</li><li><strong>生成适配镜像</strong>：<br>对于每个平台，Docker Buildx 会生成一个特定的镜像。例如，对于 <code>linux/amd64</code> 和 <code>linux/arm64</code>，它会分别为这两个平台构建独立的镜像，并将它们绑定在一个 <strong>manifest list</strong> 中。</li><li><strong>推送镜像</strong>：<br>完成构建后，你可以使用 <code>--push</code> 参数将包含多个架构镜像的 <strong>manifest list</strong> 推送到 Docker Hub 或其他镜像仓库。这个清单包含了多个平台的镜像，当用户从仓库拉取时，Docker 会自动选择与用户当前平台兼容的镜像</li></ol><h4 id="然后来实操"><a href="#然后来实操" class="headerlink" title="然后来实操"></a>然后来实操</h4><ol><li>确保 Docker 版本支持 Buildx,用<code>docker buildx version</code> 来验证&#x2F;</li><li>创建并使用新的 Builder</li><li>打包的时候加上平台参数：<code>--platform linux/amd64,linux/arm64</code></li></ol><p>具体命令如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker buildx version</span><br><span class="line">docker buildx create --use --name multiarch-builder</span><br><span class="line">docker buildx build --platform linux/amd64 -t your_image_name .</span><br><span class="line">docker buildx build --platform linux/amd64 -t your_dockerhub_username/your_image_name --push .</span><br></pre></td></tr></table></figure><p>在这过程中，我们可能还会使用<code>docker tag</code> ，这个命令可以将一个现有的镜像打上新的标签（tag），通常用于将镜像标记为自己的名字或指定版本。这对于推送镜像到 Docker Hub 或其他镜像仓库时非常有用。</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">docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]</span><br></pre></td></tr></table></figure><ul><li>**<code>SOURCE_IMAGE[:TAG]</code>**：要打标签的源镜像。<code>TAG</code> 是可选的，如果不指定，默认是 <code>latest</code>。</li><li>**<code>TARGET_IMAGE[:TAG]</code>**：新的目标标签，通常你可以为镜像指定一个新的名字或版本号。</li></ul><p>假设你有一个名为 <code>my_image:latest</code> 的镜像，并且你希望将它标记为属于你自己（例如，<code>your_dockerhub_username/my_image:latest</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">docker tag my_image:latest your_dockerhub_username/my_image:latest</span><br></pre></td></tr></table></figure><p>这条命令会将 <code>my_image:latest</code> 镜像打上 <code>your_dockerhub_username/my_image:latest</code> 的标签。</p><p>推送到 dockerhub 之后，然后就可以像往常一样使用 Docker 了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull your_dockerhub_username/your_image_name</span><br><span class="line">docker run your_dockerhub_username/your_image_name</span><br></pre></td></tr></table></figure><p>如果想走 Github action 一键打包 image 的话，是这样：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">Push</span> <span class="string">Docker</span> <span class="string">Image</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">tags:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;v*&quot;</span> <span class="comment"># 仅在 tag push（如 v1.0.0）时触发</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build-and-push:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">source</span> <span class="string">code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Check</span> <span class="string">DockerHub</span> <span class="string">secrets</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          if [ -z &quot;$&#123;&#123; secrets.DOCKER_USERNAME &#125;&#125;&quot; ] || [ -z &quot;$&#123;&#123; secrets.DOCKER_PASSWORD &#125;&#125;&quot; ]; then</span></span><br><span class="line"><span class="string">            echo &quot;❌ ERROR: DOCKER_USERNAME or DOCKER_PASSWORD is missing&quot;</span></span><br><span class="line"><span class="string">            exit 1</span></span><br><span class="line"><span class="string">          fi</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">QEMU</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/setup-qemu-action@v3</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Docker</span> <span class="string">Buildx</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/setup-buildx-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">install:</span> <span class="literal">true</span> <span class="comment"># ✅ 自动创建默认 builder</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Docker</span> <span class="string">login</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/login-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_USERNAME</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_PASSWORD</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Extract</span> <span class="string">tag</span> <span class="string">name</span></span><br><span class="line">        <span class="attr">id:</span> <span class="string">vars</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">echo</span> <span class="string">&quot;TAG=$&#123;GITHUB_REF#refs/tags/&#125;&quot;</span> <span class="string">&gt;&gt;</span> <span class="string">$GITHUB_ENV</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">push</span> <span class="string">Docker</span> <span class="string">image</span> <span class="string">(multi-arch</span> <span class="string">+</span> <span class="string">latest)</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/build-push-action@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">          <span class="attr">push:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">platforms:</span> <span class="string">linux/amd64,linux/arm64</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            cloudsmithy/flask-demo:$&#123;&#123; env.TAG &#125;&#125;</span></span><br><span class="line"><span class="string">            cloudsmithy/flask-demo:latest</span></span><br></pre></td></tr></table></figure><p>没有把 lzc-cli 写进去的原因是目前只能从终端命令行查看到推送到懒猫仓库的镜像命令，目前还不能存到一个中间位置，所以做了一个通用的版本。</p><p>事情到这里本来应该结束的，但似乎有了新的故事。</p><h4 id="故事-1：无法打包"><a href="#故事-1：无法打包" class="headerlink" title="故事 1：无法打包"></a>故事 1：无法打包</h4><p>某次在打包的过程种突然报错，期间一度以为 Orbstack 出现了问题，于是卸载重装，重启电脑，均无效，GPT 和 deepseek 也只是让我检查网络连接。期间重新 docker pull 也是没问题的。</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">characters ERROR: failed to dial qRPC: rpc error:code = Internal desc = rpc error: code = Internal desC = header key &quot;x-docker-expoSe-session-name&quot; contains value with non-printable ASCI #2793</span><br></pre></td></tr></table></figure><p>无奈只能 Google，在 issue 里有一个评论，打包目录不能出现中文。(我的 OS 默认中文) 参考链接：<a href="https://github.com/docker/buildx/issues/2793">https://github.com/docker/buildx/issues/2793</a></p><h4 id="故事-2：构建之后没有输出"><a href="#故事-2：构建之后没有输出" class="headerlink" title="故事 2：构建之后没有输出"></a>故事 2：构建之后没有输出</h4><p><code>docker buildx build --platform linux/amd64 -t your_image_name .</code></p><figure class="highlight plaintext"><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">WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这是使用 <code>docker buildx</code> 构建镜像时，指定了 <code>docker-container</code> 驱动，但是没有使用 <code>--push</code> 或 <code>--load</code> 参数。结果是，构建的镜像只会保留在构建缓存中，而不会被推送到镜像仓库或加载到本地 Docker 环境中。</p><p>我们可以通过两种方式之一来明确指定输出目标，避免出现此警告：</p><h5 id="1-使用-push-将镜像推送到远程仓库："><a href="#1-使用-push-将镜像推送到远程仓库：" class="headerlink" title="1. 使用 --push 将镜像推送到远程仓库："></a>1. <strong>使用 <code>--push</code> 将镜像推送到远程仓库</strong>：</h5><p>如果你希望构建的镜像推送到 Docker Hub 或其他 Docker 镜像仓库，可以使用 <code>--push</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">docker buildx build --platform linux/amd64 -t your_image_name --push .</span><br></pre></td></tr></table></figure><p>这将把镜像推送到 Docker 仓库，而不是仅保留在本地构建缓存中。</p><h5 id="2-使用-load-将镜像加载到本地-Docker-环境："><a href="#2-使用-load-将镜像加载到本地-Docker-环境：" class="headerlink" title="2. 使用 --load 将镜像加载到本地 Docker 环境："></a>2. <strong>使用 <code>--load</code> 将镜像加载到本地 Docker 环境</strong>：</h5><p>如果你想将构建的镜像加载到本地 Docker 环境中以便后续使用（例如运行容器），可以使用 <code>--load</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">docker buildx build --platform linux/amd64 -t your_image_name --load .</span><br></pre></td></tr></table></figure><p>这会将构建的镜像加载到本地 Docker 环境，使你可以在本地运行、调试或进行其他操作。</p><h4 id="故事-3：无法同时保存双平台-Image-到本地"><a href="#故事-3：无法同时保存双平台-Image-到本地" class="headerlink" title="故事 3：无法同时保存双平台 Image 到本地"></a>故事 3：无法同时保存双平台 Image 到本地</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker buildx build --platform linux/amd64,linux/arm64 -t cloudsmithy/shuangpin:latest . --load</span><br><span class="line">[+] Building 0.0s (0/0) docker-container:stoic_hellman</span><br><span class="line">ERROR: docker exporter does not currently support exporting manifest lists</span><br></pre></td></tr></table></figure><p><code>--load</code> 只适用于单平台构建。如果你在跨平台构建（如 <code>linux/amd64,linux/arm64</code>）时使用 <code>--load</code>，则只会将构建的默认平台镜像加载到本地，不会加载所有平台的镜像。跨平台构建时，通常需要使用 <code>--push</code> 将所有平台的镜像推送到远程仓库</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">docker buildx build --platform linux/amd64 -t cloudsmithy/shuangpin:latest . --load</span><br></pre></td></tr></table></figure><p>使用 <code>--load</code> 时，镜像会被加载到本地 Docker 守护进程中。对于大镜像，加载过程可能需要较长的时间和较多的本地存储空间。因此，如果镜像非常大，可能需要考虑是否使用 <code>--push</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">docker buildx build --platform linux/amd64,linux/arm64 -t cloudsmithy/shuangpin:latest . --push</span><br></pre></td></tr></table></figure><h4 id="故事-3：懒猫仓库黑魔法"><a href="#故事-3：懒猫仓库黑魔法" class="headerlink" title="故事 3：懒猫仓库黑魔法"></a>故事 3：懒猫仓库黑魔法</h4><p>对了，关于文档上提到的懒猫的 registry 不能在微服外面用，黑魔法的限制其实就是加了认证，直接返回 401.</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">docker run -p 5000:5500 registry.lazycat.cloud/u04123229/you/doudizhu-scorer:d1d9085174c0bf8c</span><br><span class="line">Unable to find image <span class="string">&#x27;registry.lazycat.cloud/u04123229/you/doudizhu-scorer:d1d9085174c0bf8c&#x27;</span> locally</span><br><span class="line">docker: Error response from daemon: Head <span class="string">&quot;https://registry.lazycat.cloud/v2/u04123229/you/doudizhu-scorer/manifests/d1d9085174c0bf8c&quot;</span>: no basic auth credentials.</span><br></pre></td></tr></table></figure><h4 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h4><p>希望这篇文章能够帮助大家在将开源项目移植到懒猫商店时，避免遇到一些常见的坑和问题。祝大家顺利解决跨平台构建和镜像推送中的挑战，提升开发效率！</p>]]></content>
    
    
    <summary type="html">Apple M 芯片构建懒猫微服 Docker 镜像的常见问题排查与解决方案。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="进阶" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E8%BF%9B%E9%98%B6/"/>
    
    
    <category term="Docker" scheme="https://blog.no-claw.com/tags/Docker/"/>
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（四）：完全DNS访问，和本地代理冲突了怎么办？</title>
    <link href="https://blog.no-claw.com/posts/36782d5f/"/>
    <id>https://blog.no-claw.com/posts/36782d5f/</id>
    <published>2025-05-12T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>刚拿到懒猫微服的时候，了解到这个机器完全使用 DNS 来访问是很吃惊的。拒不完全使用经验，大概是机器里部署了一套私有的 DNS server，然后广播到整个局域网。而公网上的则是 heiyu.space，通过 whois 查看，公网的 domain 是在腾讯云购买的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250513092022572.png" alt="image-20250513092022572"></p><p>所以应该是两套的解析结构，局域网访问的时候，就先用机器部署的私有 domain 进行解析，如果使用流量或者在外边，就是走互联网上 DNSPod 的解析记录。这个结论属于猜测，因为很多公有云也确实四这么做的，一个公开托管的 domain 用来互联网解析，一个 VPC 内的 private domain 用来解析 VPC 内部的地址。</p><p>懒猫微服和传统的 NAS 又很大的不同，如果作为小白玩家可以很快上手，当做 Sass 服务来用。但对于专业玩家，总有一种技术的强迫症，总用抽丝剥茧，从 Saas 一点点解析到 Iass，然后一点把懒猫编程能够公开访问的私有云。</p><p>比如网络。可以通过 dig 或者 nslookup 来解析</p><span id="more"></span><figure class="highlight plaintext"><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">dig xxx.heiyu.space +short</span><br><span class="line">dig xxx.heiyu.space AAAA +short</span><br></pre></td></tr></table></figure><p>但是，DNS 解析这里慢慢就出现问题了。在某次上传文件到懒猫网盘的时候，我发现速度慢的可怜，几乎是走了公网。在 VIP 答疑群里得知，流量应该是从代理转了一圈，然后回来的，所以慢，剩下的就是解决这个问题了。</p><p>那么办法就是放行白名单，不让他走代理，由于是 DNS 访问，而很多代理的规则是根据域名匹配的，所以要去改这个匹配规则。当然如果你用 nmtui 配置静态 IP 地址的话，那么内网访问也是没有问题了，直接走上级路由的默认路由表即可。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250513094205439.png" alt="image-20250513094205439"></p><p>而白名单主要是放行， _.heiyu.space 和 _.lazycat.cloud 这两个域名，heiyu.space 是穿透服务，lazycat.cloud 是官网和论坛。</p><p>不同的软件有不同的设置办法，比如说用 DOMAIN-SUFFIX 来替代域名的泛解析，所以放行的时候 heiyu.space 这这样子就好。我在修改配置文件的时候用 DOMAIN-SUFFIX 匹配*.heiyu.space 不生效，花了不少的时间。实际不需要再写一次 * 号。</p><p><img src="https://dl.playground.lazycat.cloud/guidelines/459/9ed1bbce-73b0-4ce9-8e22-fb20d6c8b21c.png" alt="image.png"></p><p>而最终落到配置文件上就是这样的。（之前写 DOMAIN-SUFFIX,*.lazycat.cloud,DIRECT）一直不生效。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rules:</span><br><span class="line">- DOMAIN-SUFFIX,lazycat.cloud,DIRECT</span><br><span class="line">- DOMAIN-SUFFIX,heiyu.space,DIRECT</span><br><span class="line">- DOMAIN-SUFFIX,deepseek.com,DIRECT</span><br></pre></td></tr></table></figure><p>也总结一下其他规则吧，最常见的类型有这些：</p><h3 id="1）DOMAIN"><a href="#1）DOMAIN" class="headerlink" title="1）DOMAIN"></a>1）DOMAIN</h3><ul><li>只匹配<strong>某个域名本身</strong>。</li><li>举例：<code>gs.apple.com</code> → 只有访问 <code>gs.apple.com</code> 才会命中。</li></ul><h3 id="2）DOMAIN-SUFFIX"><a href="#2）DOMAIN-SUFFIX" class="headerlink" title="2）DOMAIN-SUFFIX"></a>2）DOMAIN-SUFFIX</h3><ul><li>匹配<strong>所有以这个后缀结尾的域名</strong>。</li><li>举例：<code>apple.com</code> → <code>gs.apple.com</code>、<code>itunes.apple.com</code> 都会命中。</li></ul><h3 id="3）DOMAIN-KEYWORD"><a href="#3）DOMAIN-KEYWORD" class="headerlink" title="3）DOMAIN-KEYWORD"></a>3）DOMAIN-KEYWORD</h3><ul><li>匹配<strong>包含某个关键词的所有域名</strong>。</li><li>举例：<code>apple</code> → <code>apple.com</code>、<code>gs.apple.com</code>、<code>appleabc.xyz</code> 都会命中。</li></ul><h3 id="4）IP-CIDR"><a href="#4）IP-CIDR" class="headerlink" title="4）IP-CIDR"></a>4）IP-CIDR</h3><ul><li>匹配<strong>某个 IP 地址段</strong>。</li><li>举例：<code>192.168.0.0/16</code> → 匹配 192.168 开头的所有 IP。</li></ul><blockquote><p>这些是规则写法里最基本的几种，掌握了就能应对绝大多数情况。</p></blockquote><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/ae810d7f-7410-489c-9017-987a12234160.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服完全 DNS 访问与本地代理冲突的解决方案。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="代理" scheme="https://blog.no-claw.com/tags/%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（三）:懒猫智慧屏，以为是地表最强电视盒子，结果是闺蜜机</title>
    <link href="https://blog.no-claw.com/posts/5a9099c0/"/>
    <id>https://blog.no-claw.com/posts/5a9099c0/</id>
    <published>2025-05-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>过年那阵子，为了看春晚不卡顿，打算换一个当贝盒子，尽管这些年刷过矿机当盒子或者 Armbian，但是仍然对当贝系列情有独钟，而时过境迁，最后发现在当年风靡市场的网红盒子，不过 RK3566 而已，根据不同的 RAM+ROM 的组合来卖到更高的价格，实在是没有什么性价比。</p><p>于是想到 X86 能性能会更加好一些，比如 Android TV 这种固件，不过这种基本都是海外版本，纯净的基本什么都要自己捣鼓，而 B 站评论的第三方链接又很担心安全问题。</p><p>这问题一直持续到我购买了懒猫微服，高性能的 X86 主机，还带 HDMI（目前 Typec 不支持供电和视频传输），和飞牛的原生 Gnome 输出不同的是懒猫智慧屏其实是商店里面一个独立的 APP，本质上相当于客户端，需要手机扫码授权才能使用</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250512090024831.png" alt="image-20250512090024831"></p><p>登录之后是这样的，其实就是在原来 APP 的基础上加了一个 Chrome 浏览器，然后其他的应用也能在显示器上打开。右上角依次是搜索栏，软件商店和登出按钮。接下来就是可以愉快的观影了，个人体验新开一个账户，然后设置应用白名单体验会更加的好～（但是用手机遥控的时候会提示，智慧屏正在被其他用户使用是否停止 hhhh）</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250512090050827.png" alt="image-20250512090050827"></p><h4 id="我尝试的观影流程如下："><a href="#我尝试的观影流程如下：" class="headerlink" title="我尝试的观影流程如下："></a>我尝试的观影流程如下：</h4><ol><li>把视频传输到<strong>懒猫网盘</strong>（拖拽上传或者 Samba）</li><li>打开<strong>视频播放器</strong>（其他播放器也可）</li><li>然后选中网盘文件就可以播放啦</li></ol><p>目前支持手机端遥控，类似市面上的盒子助手， 我连接了 Action III ，能够愉快的观影了。<img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250512110003104.png" alt="image-20250512110003104"></p><p>由于是内置的应用，所以不存在电视盒子广告乱象的问题，11 代 I5 拿来观看 4K 也是轻松秒杀，需要什么就往网盘传什么。高端的需求只需要简单的办法。感觉不用买 Apple TV 了，如果需要其他软件比如 jellyfin 啥的也可以自己部署～。</p><p>机器有 USB 还能插键鼠，接好外设的时候记得重启一下，应该是容器部署的原因，让容器在启动的时候再读一遍&#x2F;dev&#x2F;bus&#x2F;usb 下的设备。除了观影，接会议室的演示大屏幕也不错～</p><p>毕竟这个页面，连 debian 虚拟机都能使用，其他的应用也不在话下。想用 X86 安卓的话，直接安装到虚拟机里～</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250512103811587.png" alt="image-20250512103811587"></p><p><strong>为啥说可以成为闺蜜机呢？</strong></p><p><strong>为啥说可以成为闺蜜机呢？</strong></p><p>某天在商场的时候看到一个大大的带支架的还可以随时移动的平板叫做闺蜜机。</p><p>那么懒猫微服 + HDMI 投屏器&#x2F;毫米波投屏器 + 可移动支架，是不是很像？</p><p>某宝上还有这样的改装套餐，解决显示器供电看来就 OK。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250512095959092.png" alt="image-20250512095959092"></p><ol><li>市面上没有好用的电视盒子？选懒猫！</li><li>市面上没有良心的在线网盘？选懒猫！</li><li>市面上没有便宜的闺蜜专机？选懒猫！</li></ol><p>市面上的闺蜜机动辄大几千，配置又差强人意，还不如买懒猫微服，还有专业的售后陪玩，探索无限可能！</p><p>什么？你说闺蜜机还得有语音助手， AI 美颜，娱乐互动？快登懒猫微服用 Docker 来部署吧，还有机会上架懒猫商店哦～</p>]]></content>
    
    
    <summary type="html">懒猫智慧屏体验，本以为是电视盒子，没想到是一台闺蜜机。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="电视盒子" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%A7%86%E7%9B%92%E5%AD%90/"/>
    
  </entry>
  
  <entry>
    <title>车机锁 Wi‑Fi 奸商跑路，高楼组 Mesh 极客破局</title>
    <link href="https://blog.no-claw.com/posts/fc9b2cf2/"/>
    <id>https://blog.no-claw.com/posts/fc9b2cf2/</id>
    <published>2025-05-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>五一假期的时候家里人打算自驾游，才发现车载地图已经很多年没更新了，找不到要去的地方，于是打算重新弄下，来一个全新的体验。</p><p>车载的导航是安卓，虽然已经很久没更新过了。从早些年折腾刷机的经验来看，就算不能连接 wifi，也应该是能够用 USB 转接的。尝试一圈，基本是这个情况，流量卡失效，wifi 功能被禁用掉，USB 转接有线网卡也无法识别。</p><p>于是打电话到 4S 店询问是否能够提供些许的支持，他们转到技术，然后告诉只能把车开到他们那里去看，不提供上门的支持。而作为这个年代的资深消费者，已经有千万遍劳心费力的折腾最后被售后三两句打发走的经历。 好说歹说发一张图片过去，尽管对方语气中透着些许的不耐烦。“你这个是赠送的，我们不了解情况，不是我们原厂的东西，而且很多车机是无法升级的”。于是又开始推销了自己的产品，问到是否还会遇到上述情况，还得 case by case 来看。“你这个车已经买了很多年了，早就没有保修了。”</p><span id="more"></span><p>我的需求无外服这三种：</p><ol><li>更新高德地图数据，不管是更新版本还是导入离线包。</li><li>升级 carplay，需要买一个盒子，还需要做安装 Autokit 硬件检测，最快也要明天到。</li><li>刷一个有 wifi 功能的正常版本，让我正常更新数据。（最后也没实现，不过估计新版本更卡）</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511232048071.png" alt="image-20250511232048071"></p><p>朋友帮我查了下这个版本的卡槽，需要把整个导航拆下来，不过弄不好还耽误用倒车影像，遂放弃。</p><p>在这个论坛：<a href="http://www.allmost.org/2019/11/android-head-unit-root-device-model.html">http://www.allmost.org/2019/11/android-head-unit-root-device-model.html</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511231907917.png" alt="image-20250511231907917"></p><h3 id="糟糕的高德地图"><a href="#糟糕的高德地图" class="headerlink" title="糟糕的高德地图"></a>糟糕的高德地图</h3><p>无法联网更新，首先想到的是离线数据包，于是致电<strong>400-810-0080</strong>，这次都是机器人接听，无法转人工。</p><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">【高德地图】尊敬的高德用户您好，您咨询的地图数据升级问题，操作方法：请您点击链接 https://auto.amap.com/download/map_data#public  ，选择对应版本下载即可；同时您可在下载界面点击我不会安装查看说明， 感谢您对高德的关注与支持！</span><br></pre></td></tr></table></figure><p>在年初的时候还针对 Mac 端的高德地图无法更新的问题咨询客服，却被告知没有这样的产品进而“建议”使用网页版。，而我一直是在 App Store 下载的。当时几乎想到距离我两三公里的高德总部去要个说法，而这次的离线数据包更是提示升级中，无法下载。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511221605339.png" alt="image-20250511221605339"></p><p>于是退而求其次，我下载一个最新的机车版是不是会更好一些？安装之前还担心数据丢失问题，于是看到下面这个话就“放心”了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511222322222.png" alt="image-20250511222322222"></p><p>但是安装之后，之前的离线地图全部消失，甚至很”贴心“的把之前旧数据的磁盘空间都释放了出来。原来马上爆满的磁盘空间，突然瘦身了。但，实时导航，手机同步，问题反馈都共用报错无法联网，这次而且无法回滚。不能上网的车机，不能联网的地图，被清空的旧版数据。第二次想杀到高德总部。</p><h3 id="ADB-的奇迹"><a href="#ADB-的奇迹" class="headerlink" title="ADB 的奇迹"></a>ADB 的奇迹</h3><p>大学同学在嵌入式行业，经验比我丰富些，于是去求助。他告诉我可以尝试通过 ADB 进入调试系统，然后使用命令行拉起来 wifi 的进程 ，看看是否能够改善。</p><p>分析之后查看到，车机的版本是 Android6.0，基于 Linux3.18 的内核，连包管理工具也没有，甚至连 top 都会把上位机卡死。于是开始分析思路，能够开启热点说明有 wifi 模块，然后依次排查进程是否启动，驱动是否安装，内核在启动时是否正常加载该驱动。那一刹那，感觉自己从调侃的修电脑，修家电，修水电，最后到了修车侠。</p><p>还好支持开发者模式，adb 调试的时候使用了甲壳虫助手，比用 USB 连接安卓 ADB 方便的多，把软件安装在车机上，不用再像以前刷机一样一遍又一遍的执行 adb device -l 查找设备，虽然这个机器慢一点，但是总归还是能够很稳的连接 ABD，然后一遍一遍尝试命令，Google，GPT，Deepseek，一遍又一遍。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511223749490.png" alt="弄好已经夜深了"></p><p>机车给了一个 USB 的接口，刚好拿来外接键盘，把 MBP 搬到车里查资料，手机开了热点越来越烫，然后就是一遍的调试，而 MBP 不能通过 adb 扫描到机车，不确定是不是和工厂模式里的设置有关系。这过程有点像刷机，有一点想像服务器上调 Linux。在此之前也想过用 USB 转 J45 连接路由器，就算没有网络的情况下，也应该能够显示一张没有网络连接的以太网卡。试验之后是完美没有，和朋友讨论之后，猜想是和内核驱动在编译的时候没有打包通用驱动或者这张卡驱动导致的。（来自之前 UFS 安装 Linux 的经验）而这个机器上完全无法执行 <strong>lsmod</strong> 和和 <strong>cat &#x2F;proc&#x2F;modules</strong>，而**<code>dmesg | grep -i &quot;load&quot;</code>**的记录也是空，实在无法想象这个系统是如何加载底层驱动的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511224325878.png" alt="image-20250511224325878"></p><p>最后思来想去大概是这几个命令生效了，因为后来重启才发现 wifi 模块加载出来了，中间加执行了很多。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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"># 在甲壳虫中可以省略adb shell字眼</span><br><span class="line"></span><br><span class="line"># 1. 直接指定组件启动 wifi（好像是自动进入 WLAN 搜索页面）</span><br><span class="line">am start -n com.android.settings/.wifi.WifiSettings</span><br><span class="line"></span><br><span class="line"># 2. 用系统通用 Intent（推荐）</span><br><span class="line">am start -a android.settings.WIFI_SETTINGS</span><br><span class="line"></span><br><span class="line"># 开启 Wi‑Fi</span><br><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 立即让系统刷新一次广播（有的 ROM 必须）</span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state true</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">验证：</span><br><span class="line">adb shell settings get global wifi_on → 返回 1；</span><br><span class="line">adb shell dumpsys wifi | grep &quot;Wi-Fi is&quot; → 出现 enabled</span><br></pre></td></tr></table></figure><p>回去看到 GPT 的提示，说<strong>操作完一定重启</strong>，否则配置不会写入  NVRAM。（我之前忽略了这个，走了很多弯路）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511231504637.png" alt="image-20250511231504637"></p><h4 id="GPT-这些内容帮助很大，可以用来验证："><a href="#GPT-这些内容帮助很大，可以用来验证：" class="headerlink" title="GPT 这些内容帮助很大，可以用来验证："></a>GPT 这些内容帮助很大，可以用来验证：</h4><p>我的<code>dumpsys wifi</code>  里出现的那一行长这样，</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">Wi‑Fi is disabled</span><br></pre></td></tr></table></figure><p>它只说明 <strong>系统目前把  Wi‑Fi 开关拨到了“关”</strong>，并不直接告诉你“有没有  Wi‑Fi  硬件”。要分清楚是“硬件存在但关掉”还是“根本没有硬件&#x2F;驱动”，可以再看 4 处信号：</p><p>| 检查点              | 如何查看                               | 典型输出                | 结论                                                 |<br>| ——————- | ————————————– | ———————– | —————————————————- | ———————————————- | — |<br>| <strong>1. Service 列表</strong> | <code>adb shell service list                | grep -i wifi</code>           | <code>wifi: [android.net.wifi.IWifiManager]</code>              | 看到这一行 → Framework 里 <strong>有</strong> Wi‑Fi 服务    |<br>| <strong>2. 驱动状态</strong>     | <code>adb shell getprop wlan.driver.status</code> | <code>ok / ready / unloaded</code> | 有值 → 内核认识 Wi‑Fi 芯片；空值 → 多半缺驱动&#x2F;被裁掉 |<br>| <strong>3. 网络接口</strong>     | <code>adb shell ifconfig -a                 | grep wlan</code>              | <code>wlan0  ...</code>                                         | 能看到 wlan0 → 芯片已初始化；完全没有 → 未加载 |<br>| <strong>4. 内核模块</strong>     | <code>adb shell lsmod \| grep -E &quot;8188      | rt5370                  | wlan&quot;</code>                                               |                                                |     |</p><h3 id="结果判读"><a href="#结果判读" class="headerlink" title="结果判读"></a>结果判读</h3><table><thead><tr><th>情况</th><th>解释</th><th>解决办法</th></tr></thead><tbody><tr><td><strong>Service 有，wlan0 有，driver&#x3D;ok，但 dumpsys 显示 disabled</strong></td><td><strong>硬件存在，只是软件开关关掉</strong></td><td>用 UI 开关或<code>adb shell settings put global wifi_on 1</code>再重启 Wi‑Fi</td></tr><tr><td><strong>Service 有，但 wlan0&#x2F;驱动都没有</strong></td><td>Framework 在，但 <strong>内核缺驱动&#x2F;Factory 里关闭了 Wi‑Fi</strong></td><td>先在工厂设置里勾选 WIFI 并保存；如仍无驱动 → 刷带 <em>wifi_bt</em> 的 ROM 或插 USB 网卡</td></tr><tr><td><strong>Service 没有（grep 不到 wifi）</strong></td><td>ROM&#x2F;MCU 层完全裁掉了 Wi‑Fi</td><td>只能刷完整固件或硬改&#x2F;外接网卡</td></tr><tr><td><strong>Service 有，driver&#x3D;unloaded</strong></td><td>驱动文件在，但未成功加载</td><td>Factory 里未启用 &#x2F; 模块版本不匹配 → 勾选保存或换匹配的系统+MCU</td></tr></tbody></table><hr><h3 id="快速打开-Wi‑Fi（无-svc-的情况下）"><a href="#快速打开-Wi‑Fi（无-svc-的情况下）" class="headerlink" title="快速打开 Wi‑Fi（无  svc 的情况下）"></a>快速打开 Wi‑Fi（无  <code>svc</code> 的情况下）</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></pre></td><td class="code"><pre><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state <span class="literal">true</span></span><br><span class="line"><span class="comment"># 然后再查看</span></span><br><span class="line">adb shell dumpsys wifi | grep <span class="string">&quot;Wi‑Fi is&quot;</span></span><br></pre></td></tr></table></figure><p>如果改完仍显示 <strong>disabled</strong>，就说明硬件&#x2F;驱动层面还没点亮，需要回“工厂设置”勾选  Wi‑Fi 或换带驱动的固件。</p><hr><p><strong>一句话</strong>：<code>dumpsys wifi</code> 看到 <em>disabled</em> 只是“当前关着”，并不代表“没有  Wi‑Fi”。再看服务列表、驱动状态、<code>wlan0</code> 接口等就能分辨：</p><ul><li>都在 → 只是没开；</li><li>驱动&#x2F;接口缺失 → 硬件被关闭或驱动裁掉，需要在工厂设置启用或刷新固件。</li></ul><p>我的结果是。无法执行 lsmod，服务正常，驱动正常，而网络接口必须在启动共享热点的时候才能看到，最后在工厂模式的时候手滑点了重启才发现一切正常。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511230601385.png" alt="image-20250511230601385"></p><p>看到这个页面的时候就感觉一切的付出都值得了，所谓念念不忘，必有回想吧。（设置里还是没有 wifi 选项，还得自己从下拉菜单启动）</p><h3 id="工厂模式："><a href="#工厂模式：" class="headerlink" title="工厂模式："></a>工厂模式：</h3><p>主要用来开关一些功能，实际在我这个过程中好像没有起到太大的作用，这个主要针对整个 OS 而言，有些版本可能在这里启动 wifi，但是我这个没有，主要还是见识一下车机模式，图一乐。</p><p>这些是 GPT 给的，没有真正执行：</p><p><strong>ADB 直接拉起</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">bash</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">复制编辑</span><br><span class="line">adb shell am start -n com.twd.settings/.FactorySetting   # FYT/TS10 系常见</span><br><span class="line">adb shell am start -n com.android.settings/.DeviceInfoFactory   # 某些 XY‑Auto</span><br></pre></td></tr></table></figure><p>若提示 <strong>Error type 3</strong> 仍找不到，说明 ROM 确实删掉了对应 Activity。</p><h3 id="Mesh：高层-WIFI-的荣耀"><a href="#Mesh：高层-WIFI-的荣耀" class="headerlink" title="Mesh：高层 WIFI 的荣耀"></a>Mesh：高层 WIFI 的荣耀</h3><p>家里在高层，即使把车开到楼下也不见能够有多好的信号，也能是根本就搜不到。然后突发奇想，正好之前有一个</p><p>交流的移动电源，然后把 Mesh 子路由拿到楼下供电，然后把楼上的路由放在窗户上。这才是有线 Mesh 的正确用法，尽管在高层，还能跑到 10M 以上的速度，然后再用楼下的子路由无线桥接，可以媲美一些无线路由器的速度了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511231244805.png" alt="image-20250511231244805"></p><p>其实还有一个方案，借助 POE 路由的便携性，把网线从窗户顺下去，然后顺便也解决供电的问题。但是，翻了翻箱子，确实没有这么长的网线，那么就速战速决，将就一下把。</p><p>设备如下，方案做好了，摸黑也要实施完。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250511224530841.png" alt="image-20250511224530841"></p><h3 id=""><a href="#" class="headerlink" title=""></a></h3><h3 id="这过程中的命令和帖子"><a href="#这过程中的命令和帖子" class="headerlink" title="这过程中的命令和帖子"></a>这过程中的命令和帖子</h3><p>一些安卓 8 以上才可以用的命令，先记录下来，作为折腾的过程。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">iwlist wlan0 scan</span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state true</span><br><span class="line">adb shell am start -n com.android.settings/.wifi.WifiSettings</span><br><span class="line"></span><br><span class="line">https://blog.csdn.net/BOJUE01/article/details/136684847</span><br><span class="line">https://blog.csdn.net/BOJUE01/article/details/136684847</span><br><span class="line">https://www.cnblogs.com/linux-37ge/p/11111596.html</span><br><span class="line">https://blog.csdn.net/pwp032984/article/details/126286159</span><br><span class="line">http://www.allmost.org/2019/11/android-head-unit-root-device-model.html</span><br></pre></td></tr></table></figure><h4 id="以下来自-GPT，也帮了很大的忙"><a href="#以下来自-GPT，也帮了很大的忙" class="headerlink" title="以下来自 GPT，也帮了很大的忙"></a>以下来自 GPT，也帮了很大的忙</h4><p>下面给你 <strong>4  种常用办法</strong>，按「年代新  →  旧」排列，只要你的 ROM 里有对应的命令或工具，就能在 <strong>PC  端终端</strong> 直接看到扫描结果。不用担心全记不住，逐个试，命令不认识就跳下一个。</p><table><thead><tr><th>方法</th><th>适用 Android 版本</th><th>是否要 root</th><th>命令示例</th></tr></thead><tbody><tr><td><strong>A. <code>cmd wifi</code> 子命令</strong></td><td>8.1 (Oreo) 及以上，只要系统内置 <code>cmd</code></td><td>❌</td><td><code>bash\nadb shell cmd wifi enable            # 先确保开 Wi‑Fi\nadb shell cmd wifi scan               # 立即触发一次扫描\nadb shell cmd wifi list-scan-results  # 打印扫描结果（BSSID/SSID/信号）\n</code></td></tr><tr><td><strong>B. <code>dumpsys wifi</code> 解析</strong></td><td>4.0+（几乎所有）</td><td>❌</td><td>&#96;&#96;&#96;bash\nadb shell dumpsys wifi</td></tr><tr><td><strong>C. <code>wpa_cli</code> 工具链</strong></td><td>ROM 里带 <code>wpa_supplicant</code> + <code>wpa_cli</code></td><td>⭕（通常要 root）</td><td><code>bash\nadb shell su -c &#39;wpa_cli scan&#39;\nadb shell su -c &#39;wpa_cli scan_results&#39;\n</code></td></tr><tr><td><strong>D. <code>iw</code>&#x2F;<code>iwlist</code> 直查射频</strong></td><td>内核自带 mac80211&#x2F;CFG80211，工具在 &#x2F;system&#x2F;xbin</td><td>⭕</td><td>&#96;&#96;&#96;bash\nadb shell su -c ‘iw dev wlan0 scan</td></tr></tbody></table><hr><h3 id="步骤拆解（通用）"><a href="#步骤拆解（通用）" class="headerlink" title="步骤拆解（通用）"></a>步骤拆解（通用）</h3><ol><li><p><strong>让 ADB 连上车机</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb devices        <span class="comment"># 出现 device 才算连上</span></span><br></pre></td></tr></table></figure></li><li><p><strong>确保 Wi‑Fi 已经启用</strong></p><ul><li>可用 <code>adb shell cmd wifi enable</code> 或 <code>settings put global wifi_on 1</code> 后再开一次 UI 开关。</li><li>查看状态：<code>adb shell dumpsys wifi | grep &quot;Wi‑Fi is&quot;</code> → <code>enabled</code>&#x2F;<code>disabled</code>。</li></ul></li><li><p><strong>触发一次扫描</strong></p><ul><li><p>新版用 <code>cmd wifi scan</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">adb shell am broadcast -a android.intent.action.SCAN_RESULTS</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>查看结果</strong></p><ul><li>任选上表 4  种方法里你设备支持的那一行。</li><li>若看不到 SSID，只见一堆 <code>&lt;hidden&gt;</code>：说明热点隐藏了 SSID，属正常现象。</li></ul></li></ol><hr><h3 id="命令输出示例（cmd-wifi-list-scan-results）"><a href="#命令输出示例（cmd-wifi-list-scan-results）" class="headerlink" title="命令输出示例（cmd wifi list-scan-results）"></a>命令输出示例（<code>cmd wifi list-scan-results</code>）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">BSSID              frequency level SSID</span><br><span class="line">60:45:cb:aa:1b:30  2462      -55   HomeWiFi_2G</span><br><span class="line">60:45:cb:aa:1b:34  5200      -70   HomeWiFi_5G</span><br><span class="line">b0:d5:9d:cc:31:c0  2412      -65   MiPhoneHotspot</span><br></pre></td></tr></table></figure><p>字段含义：</p><table><thead><tr><th>字段</th><th>解释</th></tr></thead><tbody><tr><td><strong>BSSID</strong></td><td>无线 AP 的 MAC 地址</td></tr><tr><td><strong>frequency</strong></td><td>2412 – 2472 MHz &#x3D; 2.4 GHz；5180 + &#x3D; 5 GHz</td></tr><tr><td><strong>level</strong></td><td>信号强度，单位 dBm（-30≈ 满格，-80≈ 临界）</td></tr><tr><td><strong>SSID</strong></td><td>热点名称（隐藏网络会显示 <code>&lt;hidden&gt;</code>）</td></tr></tbody></table><hr><h3 id="常见坑-对策"><a href="#常见坑-对策" class="headerlink" title="常见坑 &amp; 对策"></a>常见坑 &amp; 对策</h3><table><thead><tr><th>症状</th><th>原因</th><th>解决</th></tr></thead><tbody><tr><td><code>cmd: not found</code></td><td>ROM 删了 toybox &#x2F; cmd</td><td>改用 <strong>方法  B、C、D</strong></td></tr><tr><td><code>cmd wifi: Unknown command</code></td><td>Android 7.1 及以下</td><td>用 <strong>dumpsys</strong> 或 <code>wpa_cli</code></td></tr><tr><td><code>permission denied</code> &#x2F; <code>su: not found</code></td><td>没 root</td><td>选不用 root 的 <strong>方法  A 或 B</strong></td></tr><tr><td>扫描结果为空</td><td>Wi‑Fi 实际没启用、没有天线、驱动缺失</td><td>先 <code>settings put global wifi_on 1</code> 并从 UI 打开；或检查硬件&#x2F;ROM</td></tr></tbody></table><hr><h3 id="小贴士"><a href="#小贴士" class="headerlink" title="小贴士"></a>小贴士</h3><ul><li><p><strong>批量重扫</strong>：想每 5  秒自动扫一次可用循环：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> <span class="literal">true</span>; <span class="keyword">do</span> adb shell cmd wifi scan; adb shell cmd wifi list-scan-results | <span class="built_in">head</span> -n 10; <span class="built_in">sleep</span> 5; <span class="keyword">done</span></span><br></pre></td></tr></table></figure></li><li><p><strong>只取最强信号</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell cmd wifi list-scan-results | sed 1d | <span class="built_in">sort</span> -k3 -n | <span class="built_in">head</span></span><br></pre></td></tr></table></figure></li><li><p><strong>无 Wi‑Fi 硬件时</strong>：任何扫描命令都会报错或空结果 —— 确认已在 Factory Setting 勾选 Wi‑Fi、或插上 RTL8188EU&#x2F;RT5370 USB 网卡后重试。</p></li></ul><hr><blockquote><p><strong>一句话</strong>：连好  ADB → 开 Wi‑Fi → 在 PC 端执行 **<code>cmd wifi scan &amp;&amp; cmd wifi list-scan-results</code>**（Android 8.1+ 最方便）；旧版系统就用 <code>adb shell dumpsys wifi</code> 或 root 下的 <code>wpa_cli scan_results</code> &#x2F; <code>iw dev wlan0 scan</code> 来列出热点。祝你早日看到满屏 SSID！</p></blockquote><hr><p><code>dumpsys</code>  是 <strong>只读（dump）</strong> 的诊断工具——它只能把系统当前状态“倒”出来给你看，<strong>完全没有写入&#x2F;控制能力</strong>。<br>所以：</p><blockquote><p><strong><code>dumpsys wifi</code>  只能查看 Wi‑Fi 的开关状态、扫描结果、连接信息等，不能直接把开关从 OFF 变成 ON。</strong></p></blockquote><hr><h2 id="为什么-dumpsys-不能改状态？"><a href="#为什么-dumpsys-不能改状态？" class="headerlink" title="为什么 dumpsys 不能改状态？"></a>为什么 <code>dumpsys</code> 不能改状态？</h2><ul><li><strong>实现方式</strong>：<code>dumpsys</code> 调用各系统服务的 <em>dump()</em> 接口，这个接口按约定必须是“无副作用”的纯输出。</li><li><strong>权限限制</strong>：即使给  <code>dumpsys</code> root 权限，它也只有 <em>READ</em> 能力。</li><li><strong>系统设计</strong>：修改 Wi‑Fi 状态属于 <strong>IWifiManager.setWifiEnabled()</strong> 这一类“更改设备行为”的 API，必须走 Binder 或写全局设置值，不能通过 dump。</li></ul><hr><h2 id="不用-UI-打开-Wi‑Fi-的正规方法（回顾）"><a href="#不用-UI-打开-Wi‑Fi-的正规方法（回顾）" class="headerlink" title="不用 UI 打开 Wi‑Fi 的正规方法（回顾）"></a>不用 UI 打开 Wi‑Fi 的正规方法（回顾）</h2><blockquote><p>任选其一，具体支持情况取决于你的 ROM &#x2F; 是否 root</p></blockquote><table><thead><tr><th>方法</th><th>是否 root</th><th>Android 版本</th><th>示例</th></tr></thead><tbody><tr><td><strong>settings put global wifi_on 1</strong></td><td>不要求</td><td>4.0+</td><td><code>adb shell settings put global wifi_on 1</code></td></tr><tr><td><strong>service call wifi 13 i32 1</strong></td><td>需要</td><td>4.1+</td><td><code>adb shell su -c &#39;service call wifi 13 i32 1&#39;</code></td></tr><tr><td><strong>cmd wifi enable</strong></td><td>不要求</td><td>8.1+</td><td><code>adb shell cmd wifi enable</code></td></tr><tr><td><strong>广播／Intent</strong>(部分 ROM 有效)</td><td>不要求</td><td>4.0+</td><td><code>adb shell am start -a android.settings.WIFI_SETTINGS</code>（拉起设置页后系统会自动打开）</td></tr></tbody></table><blockquote><p><code>svc wifi enable</code> 也是写操作，但你的 ROM 把 <code>svc</code> 精简掉了，所以用不了。</p></blockquote><hr><h3 id="一键脚本（无-root-情况下最通用）"><a href="#一键脚本（无-root-情况下最通用）" class="headerlink" title="一键脚本（无 root 情况下最通用）"></a>一键脚本（无 root 情况下最通用）</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"># 1. 打开 Wi‑Fi（写 settings）</span></span><br><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line">adb shell settings put system wifi_on 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 触发系统刷新（广播）</span></span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>大多数 MT3561 Android 6&#x2F;7  车机用这两行就能把 Wi‑Fi 开关拨到 ON。<br>如果执行后 <code>dumpsys wifi</code> 仍显示 <strong>disabled</strong>，说明：</p><ol><li><strong>ROM 在启动脚本里强制关掉</strong> —— 去“工厂设置”里把 <strong>WIFI</strong> 复选框勾上并保存；</li><li><strong>硬件或驱动缺失</strong> —— 需要刷带 Wi‑Fi 的完整 ROM，或外接 RTL8188EU&#x2F;RT5370 免驱 USB 网卡。</li></ol><hr><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><ul><li><strong><code>dumpsys wifi</code> 只能看，不能改</strong>。</li><li>想无 UI 打开 Wi‑Fi：用 <code>settings put …</code>、<code>service call wifi …</code>、<code>cmd wifi enable</code>（≥8.1）等写操作；提前确保 ROM 没把 Wi‑Fi 功能裁掉，或已在“工厂设置”里启用。祝你折腾顺利！</li></ul><hr><p><code>dumpsys wifi</code>  里出现的那一行通常长这样：</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">Wi‑Fi is disabled</span><br></pre></td></tr></table></figure><p>它只说明 <strong>系统目前把  Wi‑Fi 开关拨到了“关”</strong>，并不直接告诉你“有没有  Wi‑Fi  硬件”。要分清楚是“硬件存在但关掉”还是“根本没有硬件&#x2F;驱动”，可以再看 4 处信号：</p><p>| 检查点              | 如何查看                               | 典型输出                | 结论                                                 |<br>| ——————- | ————————————– | ———————– | —————————————————- | ———————————————- | — |<br>| <strong>1. Service 列表</strong> | <code>adb shell service list                | grep -i wifi</code>           | <code>wifi: [android.net.wifi.IWifiManager]</code>              | 看到这一行 → Framework 里 <strong>有</strong> Wi‑Fi 服务    |<br>| <strong>2. 驱动状态</strong>     | <code>adb shell getprop wlan.driver.status</code> | <code>ok / ready / unloaded</code> | 有值 → 内核认识 Wi‑Fi 芯片；空值 → 多半缺驱动&#x2F;被裁掉 |<br>| <strong>3. 网络接口</strong>     | <code>adb shell ifconfig -a                 | grep wlan</code>              | <code>wlan0  ...</code>                                         | 能看到 wlan0 → 芯片已初始化；完全没有 → 未加载 |<br>| <strong>4. 内核模块</strong>     | <code>adb shell lsmod \| grep -E &quot;8188      | rt5370                  | wlan&quot;</code>                                               |                                                |     |</p><h3 id="结果判读-1"><a href="#结果判读-1" class="headerlink" title="结果判读"></a>结果判读</h3><table><thead><tr><th>情况</th><th>解释</th><th>解决办法</th></tr></thead><tbody><tr><td><strong>Service 有，wlan0 有，driver&#x3D;ok，但 dumpsys 显示 disabled</strong></td><td><strong>硬件存在，只是软件开关关掉</strong></td><td>用 UI 开关或<code>adb shell settings put global wifi_on 1</code>再重启 Wi‑Fi</td></tr><tr><td><strong>Service 有，但 wlan0&#x2F;驱动都没有</strong></td><td>Framework 在，但 <strong>内核缺驱动&#x2F;Factory 里关闭了 Wi‑Fi</strong></td><td>先在工厂设置里勾选 WIFI 并保存；如仍无驱动 → 刷带 <em>wifi_bt</em> 的 ROM 或插 USB 网卡</td></tr><tr><td><strong>Service 没有（grep 不到 wifi）</strong></td><td>ROM&#x2F;MCU 层完全裁掉了 Wi‑Fi</td><td>只能刷完整固件或硬改&#x2F;外接网卡</td></tr><tr><td><strong>Service 有，driver&#x3D;unloaded</strong></td><td>驱动文件在，但未成功加载</td><td>Factory 里未启用 &#x2F; 模块版本不匹配 → 勾选保存或换匹配的系统+MCU</td></tr></tbody></table><hr><h3 id="快速打开-Wi‑Fi（无-svc-的情况下）-1"><a href="#快速打开-Wi‑Fi（无-svc-的情况下）-1" class="headerlink" title="快速打开 Wi‑Fi（无  svc 的情况下）"></a>快速打开 Wi‑Fi（无  <code>svc</code> 的情况下）</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></pre></td><td class="code"><pre><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state <span class="literal">true</span></span><br><span class="line"><span class="comment"># 然后再查看</span></span><br><span class="line">adb shell dumpsys wifi | grep <span class="string">&quot;Wi‑Fi is&quot;</span></span><br></pre></td></tr></table></figure><p>如果改完仍显示 <strong>disabled</strong>，就说明硬件&#x2F;驱动层面还没点亮，需要回“工厂设置”勾选  Wi‑Fi 或换带驱动的固件。</p><hr><p><strong>一句话</strong>：<code>dumpsys wifi</code> 看到 <em>disabled</em> 只是“当前关着”，并不代表“没有  Wi‑Fi”。再看服务列表、驱动状态、<code>wlan0</code> 接口等就能分辨：</p><ul><li>都在 → 只是没开；</li><li>驱动&#x2F;接口缺失 → 硬件被关闭或驱动裁掉，需要在工厂设置启用或刷新固件。</li></ul><hr><h3 id="在-Android-6-0（API-23）＋-Linux-3-x-内核的-MT3561-车机上"><a href="#在-Android-6-0（API-23）＋-Linux-3-x-内核的-MT3561-车机上" class="headerlink" title="在  Android 6.0（API 23）＋  Linux 3.x  内核的 MT3561 车机上"></a>在  Android 6.0（API 23）＋  Linux 3.x  内核的 MT3561 车机上</h3><blockquote><p>下面所有命令都在 <strong>PC 端终端</strong> 输入（前面带 <code>adb</code>），车机端只需提前打开「USB 调试」。</p></blockquote><hr><h2 id="1-功能概览：Android-6-0-能-不能用什么"><a href="#1-功能概览：Android-6-0-能-不能用什么" class="headerlink" title="1  功能概览：Android 6.0 能&#x2F;不能用什么"></a>1  功能概览：Android 6.0 能&#x2F;不能用什么</h2><table><thead><tr><th>功能</th><th>Android 6.0 支持情况</th><th>备注</th></tr></thead><tbody><tr><td><strong>cmd wifi …</strong></td><td>❌ <em>没有</em>（8.1 才加入）</td><td></td></tr><tr><td><strong>svc wifi enable&#x2F;disable</strong></td><td>✅  系统自带 <strong>toybox svc</strong>但你的 ROM 把 <code>svc</code> 删掉，才会显示 <em>not found</em></td><td></td></tr><tr><td><strong>settings put global wifi_on</strong></td><td>✅ <strong>无  root 也能写</strong></td><td></td></tr><tr><td><strong>service call wifi …</strong></td><td>✅ 需要 <strong>root&#x2F;SU</strong></td><td></td></tr><tr><td><strong>wpa_cli &#x2F; iwlist &#x2F; iw</strong></td><td>取决于 ROM 是否编译进 &#x2F;system&#x2F;xbin</td><td></td></tr></tbody></table><hr><h2 id="2-无-svc-情况下打开-Wi‑Fi（不需要-root）"><a href="#2-无-svc-情况下打开-Wi‑Fi（不需要-root）" class="headerlink" title="2  无  svc 情况下打开  Wi‑Fi（不需要 root）"></a>2  无  <code>svc</code> 情况下打开  Wi‑Fi（不需要 root）</h2><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"># 打开 Wi‑Fi</span></span><br><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line">adb shell settings put system wifi_on 1    <span class="comment"># 某些 ROM 还要写 system 表</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 立即广播一次状态，让 Framework 赶紧刷新</span></span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="校验开关是否生效"><a href="#校验开关是否生效" class="headerlink" title="校验开关是否生效"></a>校验开关是否生效</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb shell settings get global wifi_on      <span class="comment"># 应返回 1</span></span><br><span class="line">adb shell dumpsys wifi | grep <span class="string">&quot;Wi‑Fi is&quot;</span>   <span class="comment"># 应变成 enabled</span></span><br></pre></td></tr></table></figure><blockquote><p>如果还是 _disabled_，说明 <strong>Factory Setting 里仍把 Wi‑Fi 禁掉</strong> 或 <strong>驱动没加载</strong>——请回工厂菜单勾选 <strong>WIFI</strong> 或插免驱 USB 网卡后再试。</p></blockquote><hr><h2 id="3-有-root-时的“硬开”——直接调-Binder"><a href="#3-有-root-时的“硬开”——直接调-Binder" class="headerlink" title="3  有 root 时的“硬开”——直接调 Binder"></a>3  有 root 时的“硬开”——直接调 Binder</h2><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"># 查询 wifi 服务是否存在</span></span><br><span class="line">adb shell service list | grep -i wifi</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用 setWifiEnabled(true)</span></span><br><span class="line">adb shell su -c <span class="string">&#x27;service call wifi 13 i32 1&#x27;</span>   <span class="comment"># 13 是 Android 6.* 通常的事务码</span></span><br></pre></td></tr></table></figure><ul><li>返回 result&#x3D;0x1 表示成功；0x0 表示失败（多半被 Factory 关闭或驱动缺失）。</li></ul><hr><h2 id="4-在-Android-6-0-上扫描-Wi‑Fi-热点"><a href="#4-在-Android-6-0-上扫描-Wi‑Fi-热点" class="headerlink" title="4  在 Android 6.0 上扫描 Wi‑Fi 热点"></a>4  在 Android 6.0 上扫描 Wi‑Fi 热点</h2><table><thead><tr><th>方法</th><th>是否 root</th><th>命令</th></tr></thead><tbody><tr><td><strong>settings + dumplog</strong>（最稳）</td><td>❌</td><td>&#96;&#96;&#96;bash\nadb shell dumpsys wifi</td></tr><tr><td><strong>wpa_cli</strong>（ROM 带 wpa_supplicant）</td><td>⭕</td><td><code>bash\nadb shell su -c &#39;wpa_cli scan&#39;\nadb shell su -c &#39;wpa_cli scan_results&#39;\n</code></td></tr><tr><td><strong>iwlist</strong>（busybox）</td><td>⭕</td><td>&#96;&#96;&#96;bash\nadb shell su -c ‘busybox iwlist wlan0 scanning</td></tr></tbody></table><hr><h2 id="5-驱动检查——确认-8188EU-RT5370-是否加载"><a href="#5-驱动检查——确认-8188EU-RT5370-是否加载" class="headerlink" title="5  驱动检查——确认 8188EU&#x2F;RT5370 是否加载"></a>5  驱动检查——确认 8188EU&#x2F;RT5370 是否加载</h2><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">adb shell lsmod | grep -E <span class="string">&quot;8188|rt5370|wlan&quot;</span></span><br><span class="line">adb shell getprop wlan.driver.status</span><br><span class="line">adb shell ifconfig -a | grep wlan</span><br></pre></td></tr></table></figure><table><thead><tr><th>结果</th><th>说明</th><th>后续</th></tr></thead><tbody><tr><td>能看到 <strong>8188eu &#x2F; rt5370sta</strong> 等模块</td><td>驱动已插</td><td>只需把 Wi‑Fi 开关打开即可</td></tr><tr><td>没有任何模块 &#x2F; getprop 为空</td><td>内核缺驱动 or Factory 关闭</td><td>1) Factory 勾选 WIFI 保存 2) 刷带 <em>wifi_bt</em> 的 ROM3) 外接免驱 USB 网卡</td></tr></tbody></table><hr><h2 id="6-Factory-Setting-触摸不到-Wi‑Fi-条目的两大原因"><a href="#6-Factory-Setting-触摸不到-Wi‑Fi-条目的两大原因" class="headerlink" title="6 Factory Setting 触摸不到  Wi‑Fi 条目的两大原因"></a>6 Factory Setting 触摸不到  Wi‑Fi 条目的两大原因</h2><ol><li><strong>MCU&#x2F;System 不匹配</strong> →  刷同型号、带  <em>wifi_bt</em>  标记的 <strong>全量包</strong></li><li><strong>硬件本身无 Wi‑Fi 芯片</strong> →  外接 <strong>RTL8188EU &#x2F; RT5370</strong> USB 网卡（Android 6 内核 3.x 自带驱动）</li></ol><hr><h3 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h3><p>在 <strong>Android 6.0 + Linux 3.x</strong> 的  evb3561sv  车机里：</p><ul><li><code>svc</code> 被删时，用 <strong><code>settings put global wifi_on 1</code> + 状态广播</strong> 就能无界面开 Wi‑Fi；</li><li>若有 root，也可 <code>service call wifi 13 i32 1</code> 直接写 Binder；</li><li><code>dumpsys wifi</code> 看到 <em>disabled</em> 只是开关关着，不代表硬件不存在——再看 <code>service list</code>、<code>lsmod</code>、<code>wlan0</code> 就能判断；</li><li>真缺驱动或 Factory 关闭时，要么在隐藏菜单勾选 <strong>WIFI</strong>，要么刷带  Wi‑Fi  驱动的完整固件，或者插 RTL8188EU&#x2F;RT5370  免驱 USB 网卡。祝你早点连上网！</li></ul><hr><p><code>shell: svc wifi enable</code>  这条命令只是 <strong>Android 自带的小工具封装</strong>；<br>你那台  evb3561sv  的 ROM 把 <em>toybox&#x2F;toolbox</em> 精简了，直接把 <strong><code>svc</code> 可执行文件删掉</strong>，所以才提示 _not found_。<br>没关系，<strong>核心功能还在</strong>——我们可以绕过 <code>svc</code>，改用 <strong>系统设置 &#x2F; Binder 服务 &#x2F; cmd</strong> 三种办法来“硬切” Wi‑Fi。先按难度从低到高排一下，你照顺序尝试即可。</p><hr><h2 id="0-先确认两件事"><a href="#0-先确认两件事" class="headerlink" title="0  先确认两件事"></a>0  先确认两件事</h2><table><thead><tr><th>检查</th><th>命令 (PC 端执行)</th><th>说明</th></tr></thead><tbody><tr><td>车机是否连上 ADB</td><td><code>adb devices</code></td><td>看到 <code>device</code> 才算连通；若是 <code>unauthorized</code>，去车机点“始终允许”。</td></tr><tr><td>ROM 里有没有 <code>cmd</code>&#x2F;<code>settings</code>&#x2F;<code>service</code></td><td><code>adb shell which cmd; which settings; which service</code></td><td>至少会有 <strong><code>settings</code></strong> 和 **<code>service</code>**；有哪个就用哪个。</td></tr></tbody></table><hr><h2 id="1-用-settings-写系统值（多数无-root-机器可用）"><a href="#1-用-settings-写系统值（多数无-root-机器可用）" class="headerlink" title="1  用 settings 写系统值（多数无 root 机器可用）"></a>1  用 <strong>settings</strong> 写系统值（多数无 root 机器可用）</h2><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"># 开启 Wi‑Fi</span></span><br><span class="line">adb shell settings put global wifi_on 1</span><br><span class="line">adb shell settings put system wifi_on 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 立即让系统刷新一次广播（有的 ROM 必须）</span></span><br><span class="line">adb shell am broadcast -a android.intent.action.WIFI_STATE_CHANGED --ez state <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 可选：重启 Wi‑Fi 子系统（有 sh 脚本时）</span></span><br><span class="line">adb shell stop wifi</span><br><span class="line">adb shell start wifi</span><br></pre></td></tr></table></figure><blockquote><p><strong>验证</strong>：<br><code>adb shell settings get global wifi_on</code> → 返回 <code>1</code>；<br><code>adb shell dumpsys wifi | grep &quot;Wi-Fi is&quot;</code> → 出现 <strong>enabled</strong>。</p></blockquote><p>如果重启车机后又变回 <code>0</code>，说明 ROM 在启动脚本里把 Wi‑Fi 强行关掉——请继续看下一招。</p><hr><h2 id="2-用-service-call-wifi-直接打-Binder（需-root，但最稳）"><a href="#2-用-service-call-wifi-直接打-Binder（需-root，但最稳）" class="headerlink" title="2  用 service call wifi 直接打 Binder（需 root，但最稳）"></a>2  用 <strong>service call wifi</strong> 直接打 Binder（需 root，但最稳）</h2><ol><li><p>先确认系统里有 <code>wifi</code> 服务：</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">adb shell service list | grep -i wifi</span><br><span class="line"><span class="comment"># 正常会看到 &quot;wifi: [android.net.wifi.IWifiManager]&quot; 之类</span></span><br></pre></td></tr></table></figure></li><li><p>调用 <code>setWifiEnabled(true)</code>（在 Android 5&#x2F;6  上通常是 <strong>事务码 13</strong>；如果无效换 6、8 试）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb shell su -c <span class="string">&#x27;service call wifi 13 i32 1&#x27;</span>   <span class="comment"># 开</span></span><br><span class="line">adb shell su -c <span class="string">&#x27;service call wifi 13 i32 0&#x27;</span>   <span class="comment"># 关</span></span><br></pre></td></tr></table></figure><ul><li>若提示 <em>su: not found</em> 就表示没 root；跳过这个方案或先刷 Magisk。</li></ul></li></ol><hr><h2 id="3-用-cmd-wifi（Android-8-1‑O-以后才有）"><a href="#3-用-cmd-wifi（Android-8-1‑O-以后才有）" class="headerlink" title="3  用 cmd wifi（Android 8.1‑O 以后才有）"></a>3  用 <strong>cmd wifi</strong>（Android 8.1‑O 以后才有）</h2><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">adb shell cmd wifi <span class="built_in">enable</span></span><br><span class="line">adb shell cmd wifi <span class="built_in">disable</span></span><br></pre></td></tr></table></figure><blockquote><p>在 MT3561 Android 8.1（Oreo）第三方 ROM 上测试可行；如果返回 <em>Unknown command</em> 就说明版本还在 7.1 以下，没有 <code>cmd wifi</code> 子命令。</p></blockquote><hr><h2 id="4-如果上述全部无效-→-八成是-ROM-层面禁用-缺硬件"><a href="#4-如果上述全部无效-→-八成是-ROM-层面禁用-缺硬件" class="headerlink" title="4  如果上述全部无效 → 八成是 ROM 层面禁用 &#x2F; 缺硬件"></a>4  如果上述全部无效 → 八成是 ROM 层面禁用 &#x2F; 缺硬件</h2><ol><li><p><strong>再回 Factory Setting</strong></p><ul><li>密码 3368 &#x2F; 8888 → 勾选 <strong>WIFI</strong> → Save → 重启。</li><li>没有该项 &#x3D; ROM 完全裁掉了 Wi‑Fi。</li></ul></li><li><p><strong>查驱动</strong></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">adb shell lsmod | grep -E <span class="string">&#x27;8188|rt5370|wlan&#x27;</span></span><br><span class="line">adb shell dmesg | grep -i wlan</span><br></pre></td></tr></table></figure><ul><li>空白 &#x3D; 说明内核没加载任何 Wi‑Fi 模块。</li></ul></li><li><p><strong>解决思路</strong></p><table><thead><tr><th>路线</th><th>说明</th></tr></thead><tbody><tr><td>刷带 <em>wifi_bt</em> 字样的 <strong>完整 ROM + MCU</strong></td><td>与主板编码 <code>evb3561sv_w_65_m0</code> 对应的版本，一键恢复 Wi‑Fi。</td></tr><tr><td>外接 <strong>RTL8188EU &#x2F; RT5370</strong> USB 网卡</td><td>车机对这两款芯片免驱，插上后重启即可在「网络与互联网」里出现 Wi‑Fi。</td></tr><tr><td>临时上网</td><td>手机 USB 共享网络 (Tethering) → 车机当“有线客户端”，不依赖 Wi‑Fi 射频。</td></tr></tbody></table></li></ol><hr><h3 id="常见报错对照"><a href="#常见报错对照" class="headerlink" title="常见报错对照"></a>常见报错对照</h3><table><thead><tr><th>报错 &#x2F; 现象</th><th>判断</th><th>处理</th></tr></thead><tbody><tr><td><code>svc: not found</code></td><td>ROM 精简掉 toybox → 用 <strong>settings &#x2F; service</strong> 方案</td><td>见步骤 1&#x2F;2</td></tr><tr><td><code>settings: not found</code></td><td>工具也被删</td><td>装一个 <em>busybox</em> 或 <em>toybox</em> APK，或直接跳到 <strong>service call wifi</strong></td></tr><tr><td><code>service call wifi … result=0x0</code> 且 Wi‑Fi 仍 off</td><td>Binder 调用了，但返回 false</td><td>多半 “Factory” 里没勾选 Wi‑Fi 或硬件缺失</td></tr><tr><td><code>service list</code> 根本没 wifi</td><td>内核 &#x2F; Framework 把整个 Wi‑Fi Manager 移除了</td><td>只能刷新 ROM 或外接 USB 网卡</td></tr></tbody></table><hr><h2 id="一句话总结-1"><a href="#一句话总结-1" class="headerlink" title="一句话总结"></a>一句话总结</h2><blockquote><p>你的 ROM 砍掉了 <code>svc</code> 可执行文件，所以 <code>svc wifi enable</code> 无法用；先尝试 **<code>settings put global wifi_on 1</code>**，再不行就（有 root 时）用 **<code>service call wifi 13 i32 1</code>**。若系统连 <code>wifi</code> 服务都不存在，就说明 Wi‑Fi 在 Factory Setting 被禁用或 ROM&#x2F;硬件干脆没装，最终只能刷含驱动的完整固件或外接免驱 USB 网卡。祝折腾顺利！</p></blockquote><hr><p>下面给你一份<strong>最通用、从零到一</strong>的操作清单，帮你重新进入  evb3561sv  车机的「工厂设置（Factory Setting）」隐藏菜单，并把  Wi‑Fi  模块启&#x2F;停用。整套流程 3‑5  分钟，风险低，只要别在保存‑重启时断电即可。</p><table><thead><tr><th>步骤</th><th>详细做法</th><th>补充说明</th></tr></thead><tbody><tr><td><strong>1  回到主界面</strong></td><td>点实体  🏠  键或手势返回，退出你现在的「运行命令」App，回到桌面。</td><td></td></tr><tr><td><strong>2  打开系统设置</strong></td><td>桌面找灰色齿轮 → <strong>设置（Settings&#x2F;设置中心&#x2F;Car Setting）</strong>。如果桌面上没有，可在所有 App 列表里找“设置”或“车机设置”。</td><td></td></tr><tr><td><strong>3  定位“工厂设置”入口</strong></td><td>不同 ROM 位置略有差异，常见三种：① <strong>系统  →  关于设备 → 工厂设置</strong>② <strong>Car Setting → 版本信息</strong> 页面右上角小齿轮&#x2F;🔧③  直接在主设置页底部看到 <strong>Factory</strong>&#x2F;“工厂” 按钮</td><td>还有少数机型需要在顶部状态栏下拉，长按 ⚙️ 5  秒弹出“工厂模式”，可先试前两种常见路径再换招。</td></tr><tr><td><strong>4  输入密码</strong></td><td>出现数字键盘后依次试：• <strong>8888</strong>（XY‑Auto 机型）• <strong>3368</strong>（FYT 机型，多数 evb3561sv 属此族）• <strong>3711、0000、123456</strong> 等备选</td><td>正确密码会立刻进入隐藏菜单；错了则原样返回，继续换密码即可。(<a href="https://xdaforums.com/t/list-of-factory-codes-for-android-head-units.4240053/?utm_source=chatgpt.com">XDA Forums</a>, <a href="https://fcc.report/FCC-ID/2A47F-7038B/5728176.pdf?utm_source=chatgpt.com">FCC Report</a>)</td></tr><tr><td><strong>5  勾选&#x2F;取消  Wi‑Fi</strong></td><td>进入后通常会看到 <strong>General &#x2F; Feature &#x2F; Hardware</strong> 等标签：•  找到 <strong>WIFI &#x2F; WLAN &#x2F; WIFI&amp;BT</strong> 这一项• <strong>启用</strong> → 打勾；<strong>停用</strong> → 取消勾选•  若有 <strong>BT</strong> 选项，可一并视需要勾选</td><td>字段名可能稍有区别，但一定带 “WIFI” 字样；看不到这一行多半是刷错 MCU 或 ROM 被阉割。</td></tr><tr><td><strong>6  保存并重启</strong></td><td>点页面底部 <strong>Save &#x2F; Apply &#x2F; OK</strong>；系统提示 <em>Reboot now?</em> 选 <strong>Yes</strong>，或手动重启一次电源。</td><td>期间千万别断 12 V 电源（ACC + B+)。</td></tr><tr><td><strong>7  验证生效</strong></td><td>重启后进 <strong>设置  →  网络与互联网</strong>：•  若出现 <strong>Wi‑Fi</strong> 开关并能扫描热点，说明启用成功；•  若仍无开关，则回隐藏菜单检查是否勾选保存成功，或确认硬件&#x2F;驱动。</td><td>想进一步排错：<code>adb shell getprop wlan.driver.status</code> 应返回 <code>ok/ready</code>；空值说明驱动仍未加载。</td></tr></tbody></table><hr><h3 id="如果根本找不到“工厂设置”按钮怎么办？"><a href="#如果根本找不到“工厂设置”按钮怎么办？" class="headerlink" title="如果根本找不到“工厂设置”按钮怎么办？"></a>如果根本找不到“工厂设置”按钮怎么办？</h3><ol><li><p><strong>用 Activity Launcher 类工具</strong></p><ul><li>在 APK 市场搜索并安装 <strong>“Activity Launcher”</strong> 或 <strong>“QuickShortcutMaker”</strong>；</li><li>打开后搜索 <code>factory</code> &#x2F; <code>settings.FactorySetting</code> &#x2F; <code>com.twd.factorysetting</code>，点进去也能弹出隐藏菜单。</li></ul></li><li><p><strong>ADB 直接拉起</strong></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">adb shell am start -n com.twd.settings/.FactorySetting   <span class="comment"># FYT/TS10 系常见</span></span><br><span class="line">adb shell am start -n com.android.settings/.DeviceInfoFactory   <span class="comment"># 某些 XY‑Auto</span></span><br></pre></td></tr></table></figure><p>若提示 <strong>Error type 3</strong> 仍找不到，说明 ROM 确实删掉了对应 Activity。</p></li></ol><hr><h3 id="常见疑问速答"><a href="#常见疑问速答" class="headerlink" title="常见疑问速答"></a>常见疑问速答</h3><table><thead><tr><th>疑问</th><th>解答</th></tr></thead><tbody><tr><td><strong>密码都试完仍进不去？</strong></td><td>可能设备品牌归属不同，联系卖家确认密码；或在 Activity Launcher 搜索 <code>factory</code> 直接拉 Activity。</td></tr><tr><td><strong>选项勾了保存却自动跳回未勾？</strong></td><td>MCU 与系统包不匹配 → 刷与主板版本一致、带 <em>wifi_bt</em> 标识的完整 ROM + MCU。</td></tr><tr><td><strong>Wi‑Fi 列表能扫，但想让车机开热点？</strong></td><td>默认无线芯片可能只支持 STA；需外接支持 AP 模式的 USB Wi‑Fi (RTL8188EU&#x2F;RT5370) 或刷支持 hostapd 的内核。</td></tr></tbody></table><hr><h4 id="关键提醒"><a href="#关键提醒" class="headerlink" title="关键提醒"></a>关键提醒</h4><ul><li><strong>操作完一定重启</strong>，否则配置不会写入  NVRAM。</li><li>切记保持 <strong>稳定供电</strong>，避免在行车或电池电压忽高忽低时操作。</li><li>建议首次进入隐藏菜单先点 <em>Backup</em> 或 <em>Export Config</em> 备份一份原始配置，方便误操作后恢复。</li></ul><hr><blockquote><p><strong>一句话</strong>：回桌面 → 打开「设置」→ 找到「工厂设置」→ 输入 8888／3368 等密码 → 在隐藏菜单勾选 <strong>WIFI</strong> → 保存并重启；若连入口都找不到，就用 Activity Launcher 或  ADB 命令直拉工厂设置界面，再按同样步骤操作。祝你成功点亮  Wi‑Fi！</p></blockquote>]]></content>
    
    
    <summary type="html">车机锁 WiFi 与高楼 Mesh 组网的极客解决方案</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="路由器" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
    
    <category term="车载" scheme="https://blog.no-claw.com/tags/%E8%BD%A6%E8%BD%BD/"/>
    
  </entry>
  
  <entry>
    <title>Proxmox Virtual Environment (PVE) 虚拟机安装不完全指南</title>
    <link href="https://blog.no-claw.com/posts/13af016a/"/>
    <id>https://blog.no-claw.com/posts/13af016a/</id>
    <published>2025-05-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文将详细介绍如何在 Proxmox Virtual Environment (PVE) 中安装虚拟机。我们将从系统安装开始，逐步引导您完成虚拟机的创建和配置。</p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>我选择使用 Ventoy 进行引导，这样可以避免反复使用 Etcher 等工具进行写盘操作。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2025/02/14/1739495708950-8ff5ad67-a79d-4c77-b0c0-5e02f5acd091.png" alt="Ventoy引导界面"></p> <span id="more"></span><h2 id="系统安装"><a href="#系统安装" class="headerlink" title="系统安装"></a>系统安装</h2><ol><li><p><strong>启动安装程序</strong></p><p>启动后，您将看到如下界面：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/14/1739495627040-1aa04d81-450c-42c9-8bac-8170d89b7138.png" alt="装机启动页面"></p></li><li><p><strong>用户协议</strong></p><p>阅读并接受用户协议：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/02/14/1739495904206-0d29b327-2ea5-4329-a4e0-9ceb00ae05e4.png" alt="用户协议"></p></li><li><p><strong>选择安装目录</strong></p><p>选择安装 Proxmox VE 的磁盘：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2025/02/14/1739495937155-0e3363b9-5463-4f99-ac50-6b3add6dc2c0.png" alt="选择安装目录"></p></li><li><p><strong>设置密码</strong></p><p>设置 root 用户的密码，邮箱地址可以随意填写：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/14/1739495963290-ac605244-e2e1-458a-a95b-4e8ade3f7fb4.png" alt="设置密码"></p></li><li><p><strong>配置网络</strong></p><p>设置系统的 IP 地址：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2025/02/14/1739496093500-ea037d25-1425-4b00-9b8f-7372307748ac.png" alt="设置IP地址"></p></li><li><p><strong>开始安装</strong></p><p>确认所有设置后，开始安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img12@main/2025/02/14/1739496103042-b405e76f-9e53-47c8-b1ec-8159079663d4.png" alt="写盘安装"></p></li><li><p><strong>安装完成</strong></p><p>安装完成后，系统将提示您访问 Web 管理界面，默认端口为 8006：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/14/1739496130460-7c1478d4-2ed3-4e1d-961a-0c9c8f440cb7.png" alt="安装完成"></p></li><li><p><strong>重启系统</strong></p><p>拔掉引导 U 盘并重启系统：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2025/02/14/1739496156501-56f4c603-2a22-4313-bdd9-5c79e603fff3.png" alt="重启系统"></p></li></ol><h2 id="登录与配置"><a href="#登录与配置" class="headerlink" title="登录与配置"></a>登录与配置</h2><ol><li><p><strong>登录系统</strong></p><p>使用前面设置的密码登录系统：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/02/14/1739496256424-0a1e4ec0-fb5c-49d4-b017-49a9982f1dde.png" alt="登录界面"></p></li><li><p><strong>Web 管理界面</strong></p><p>登录后，您将看到 Proxmox VE 的 Web 管理界面：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/14/1739496317429-c7c595d0-28f7-48be-94ee-6c534ac44c7f.png" alt="Web管理界面"></p></li><li><p><strong>测试网络连通性</strong></p><p>确保网络连接正常：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2025/02/14/1739496429879-3932554f-71c9-42c4-8b69-d23619e0781b.png" alt="测试网络连通性"></p></li></ol><h2 id="创建虚拟机"><a href="#创建虚拟机" class="headerlink" title="创建虚拟机"></a>创建虚拟机</h2><ol start="4"><li><p><strong>上传 ISO 文件</strong></p><p>首先，上传操作系统的 ISO 文件：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/14/1739496398176-ae0c6dfc-6935-4692-adfd-5f18a31ae8ed.png" alt="上传ISO文件"></p></li><li><p><strong>创建虚拟机</strong></p><ul><li><p><strong>设置虚拟机编号</strong></p><p>为虚拟机设置一个编号：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/02/14/1739496452054-57e7920b-fb05-4ef9-a21e-a61a1faa4f96.png" alt="设置虚拟机编号"></p></li><li><p><strong>选择镜像</strong></p><p>选择之前上传的 ISO 文件作为安装镜像：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img10@main/2025/02/14/1739496486416-b23bcf19-3054-4227-9f38-8734adda0d7a.png" alt="选择镜像"></p></li><li><p><strong>配置硬件</strong></p><ul><li><p><strong>主板信息</strong></p><p>保持默认设置即可：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img10@main/2025/02/14/1739496507107-d7b905a2-15f2-4f82-984b-de1838473abb.png" alt="主板信息"></p></li><li><p><strong>磁盘设置</strong></p><p>建议选择 VirtIO 以提高性能：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2025/02/14/1739496518036-c92d53c9-26f1-41b0-a2f8-c0a27ff3da6e.png" alt="磁盘信息"></p></li><li><p><strong>CPU 配置</strong></p><p>注意不要超过宿主机的 CPU 核心数，否则虚拟机将无法启动：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2025/02/14/1739496553350-c56e06ea-452e-4c47-83ba-c5862ee4fb2c.png" alt="CPU配置"></p></li><li><p><strong>内存分配</strong></p><p>根据需求分配内存：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img2@main/2025/02/14/1739496596444-f2f1e55d-5cbb-4785-bf62-0594c8b21fd5.png" alt="内存分配"></p></li><li><p><strong>网络设置</strong></p><p>同样建议选择 VirtIO：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/14/1739496606806-c87db584-4e0d-4cad-a02a-85b99203c78a.png" alt="网络设置"></p></li></ul></li><li><p><strong>确认信息</strong></p><p>确认所有设置无误后，点击完成：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2025/02/14/1739496631571-e5268655-ef8a-4013-8150-ef9a4c52445d.png" alt="确认信息"></p></li></ul></li><li><p><strong>启动虚拟机</strong></p><p>启动虚拟机并开始安装操作系统：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/14/1739496828640-31167397-96bf-4119-818a-43d50bb773d8.png" alt="启动虚拟机"></p></li></ol><h2 id="操作系统安装"><a href="#操作系统安装" class="headerlink" title="操作系统安装"></a>操作系统安装</h2><ol start="7"><li><p><strong>安装过程</strong></p><p>按照提示进行操作系统安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img4@main/2025/02/14/1739496852570-0ce8ac5b-35ca-4a3b-8941-b7743854c09d.png" alt="安装过程"></p></li><li><p><strong>加载驱动</strong></p><p>在安装过程中，加载 VirtIO 驱动：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img10@main/2025/02/14/1739496688201-f337a991-9413-46f1-b7d6-ac9f0d37c1e7.png" alt="加载驱动"></p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/14/1739496710136-4aaabb2e-1f4a-4557-9fb1-29f95dc31e38.png" alt="选择VirtIO驱动"></p></li><li><p><strong>分区与安装</strong></p><p>对磁盘进行分区并开始安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2025/02/14/1739496747084-525bc23a-cefd-4125-b475-4ffd8daa5a15.png" alt="分区与安装"></p></li><li><p><strong>复制安装文件</strong></p></li></ol><p>安装程序将复制文件并进行系统安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/02/14/1739496768338-782c747c-30a2-4621-a3eb-0256ffecb588.png" alt="复制安装文件"></p><ol start="11"><li><strong>设置时间</strong></li></ol><p>设置系统时区：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2025/02/14/1739496890195-2798b236-ecf5-4a58-802b-96a47f69ac6f.png" alt="设置时间"></p><ol start="12"><li><strong>网络配置</strong></li></ol><p>安装完成后，系统可能暂时无法联网，稍后我们将安装网络驱动：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2025/02/14/1739496907046-ffc098df-4d4f-4e74-b5e5-86a71f377844.png" alt="网络配置"></p><ol start="13"><li><strong>设置用户名和密码</strong></li></ol><p>为操作系统设置用户名和密码：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2025/02/14/1739496981075-0188047a-70cd-47a1-b054-c9687e674b35.png" alt="设置用户名和密码"></p><ol start="14"><li><strong>安装 VirtIO 驱动</strong></li></ol><p>安装 VirtIO 网络驱动以确保网络功能正常：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/02/14/1739496999682-a1dea257-7663-430b-90f4-6967c0ca7a43.png" alt="安装VirtIO驱动"></p><h2 id="Linux-系统安装"><a href="#Linux-系统安装" class="headerlink" title="Linux 系统安装"></a>Linux 系统安装</h2><p>对于 Linux 系统，安装过程类似，但通常 Linux 系统已经自带 VirtIO 驱动，无需手动安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2025/02/14/1739497063172-b31a2a3d-f080-4f5d-8842-603e1db11cfe.png" alt="Linux安装"></p><p>通过以上步骤，您应该已经成功在 Proxmox VE 中安装并配置了虚拟机。希望本指南对您有所帮助！</p>]]></content>
    
    
    <summary type="html">PVE 虚拟化平台安装指南，从零搭建家庭虚拟化环境。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（二）：打造多人协作环境,权限控制+数据隔离一步到位</title>
    <link href="https://blog.no-claw.com/posts/4d17c8d/"/>
    <id>https://blog.no-claw.com/posts/4d17c8d/</id>
    <published>2025-05-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在懒猫微服上架了一些应用，正好分享给家里人用。这个实现起来很简单，在懒猫微服上开一个新的用户，然后控制这个用户是否可以安装软件，可以使用什么软件，这样不同用户之间的数据就隔离开了，比如每个人的懒猫网盘和懒猫清单是独立的，互相不会产生干扰。</p><p>下面就以实际操作为例，教大家新建用户，并且了解这些自带的安全机制。</p><h2 id="新建用户"><a href="#新建用户" class="headerlink" title="新建用户"></a>新建用户</h2><p><strong>设置</strong> - <strong>用户管理</strong>，这里可以看到现存的账户，第一次激活的时候会提示注册一个管理员账户，后面可以右上角点击邀请成员，然后会得到弹出一个二维码，新的客户端需要下载懒猫客户端，然后客户端扫码添加输入信息即可。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510111411166.png" alt="image-20250510111411166"></p><p>扫码后可以为新用户设置用户名和密码。客户端下载地址：<br>👉 <a href="https://lazycat.cloud/download">https://lazycat.cloud/download</a></p><span id="more"></span><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510112156501.png" alt="image-20250510112156501"></p><p>注册完成后，新成员就可以使用自己的账号登录懒猫微服啦。默认是“非管理员权限”，更安全。</p><h2 id="用户权限控制"><a href="#用户权限控制" class="headerlink" title="用户权限控制"></a>用户权限控制</h2><p>新建之后，我对这个新用户的画像是用户而不是管理者，所以只需要登录之后看到应用白名单就可以了。</p><p>点击新建用户的头像，可以设置用户可以看见应用的白名单。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510114715784.png" alt="image-20250510114715784"></p><p>比如，我只授权了家庭成员使用懒猫网盘、懒猫清单等基础工具。于是使用手机端登录新的账户，可以看到在<strong>我的应用</strong>中只有刚刚选中那些，这对于日常使用来说刚刚好，这个页面相对于安装了几十个 app 的管理页面来说，实在是清爽。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510114840299.png" alt="image-20250510114840299"></p><h3 id="登录的二次验证"><a href="#登录的二次验证" class="headerlink" title="登录的二次验证"></a>登录的二次验证</h3><p>在此之前我们先来介绍多因子验证（MFA），通俗来讲我们在使用用户名密码登录的时候有时候还要接收一个验证码，有时候是发到邮箱的，有的是手机短信，还有个需要安装特定的 APP 来查看，比如 Authy，Google Authenticator。</p><p>而懒猫微服在 APP 中内置了 MFA 接收验证码的功能，新设备登录的时候会有如下提示：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510120716665.png" alt="image-20250510120716665"></p><p>在一台新的设备登录懒猫账户的时候，已经登录这个账户的设备就会弹出这个提示，这个时候我一般是提前打开懒猫微服 APP。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510115337692.png" alt="image-20250510115337692"></p><p>当然如果没有提前打开或者登录呢，也可以在「微服管理 - 安全码」来查看，比如这样，这个方式很 Apple 但是用着比 Apple 的提示舒服多了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510115726710.png" alt="image-20250510115726710"></p><p>那么什么时候才会触发这个 MFA 呢？经过不完全测试，主要还是在这里设备管理这里有一个云端白名单，在这里的设备可以就可以免去 MFA 的验证，如果删除某个设备之后，这个设备会马上注销登录，并且在此登录的时候还需要 MFA 验证。这个操作，极大了降低了被黑客攻击的可能性。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510115805192.png" alt="image-20250510115805192"></p><p>当然如果你觉得 MFA 比较麻烦，那么也可以使用手机号码的方式进行登录，绑定手机号，然后用收验证码的方式进行登录,比如这样：</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">【懒猫微服】验证码：0000，5分钟内有效！请勿转发或泄漏。</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250510120805601.png" alt="image-20250510120805601"></p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>以前用过很多 NAS，一直苦于怕黑客攻击而没有监管 NAS 放在互联网，而一套完整的登录机制也要花费很大的精力去维护，拓展。期间也选择过蒲公英这样的异地组网设备，虽然可以达到目的，但是过程不尽如人意，对于很多国产生态来说，售后一直是缺失的很重要的一环。而懒猫微服恰好弥补了这样的短板，让懂技术的人从繁杂的维护设备中解放出来，像使用公有云一样的使用 NAS。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/fec2fe20-307a-49d2-8cc5-a5ec370d31c5.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服多用户管理教程，权限控制与数据隔离配置一步到位。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="MFA" scheme="https://blog.no-claw.com/tags/MFA/"/>
    
  </entry>
  
  <entry>
    <title>轻松管理 ES 集群：我把 Infinilabs Console 上架了懒猫微服，默认支持外网访问、</title>
    <link href="https://blog.no-claw.com/posts/75c015c8/"/>
    <id>https://blog.no-claw.com/posts/75c015c8/</id>
    <published>2025-05-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前我的 infinilabs Console 一直跑在群晖里，由于和 Coco-AI 的默认端口冲突，导致经常忘记端口信息，群晖里运行着 Easysearch，Elasticsearch、OpenSearch 三个大集群，也想慢慢迁移到其他性能高的机器上去，正好最近购买了懒猫微服，能够让我做应用的迁移，顺便还得能上架一些应用。</p><h2 id="Infinilabs-console-是什么？"><a href="#Infinilabs-console-是什么？" class="headerlink" title="Infinilabs.console 是什么？"></a>Infinilabs.console 是什么？</h2><p>如果你用过 Elasticsearch，那就一定知道 Kibana。Infinilabs Console，就是极限科技团队开发的国产可视化控制台，是一个面向 Easysearch、Elasticsearch 和 OpenSearch 的运维、监控、数据管理平台，可以看作是国产版的 Kibana 替代品。</p><p>最初接触这个款产品的时候让我眼前一亮，它能够借助 Easysearch 或者 Elasticsearch 的 REST API 来连接集群，同时也高效地管理和监控 Elasticsearch、OpenSearch 以及 INFINI Easysearch 等搜索引擎集群，提供统一的运维、监控、安全和数据管理能力。这一点其实是 Kibana 比不了的，尽管是老牌软件，但是初学 ES 的时候 Kibana 连接 ES 要查 log 设置一些 key，这个整个部署过程就花了一个小上午的时间。而且跨版本，跨引擎来支持的能力也是其他可视化工具无法比拟的。简单来说，真的很符合国人的使用习惯。</p><span id="more"></span><p>首先我们可以在连接的时候不同的引擎（Easysearch、Elasticsearch、OpenSearch ），以及你集群的位置（线下还是在各种云上），同时支持 HTTP 和 HTTPS 的连接。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509103447515.png" alt="连接集群"></p><p>连接之后，可以看到已经正确识别出来了的 Easysearch、Elasticsearch 和 OpenSearch，并且抓取了相应的数据监控，比如基本的集群状态，节点数量，索引，分片以及文档的数量，还有磁盘和 JVM 的占用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509103439985.png" alt="集群信息"></p><p>执行 DSL 的时候可以开启多个 TAB 页这个是我最喜欢的功能，尤其在做集群迁移的时候再也不用找不同的系统去登录了，这里手动@aws 的 OpenSearch。除此之外，做快照传到 S3 的时候也不用担心 access_key 读不到的问题了。曾经我是托管 OpenSearch 的用户，托管节点有诸多问题，无法登录，由于服务本身的问题导致业务滞后（升级卡住，看门狗不定时杀进程），做快照必须借助 Postman 来传递 IAM 凭证。但，换了 Infinilabs console 和 Easysearch 之后，整个世界都清净了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509103527869.png" alt="DSL 开发工具"></p><p>GitHub 项目地址如下：<a href="https://github.com/infinilabs/console">https://github.com/infinilabs/console</a></p><h2 id="为什么选择了懒猫商店？"><a href="#为什么选择了懒猫商店？" class="headerlink" title="为什么选择了懒猫商店？"></a>为什么选择了懒猫商店？</h2><p>懒猫微服解决了我日常使用 NAS 的几个痛点：</p><ul><li>装了一堆服务（Redis、MinIO、MeiliSearch、Adminer、Swagger UI……），入口太分散；</li><li>每次看容器状态都要 <code>docker ps</code> 一把梭；</li><li>Homepage 要手动配置，配置文件写起来太繁琐；</li></ul><p>部署成功后会给到一个域名，然后通过域名访问可以自动解析内外网的 IP 地址，同时也自带了路由守卫功能来重定向到懒猫的 SSO，</p><p>而在传统 NAS 部署 Authentik 然后再去应用端做 SSO 的适配应该是 NAS 玩家的终极梦想，而上架商店之后自动集成了这样的认证系统（也是单点登录）。然后，在外边的时候也可以监控和操作自己的 ES 集群啦～（随地大小班的理由又多了一条）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509110532533.png" alt="保护应用的 SSO"></p><p>因为上架的应用是 HTTP 的，懒猫微服还能自动做了一个 TLS 传输，用的他们自己域名，然后通过 https 访问 Infinilabs Console。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509110738032.png" alt="自带 TLS"></p><p>除此之外还自带了 dozzle，可以很方便查看安装应用的上架信息，毕竟对于开发者来说，装机玩 NAS 是兴趣，但是搭建好之后的维护问题也同样劳心费力，真的一点都不想浪费时间和精力，那么杂活就交给平台来管理吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250509113301412.png" alt="查询应用日志"></p><p>进入懒猫微服的【应用商店】，搜索：<code>infinilabs.console</code>一键安装并启动，打开浏览器，开始使用 Infinilabs Console 吧～</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/5cde62de-530b-4364-95a6-033fd289cb98.png" alt="image.png" title="image.png"></p><p>地址如下：</p><p><a href="https://lazycat.cloud/appstore/#/shop/detail/xu.infinilabs.console">https://lazycat.cloud/appstore/#/shop/detail/xu.infinilabs.console</a></p><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><ul><li>infinilabs.console 介绍：<a href="https://infinilabs.cn/products/console/">https://infinilabs.cn/products/console/</a></li><li>infinilabs Github 介绍：<a href="https://infinilabs.cn/products/console/">https://infinilabs.cn/products/console/</a></li><li>懒猫微服上架地址：<a href="https://lazycat.cloud/">https://lazycat.cloud/</a></li><li>懒猫微服官网：<a href="https://lazycat.cloud/">https://lazycat.cloud/</a></li></ul><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/c4ab5141-2854-4536-b95e-35001d6cc3f2.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">将 Infinilabs Console 上架懒猫微服商店，轻松管理 ES 集群并支持外网访问</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>懒猫微服实战入门（一）： 从开启SSH到免密登录，一步步教你搞定远程管理</title>
    <link href="https://blog.no-claw.com/posts/56f9b737/"/>
    <id>https://blog.no-claw.com/posts/56f9b737/</id>
    <published>2025-05-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>对于很多资深的 NAS 玩家来说，拿到一台机器首先要配置远程登录和环境依赖。懒猫上其实可以实现云计算讲的 Iass - Pass -Sass 这三个层级，不过对于资深玩家而言，肯定是要从 Infra 这个级别入手的。</p><p>官方文档如下：<br><a href="https://developer.lazycat.cloud/ssh.html">https://developer.lazycat.cloud/ssh.html</a></p><p>安装懒猫开发者工具，然后再右上角能够看到 sshd 服务的状态。<br>然后点击开启，之后我们才可以使用 ssh 登录，在写这篇文章测试的时候，我关闭了这个按钮，再去 ssh 直接就报错了。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/823b1afe-fb70-4866-8303-aa051e9b65bc.png" alt="image.png" title="image.png"></p><p>默认是 root 身份登录，密码在开发者工具里启动的时候设置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh root@&lt;your-service-name&gt;.heiyu.space</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">ssh-copy-id -i ~/.ssh/id_ed25519 root@xxxxx.heiyu.space</span><br></pre></td></tr></table></figure><span id="more"></span><p>输出如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: &quot;/Users/xu/.ssh/id_ed25519.pub&quot;</span><br><span class="line">/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed</span><br><span class="line">/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys</span><br><span class="line">root@micro.heiyu.space&#x27;s password:</span><br><span class="line"></span><br><span class="line">Number of key(s) added:        1</span><br><span class="line"></span><br><span class="line">Now try logging into the machine, with: &quot;ssh -i /.ssh/id_ed25519 &#x27;root@xxxx.heiyu.space&#x27;&quot;</span><br><span class="line">and check to make sure that only the key(s) you wanted were added.</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果觉得 root 用户不安全的话，可以新建一个日常用户，然后加到 docker 组里面，也能正常使用 docker</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> useradd -m -s /bin/bash user1</span><br><span class="line"></span><br><span class="line">usermod -aG docker user1</span><br><span class="line"></span><br><span class="line">usermod -aG <span class="built_in">sudo</span> user1</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/e40ad428-93be-487e-880c-d37c76f27fc1.png" alt="image.png" title="image.png"></p><p>如果遇到到 root 组会有无法使用 sudo 的问题，请独立安装，sudo 是单独的软件包,需要安装才有.并不是所有 Linux 都有 sudo</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt update &amp;&amp; apt install <span class="built_in">sudo</span></span><br></pre></td></tr></table></figure><p>注意：要开着懒猫微服 APP ，否则无法使用 heiyu.space 提供的穿透服务。</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/a303adbb-d3f5-4fa2-9a6d-6d3b25abbe34.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服开启 SSH 并配置免密登录的完整教程，轻松搞定远程管理。</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="入门" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E5%85%A5%E9%97%A8/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="SSH" scheme="https://blog.no-claw.com/tags/SSH/"/>
    
  </entry>
  
  <entry>
    <title>快速检索懒猫商店1000+应用,微服秒变Mac原生APP</title>
    <link href="https://blog.no-claw.com/posts/ce3905e3/"/>
    <id>https://blog.no-claw.com/posts/ce3905e3/</id>
    <published>2025-05-06T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>懒猫商店如今已有 1000+ 应用，日常使用中经常要在搜索栏反复查找，着实有些不便。有没有更简单的方法呢？答案是：<strong>可以直接把网页保存成 Mac 应用，像手机 App 一样快捷打开！</strong></p><p>下面就手把手教大家几种实用的方法。</p><h3 id="优雅方案——PWA"><a href="#优雅方案——PWA" class="headerlink" title="优雅方案——PWA"></a>优雅方案——PWA</h3><p>在 Mac 上，我们有更高级的玩法。<br>不少现代网站都支持 <strong>PWA（Progressive Web App）</strong>，简单来说，就是让网页像 App 一样运行：</p><ul><li>可以像应用一样安装在本地</li><li>点击图标就能直接启动，无需打开浏览器</li><li>界面简洁，没有多余的地址栏和标签页</li></ul><p>下面是懒猫清单的安装效果：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506145822013.png" alt="懒猫清单 PWA 效果图"></p><span id="more"></span><p><strong>支持 PWA 的网站，在地址栏右侧会自动弹出“安装应用”按钮。</strong></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506144310554.png" alt="添加应用"></p><p>只需点击它，就能轻松将网页保存为应用。</p><blockquote><p>PWA 的优点：速度快、体验好、支持离线，真正做到了网页与 App 的无缝结合。</p></blockquote><p>通过 PWA 添加之后，会在 Finder 里弹出 Chrome 应用，我这里添加了懒猫网盘，懒猫原生的 APP 基本都是带 PWA 的，所以这一点体验很好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506144413958.png" alt="Chrome应用"></p><p>添加完桌面应用之后，浏览器会有“在应用中打开”的提示，点击就可以像 APP 一下打开，就是前面第二张懒猫清单的图片。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506193028087.png"></p><h3 id="如何通过-Chrome-中安装懒猫-Web-应用"><a href="#如何通过-Chrome-中安装懒猫-Web-应用" class="headerlink" title="如何通过 Chrome 中安装懒猫 Web 应用"></a>如何通过 Chrome 中安装懒猫 Web 应用</h3><ol><li>在 Chrome 浏览器中打开你要保存的网站（如懒猫微服务）。</li><li>点击右上角“更多”按钮，依次选择**投放、保存和分享 → 将网页安装为应用…**。</li><li>有些网站也会直接在地址栏右侧显示“安装”图标，点一下即可快速安装。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506145022908.png" alt="Chrome 安装应用步骤"></p><p>安装时你可以自定义应用名称，这里以 OnlyOffice 为例。</p><p>这样做还可以解决 Mac 没有 Office 订阅的痛点，直接通过网页版弥补。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506145157209.png" alt="OnlyOffice 安装为应用"></p><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">/Users/你的用户名/Applications/Chrome Apps.localized/</span><br></pre></td></tr></table></figure><p>它们会以<code>.app</code>格式存在，完全就像普通 Mac 应用一样。</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">❰~/Applications/Chrome Apps.localized❱✔≻ <span class="built_in">ls</span></span><br><span class="line">Icon?                懒猫清单.app/</span><br><span class="line">ONLYOFFICE Docs.app/ 懒猫网盘.app/</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506145210451.png" alt="应用存放目录"></p><h3 id="如何通过-Safari-中把懒猫应用添加为-APP"><a href="#如何通过-Safari-中把懒猫应用添加为-APP" class="headerlink" title="如何通过 Safari 中把懒猫应用添加为 APP"></a>如何通过 Safari 中把懒猫应用添加为 APP</h3><p>对于不支持 PWA 的网站，Safari 也提供了一个类似的解决方案。</p><ol><li>在 Safari 中打开要保存的网页。</li><li>选择<strong>“文件 → 添加到程序坞”</strong>，或者点击<strong>“共享”按钮 → 添加到程序坞</strong>。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506202105954.png" alt="Safari 添加应用操作"></p><p>输入自定义的应用名称，点击<strong>“添加”</strong>。这个应用会自动放在应用程序里面。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506202120973.png" alt="输入应用名称"></p><p>应用将会被保存到“应用程序”文件夹中，支持从程序坞、启动台或 Spotlight 快速启动。</p><h3 id="直接拖拽到-Dock，一键启动"><a href="#直接拖拽到-Dock，一键启动" class="headerlink" title="直接拖拽到 Dock，一键启动"></a>直接拖拽到 Dock，一键启动</h3><p>无论是通过 Chrome 还是 Safari 安装的网页 App，安装完成后都可以像普通应用一样拖到 Dock。</p><p>只需保持懒猫微服务后台连接，点击 Dock 图标，就能立即打开应用，体验和原生 App 无异！</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250506202828139.png" alt="拖到 Dock 后效果"></p><h3 id="进阶玩法：自定义网页启动器"><a href="#进阶玩法：自定义网页启动器" class="headerlink" title="进阶玩法：自定义网页启动器"></a>进阶玩法：自定义网页启动器</h3><p>当然，你也可以用 Python 快速实现一个简单的网页启动器：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> webbrowser</span><br><span class="line"></span><br><span class="line">webbrowser.<span class="built_in">open</span>(<span class="string">&quot;https://www.apple.com&quot;</span>)  <span class="comment"># 打开网页</span></span><br></pre></td></tr></table></figure><p>支持新窗口、新标签等操作，适合简单自定义。</p><h1 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h1><p>通过以上方法，我们就可以把常用的懒猫 APP 变成 Mac 的桌面应用，随时一键直达，告别繁琐的搜索过程，体验飞跃式提升！</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/b551e149-48d5-4b6c-b570-65f295961d4b.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">用 PWA 将懒猫商店变成 Mac 原生应用，快速检索上千款应用</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
  </entry>
  
  <entry>
    <title>MySQL数据实时接入Easysearch，零代码迁移全流程</title>
    <link href="https://blog.no-claw.com/posts/7a296932/"/>
    <id>https://blog.no-claw.com/posts/7a296932/</id>
    <published>2025-05-04T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着业务对数据搜索与分析能力的要求不断提高，越来越多的团队开始将关系型数据库中的数据迁移至搜索引擎中。<br>本篇文章将分享如何通过<strong>AWS DMS</strong>，实现 MySQL 数据无缝迁移到<strong>EasySearch</strong>，并打通实时同步链路的全过程。</p><blockquote><p>注意：AWS DMS 原生仅支持托管的 OpenSearch 和 Elasticsearch，不直接支持 EasySearch。本文将介绍如何通过一些配置技巧，优雅地解决这个问题。</p></blockquote><hr><h2 id="一、准备-MySQL-源数据库"><a href="#一、准备-MySQL-源数据库" class="headerlink" title="一、准备 MySQL 源数据库"></a>一、准备 MySQL 源数据库</h2><h3 id="1-创建数据库与数据表"><a href="#1-创建数据库与数据表" class="headerlink" title="1. 创建数据库与数据表"></a>1. 创建数据库与数据表</h3><p>首先，我们需要准备好待迁移的 MySQL 数据库。我这里使用的是<strong>DBeaver</strong>工具，当然你也可以选择更专业的 MySQL Workbench 或 DataGrip。</p><p>新建数据库时，选择<code>utf8mb4</code>编码，库名命名为<code>source</code>（后续 DMS 迁移任务中会用到）。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/651324794a377a2f3dc9b0a6f95cb852.png" alt="新建数据库"></p><p>接下来创建数据表并定义字段。图形化工具可以避免手写 DDL，对非 DBA 用户非常友好。</p><span id="more"></span><p><img src="https://i-blog.csdnimg.cn/img_convert/bbd6173a5e00063914aa7603f5b6bce2.png" alt="新建表"></p><h3 id="2-插入测试数据"><a href="#2-插入测试数据" class="headerlink" title="2. 插入测试数据"></a>2. 插入测试数据</h3><p>为了验证迁移效果，我们先写几条假数据。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/811cb54cdfa7cbae3995aaa5915e6ccf.png" alt="填充数据"></p><p>确认数据已成功写入并提交。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/230505f6100f91989676fc771ae57e76.png" alt="确认数据"></p><hr><h2 id="二、配置-AWS-DMS-迁移"><a href="#二、配置-AWS-DMS-迁移" class="headerlink" title="二、配置 AWS DMS 迁移"></a>二、配置 AWS DMS 迁移</h2><h3 id="1-创建源端点（MySQL）"><a href="#1-创建源端点（MySQL）" class="headerlink" title="1. 创建源端点（MySQL）"></a>1. 创建源端点（MySQL）</h3><p>在 AWS DMS 中，首先需要定义<strong>源端点</strong>。MySQL 作为数据源，EasySearch 作为目标端。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/39995ab5314ef9a720f5d6bee7129c1c.png" alt="创建源端点"></p><h3 id="2-创建目标端点（EasySearch）"><a href="#2-创建目标端点（EasySearch）" class="headerlink" title="2. 创建目标端点（EasySearch）"></a>2. 创建目标端点（EasySearch）</h3><p>我的 EasySearch 部署在公网的 Linux 服务器上。配置目标端点时，有两个重点：</p><ul><li>关闭认证与 TLS，将传输协议改为 HTTP</li><li>伪装为开源 Elasticsearch，绕过 DMS 的原生认证要求</li></ul><p><img src="https://i-blog.csdnimg.cn/img_convert/c09a6ac0e944c01f6bbfe33f167a67ed.png" alt="配置Easysearch端点"></p><p>只需要调整这两个关键参数，即可完成兼容。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/41848a18b4341576d8277f2da8153847.png" alt="Easysearch端关键参数"></p><h3 id="3-创建复制实例"><a href="#3-创建复制实例" class="headerlink" title="3. 创建复制实例"></a>3. 创建复制实例</h3><p>迁移任务需要一个<strong>复制实例</strong>，即 DMS 后台自动启动的迁移代理服务器。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/492766cd92cbe05a54c06c79a30d59c7.png" alt="复制实例"></p><h3 id="4-测试连接"><a href="#4-测试连接" class="headerlink" title="4. 测试连接"></a>4. 测试连接</h3><p>实例启动后，记得测试源端（MySQL）和目标端（EasySearch）的连通性，确保网络正常。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c46fff38da46b609f827d294c7c8c6c1.png" alt="测试MySQL连通"></p><p><img src="https://i-blog.csdnimg.cn/img_convert/df9bfa3e151db2d868f1fdc519a7bc30.png" alt="测试Easysearch连通"></p><hr><h2 id="三、创建迁移任务并启动"><a href="#三、创建迁移任务并启动" class="headerlink" title="三、创建迁移任务并启动"></a>三、创建迁移任务并启动</h2><p>在控制台新建迁移任务</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c6fa271d4b36963241bce7cb857989b4.png" alt="新建迁移任务"></p><h3 id="1-预检查：开启-MySQL-Binlog"><a href="#1-预检查：开启-MySQL-Binlog" class="headerlink" title="1. 预检查：开启 MySQL Binlog"></a>1. 预检查：开启 MySQL Binlog</h3><p>为了支持 CDC（持续复制），需要提前在 MySQL 开启 binlog，并调整格式为<code>ROW</code>。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/e59c53931b83c24efc3a26db4b9acc6e.png" alt="开启binlog"></p><p><img src="https://i-blog.csdnimg.cn/img_convert/9b00b6ad96fa1d28f9cae3fdec14ec78.png" alt="修改binlog格式"></p><h3 id="2-创建迁移任务"><a href="#2-创建迁移任务" class="headerlink" title="2. 创建迁移任务"></a>2. 创建迁移任务</h3><p>在 DMS 中创建任务，选择<strong>持续复制</strong>模式，源库填写<code>source</code>，EasySearch 会自动将数据表转为索引。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/dfaff18db6f54b65609bb1f5b1f2e65c.png" alt="迁移任务"></p><p>注意：目标端为 EasySearch 时<strong>需要关闭数据验证</strong>，否则迁移任务会因兼容性问题失败。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/f4dd30236ac44b6a34af28c29f4cfb87.png" alt="关闭验证"></p><hr><h2 id="四、验证迁移效果"><a href="#四、验证迁移效果" class="headerlink" title="四、验证迁移效果"></a>四、验证迁移效果</h2><h3 id="1-启动任务并查看索引"><a href="#1-启动任务并查看索引" class="headerlink" title="1. 启动任务并查看索引"></a>1. 启动任务并查看索引</h3><p>EasySearch 初始化状态下只有默认索引。<br>启动任务后，DMS 自动创建了一个新索引<code>newtable</code>，映射 MySQL 的数据表。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/f147c6e4ba1706f869a162ff9498de3a.png" alt="EasySearch初始状态"></p><p><img src="https://i-blog.csdnimg.cn/img_convert/b513d1400c4b61228bb8ab44c87df907.png" alt="newtable索引"></p><p>打开索引，可以看到 MySQL 数据已转换为 EasySearch 的文档格式。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/ba0f7e1720399a10e02c7c5c07517ce5.png" alt="查看数据"></p><h3 id="2-模拟实时数据同步"><a href="#2-模拟实时数据同步" class="headerlink" title="2. 模拟实时数据同步"></a>2. 模拟实时数据同步</h3><p>因为是 CDC 持续复制模式，我继续向 MySQL 插入新数据，模拟上游系统的实时写入。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/fc30add0cde7c55687b64dcbdb3ad70e.png" alt="继续插入数据"></p><p>EasySearch 这边几乎实时就收到了新数据，验证了迁移链路的连贯性。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/734cf8c389d53dd5aae0792d3995ee45.png" alt="查询新数据"></p><hr><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>本次实战中，借助 AWS DMS，我们实现了从 MySQL 到 EasySearch 的实时数据同步，具备以下优势：</p><ul><li><strong>无需改造业务系统</strong>，兼容现有 MySQL 数据结构</li><li><strong>支持 CDC</strong>，保证数据的实时同步与一致性</li><li><strong>EasySearch 原生接入</strong>，数据即迁即用</li></ul><p>虽然 AWS DMS 默认并不支持 EasySearch，但通过合理配置与兼容策略，我们依然实现了两者的高效打通。</p><p>如果你的业务需要将 MySQL 数据实时同步到 EasySearch，这套方案值得一试。</p>]]></content>
    
    
    <summary type="html">零代码实现 MySQL 数据实时同步到 Easysearch 的完整流程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>自带内网穿透,懒猫微服治好了我的NAS焦虑</title>
    <link href="https://blog.no-claw.com/posts/b1e8232f/"/>
    <id>https://blog.no-claw.com/posts/b1e8232f/</id>
    <published>2025-05-03T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>知道懒猫微服是一两年前，最初的印象是极客机甲风格，有颜值有性能有。近期入手也是出于朋友推荐,主要还是喜欢自带的内网穿透功能,虽然家里有公网 IP，但是不定时会被运营商封端口（不止常用端口），总是一阵一阵的，询问运营商也没有一个明确的结论，倒不如选一个商业的产品然后省去自己折腾的时间吧</p><p>虽然我一直很想 DIY 这样一款产品，开源的大多是 KVM-base 的方案，或者还有商业的 EXSI。毕竟个人精力有限，一直搁置到现在，然后随着事情越来越多，就购买了一台来玩玩，也脱胎换骨当甲方提需求。官网如下：<a href="https://lazycat.cloud/">https://lazycat.cloud/</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504201210064.png" alt="image-20250504201210064"></p><p>机器是这样的，浓浓的机甲风格连，着运行了几天也一点都不烫。之前还担心炒豆子的问题，其实相对于白天的噪音几乎是没有的。</p><span id="more"></span><ul><li><p>i5-1135G7，现在来看不算最新的，不过也比很多 NAS 强很多了，手动@群晖</p></li><li><p>32G 内存（只能一个盘位，所以加满了）</p></li><li><p>2.5 寸 2T 原装 HDD（预算有限，目前还在测试阶段，自带的盘是叠瓦盘，介意的话可以自己买盘替换）</p></li></ul><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504200345665.png" alt="image-20250504200345665"></p><p>neofetch 能看到是基于 Debian12 的。然后开发团队在上层构建自己的应用，只是 ssh 需要额外申请，不过一会就批了。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504202608288.png" alt="image-20250504202608288"></p><p>提供全平台的客户端，该有的都有了，这里开发适配应该花了不少时间吧。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504195908460.png" alt="image-20250504195908460"></p><p>MacOS 客户端的界面如下, 如果通过 web 访问就是这样，和传统的群晖有个主页不一样，这个更像是服务导向的，对于小白来说，只需要按照 Sass 的方式来使用，比如文件备份，时间机器，异地组网。而对于技术人员来讲，我个人觉得是更加吃力一些，要搞清楚每个服务是怎么启动的，怎么保证网络传输，怎么保证 HA，尽管懒猫团队已经实现了这些，但是出于职业习惯，还是希望抽丝剥茧，搞清楚从 Iass 到 Sass 的原理，然后学一学背后的哲学，以及在懒猫的商店上架自己应用，还有把应用接入懒猫的 SSO 系统。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504171611929.png" alt="image-20250504171611929"></p><p>关于服务嘛，是大多数玩家最喜欢的地方，有专门的 N 对 1 答疑，7*18 服务比 7 *24 听起来要靠谱很多，只能说专业，太专业了。团队是 deepin 二次创业出来的，都是技术流，所以懂用户，在群里可以做深度的技术答疑。在我之前的感受就是，玩 nas 的太多是爱好者，很多不懂 Linux 和开发，或者懂技术的都是理论派，对自己 host-server 或者 application 没有兴趣。以前的同事能因为这个结缘，由于彼此方向不同更多会集中在 Iass 和 network 的层面，而端对端的解决方案甚少。</p><p>但是懒猫让我看到了未来 nas 进化的方向，从硬件 - Iass -pass - Sass 做了全套的定制，也做了我一直想做而没有做完的事情，最早我的想发是在 Centos 上用 docker 跑很服务，然后用 NFS 做 share，然后用 KVM 做虚拟化层，然后用商业的方案做异地组网。尽管过过程十分坎坷，遇到了硬盘噪音，纯开源项目支持不到位，商业方案售后不专业等问题，最后就只在内网使用，走了很多弯路吧。</p><p>相信懒猫的这个价格，如果用 AWS 的话，最多半年就烧光 credit 了。有如此专业的团队来支持，治好了我的 NAS 焦虑。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250504201658519.png" alt="image-20250504201658519"></p><p>文章来源：</p><p><img src="https://lzc-playground-1301583638.cos.ap-chengdu.myqcloud.com/guidelines/459/c24bffd1-eb1f-40fa-9dc6-bc5dc9337601.png" alt="image.png" title="image.png"></p>]]></content>
    
    
    <summary type="html">懒猫微服自带内网穿透功能，彻底解决 NAS 远程访问焦虑</summary>
    
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="番外" scheme="https://blog.no-claw.com/categories/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/%E7%95%AA%E5%A4%96/"/>
    
    
    <category term="懒猫微服" scheme="https://blog.no-claw.com/tags/%E6%87%92%E7%8C%AB%E5%BE%AE%E6%9C%8D/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>使用 Nginx 获取并返回客户端的公网 IP 地址</title>
    <link href="https://blog.no-claw.com/posts/92ab0084/"/>
    <id>https://blog.no-claw.com/posts/92ab0084/</id>
    <published>2025-04-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在某些场景下，我们需要快速获取客户端的公网 IP 地址。虽然有许多在线服务（如 IP Address Lookup、IPv4&#x2F;IPv6 检测工具或<a href="https://checkip.amazonaws.com/%EF%BC%89%E5%8F%AF%E4%BB%A5%E6%8F%90%E4%BE%9B%E6%AD%A4%E5%8A%9F%E8%83%BD%EF%BC%8C%E4%BD%86%E9%80%9A%E8%BF%87%E8%87%AA%E5%BB%BANginx%E6%9C%8D%E5%8A%A1%E6%9D%A5%E5%AE%9E%E7%8E%B0%E8%BF%99%E4%B8%80%E9%9C%80%E6%B1%82%EF%BC%8C%E4%B8%8D%E4%BB%85%E7%81%B5%E6%B4%BB%E5%8F%AF%E6%8E%A7%EF%BC%8C%E8%BF%98%E8%83%BD%E6%9B%B4%E5%A5%BD%E5%9C%B0%E6%BB%A1%E8%B6%B3%E4%B8%AA%E6%80%A7%E5%8C%96%E9%9C%80%E6%B1%82%E3%80%82">https://checkip.amazonaws.com/）可以提供此功能，但通过自建Nginx服务来实现这一需求，不仅灵活可控，还能更好地满足个性化需求。</a></p><p>下面是一个简单的 Nginx 配置示例，用于返回客户端的公网 IP 地址。</p><span id="more"></span><h2 id="配置-Nginx-返回客户端-IP-地址"><a href="#配置-Nginx-返回客户端-IP-地址" class="headerlink" title="配置 Nginx 返回客户端 IP 地址"></a>配置 Nginx 返回客户端 IP 地址</h2><p>如果你希望 Nginx 直接返回客户端的 IP 地址，可以通过在<code>location</code>块中使用<code>$remote_addr</code>变量来实现。以下是一个完整的配置示例：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> /get_ip &#123;</span><br><span class="line">        <span class="attribute">default_type</span> <span class="string">&#x27;application/json&#x27;</span>;</span><br><span class="line">        <span class="attribute">return</span> <span class="number">200</span> <span class="string">&#x27;&#123;&quot;ip_addr&quot;: &quot;<span class="variable">$remote_addr</span>&quot;&#125;&#x27;</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><ol><li><p><strong><code>listen 80;</code></strong><br>监听 80 端口，处理 HTTP 请求。</p></li><li><p><strong><code>location /get_ip { ... }</code></strong><br>定义一个路径为<code>/get_ip</code>的请求处理块。当客户端访问<code>/get_ip</code>时，Nginx 会执行该块中的指令。</p></li><li><p><strong><code>default_type &#39;application/json&#39;;</code></strong><br>设置响应的默认 MIME 类型为<code>application/json</code>，确保客户端能够正确解析返回的 JSON 数据。</p></li><li><p><strong><code>return 200 &#39;{&quot;ip_addr&quot;: &quot;$remote_addr&quot;}&#39;;</code></strong><br>返回一个 HTTP 状态码为 200 的响应，内容为一个 JSON 对象，其中<code>ip_addr</code>字段的值为客户端的 IP 地址（通过<code>$remote_addr</code>变量获取）。</p></li></ol><h3 id="示例响应"><a href="#示例响应" class="headerlink" title="示例响应"></a>示例响应</h3><p>当客户端访问<code>/get_ip</code>路径时，Nginx 会返回如下格式的 JSON 响应：</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;ip_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;客户端IP地址&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>例如，如果客户端的 IP 地址是<code>203.0.113.1</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;ip_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;203.0.113.1&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过以上配置，你可以快速搭建一个简单的 Nginx 服务，用于返回客户端的公网 IP 地址。这种方式不仅高效，还能根据需求进一步扩展功能，例如记录 IP 地址、限制访问频率等。如果你需要更复杂的功能，可以结合 Nginx 的其他模块和变量来实现。</p>]]></content>
    
    
    <summary type="html">自建 Nginx 服务返回客户端公网 IP，简单配置实现个性化 IP 查询接口。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="网关" scheme="https://blog.no-claw.com/tags/%E7%BD%91%E5%85%B3/"/>
    
    <category term="Nginx" scheme="https://blog.no-claw.com/tags/Nginx/"/>
    
  </entry>
  
  <entry>
    <title>从认证到透传：用 Nginx 为 Easysearch 构建一体化认证网关</title>
    <link href="https://blog.no-claw.com/posts/c50511c4/"/>
    <id>https://blog.no-claw.com/posts/c50511c4/</id>
    <published>2025-04-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在构建本地或云端搜索引擎系统时，EasySearch 凭借其轻量、高性能、易部署等优势，逐渐成为众多开发者和技术爱好者的首选。但在实际部署过程中，如何借助 Nginx 为 EasySearch 提供高效、稳定且安全的访问入口，尤其是在身份认证方面，仍然是一个关键技术环节。</p><p>本教程将围绕 Basic Auth 认证机制展开，系统讲解如何通过 Nginx 实现安全防护、认证信息透传等常见配置场景，帮助你在多种实际部署环境中快速搭建可靠的访问控制机制。</p><span id="more"></span><p>无论你是在搭建家庭 NAS 服务，还是在企业环境中集成搜索引擎系统，本教程都能为你提供一套可落地、可复用的 Nginx 安全认证解决方案。</p><p>下面是我的 Nginx 配置文件示例。我们通过 Docker 启动 Nginx 容器，并将本地编写好的配置文件挂载到容器中，从而实现自定义的反向代理和认证逻辑：</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">docker run -d \</span><br><span class="line">  --name my-nginx \</span><br><span class="line">  -p 80:80 \</span><br><span class="line">  -v $(<span class="built_in">pwd</span>)/default.conf:/etc/nginx/conf.d/default.conf \</span><br><span class="line">  nginx</span><br></pre></td></tr></table></figure><p><strong>default.conf</strong>配置如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name localhost;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 根路径可选配置，如果你要一个欢迎页</span></span><br><span class="line">    location / &#123;</span><br><span class="line">        <span class="built_in">return</span> 200 <span class="string">&#x27;Nginx is running.\n&#x27;</span>;</span><br><span class="line">        add_header Content-Type text/plain;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 反向代理 EasySearch</span></span><br><span class="line">    location /es/ &#123;</span><br><span class="line">        proxy_pass https://backend:9200/;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 修正请求头</span></span><br><span class="line">        proxy_http_version 1.1;</span><br><span class="line">        <span class="comment"># proxy_pass_request_headers on; ÷</span></span><br><span class="line">        proxy_set_header Host <span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 如果需要保活连接</span></span><br><span class="line">        proxy_set_header Connection <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 可选：允许跨域访问（用于前端 AJAX 调试）</span></span><br><span class="line">        add_header Access-Control-Allow-Origin *;</span><br><span class="line">        add_header Access-Control-Allow-Methods <span class="string">&#x27;GET, POST, OPTIONS&#x27;</span>;</span><br><span class="line">        add_header Access-Control-Allow-Headers <span class="string">&#x27;Authorization,Content-Type&#x27;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># proxy_set_header Authorization &quot;Basic YWRtaW46MTIzNDU2&quot;;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 清理路径前缀 `/es/`</span></span><br><span class="line">        rewrite ^/es/(.*)$ /<span class="variable">$1</span> <span class="built_in">break</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"># location /static/ &#123;</span></span><br><span class="line">    <span class="comment">#     root /usr/share/nginx/html;</span></span><br><span class="line">    <span class="comment"># &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="🌐-配置整体结构"><a href="#🌐-配置整体结构" class="headerlink" title="🌐 配置整体结构"></a>🌐 配置整体结构</h3><figure class="highlight nginx"><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="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span> localhost;</span><br></pre></td></tr></table></figure><ul><li><strong>监听端口</strong>：监听本地 <code>80</code> 端口（HTTP 默认端口）</li><li><strong>服务名称</strong>：用于匹配请求的 <code>Host</code>，这里是 <code>localhost</code></li></ul><hr><h3 id="🎉-欢迎页（根路径-）"><a href="#🎉-欢迎页（根路径-）" class="headerlink" title="🎉 欢迎页（根路径 /）"></a>🎉 欢迎页（根路径 <code>/</code>）</h3><figure class="highlight nginx"><table><tr><td class="gutter"><pre><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">location</span> / &#123;</span><br><span class="line">    <span class="attribute">return</span> <span class="number">200</span> <span class="string">&#x27;Nginx is running.\n&#x27;</span>;</span><br><span class="line">    <span class="attribute">add_header</span> Content-Type text/plain;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>请求 <code>/</code> 会返回纯文本响应 <code>&quot;Nginx is running.&quot;</code>，可用于验证 Nginx 是否启动正常。</li><li><code>add_header Content-Type text/plain</code>：指定响应内容为纯文本。</li></ul><hr><h3 id="🔁-es-代理-EasySearch-后端服务"><a href="#🔁-es-代理-EasySearch-后端服务" class="headerlink" title="🔁 /es/ 代理 EasySearch 后端服务"></a>🔁 <code>/es/</code> 代理 EasySearch 后端服务</h3><hr><h4 id="🚚-请求头处理"><a href="#🚚-请求头处理" class="headerlink" title="🚚 请求头处理"></a>🚚 请求头处理</h4><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="attribute">proxy_http_version</span> <span class="number">1</span>.<span class="number">1</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span> X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;&quot;</span>;</span><br></pre></td></tr></table></figure><ul><li><code>proxy_http_version 1.1</code>：确保代理使用 HTTP&#x2F;1.1，支持长连接。</li><li><code>Host</code>：保留原始请求的主机名。</li><li><code>X-Real-IP</code> &#x2F; <code>X-Forwarded-For</code>：传递客户端真实 IP。</li><li><code>X-Forwarded-Proto</code>：传递原始协议（http &#x2F; https）。</li><li><code>Connection &quot;&quot;</code>：用于避免默认的 <code>keep-alive</code> 设置引起错误（推荐保留）。</li></ul><hr><h4 id="🔐-可选的认证头（注释中）"><a href="#🔐-可选的认证头（注释中）" class="headerlink" title="🔐 可选的认证头（注释中）"></a>🔐 可选的认证头（注释中）</h4><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># proxy_set_header Authorization &quot;Basic YWRtaW46MTIzNDU2&quot;;</span></span><br></pre></td></tr></table></figure><ul><li>可选开启，用于将认证信息转发到后端。</li><li>上面的字符串是 <code>admin:123456</code> 的 base64 编码。可以根据需要开启。</li></ul><hr><h4 id="🌍-CORS-设置（跨域）"><a href="#🌍-CORS-设置（跨域）" class="headerlink" title="🌍 CORS 设置（跨域）"></a>🌍 CORS 设置（跨域）</h4><figure class="highlight nginx"><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="attribute">add_header</span> Access-Control-Allow-Origin *;</span><br><span class="line"><span class="attribute">add_header</span> Access-Control-Allow-Methods <span class="string">&#x27;GET, POST, OPTIONS&#x27;</span>;</span><br><span class="line"><span class="attribute">add_header</span> Access-Control-Allow-Headers <span class="string">&#x27;Authorization,Content-Type&#x27;</span>;</span><br></pre></td></tr></table></figure><ul><li>允许任意源访问（前端页面可以跨域请求 <code>/es/</code>）。</li><li>支持的方法：GET、POST、OPTIONS。</li><li>允许传递的请求头：Authorization 和 Content-Type。</li><li>✅ 适用于 AJAX 调试、前后端分离等场景。</li></ul><hr><h4 id="🔧-URL-重写"><a href="#🔧-URL-重写" class="headerlink" title="🔧 URL 重写"></a>🔧 URL 重写</h4><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">rewrite</span><span class="regexp"> ^/es/(.*)$</span> /<span class="variable">$1</span> <span class="literal">break</span>;</span><br></pre></td></tr></table></figure><ul><li>移除 <code>/es/</code> 前缀，转发给后端。例如：<br>用户请求 <code>/es/_cat/indices</code> 实际转发到 <code>/cat/indices</code>。</li><li><code>break</code> 表示在当前 location 中中止后续重写检查。</li></ul><hr><h4 id="📦-可选静态资源（被注释掉）"><a href="#📦-可选静态资源（被注释掉）" class="headerlink" title="📦 可选静态资源（被注释掉）"></a>📦 可选静态资源（被注释掉）</h4><figure class="highlight nginx"><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"># location /static/ &#123;</span></span><br><span class="line"><span class="comment">#     root /usr/share/nginx/html;</span></span><br><span class="line"><span class="comment"># &#125;</span></span><br></pre></td></tr></table></figure><ul><li>若开启，可以直接通过 <code>/static/xxx.js</code> 访问 Nginx 本地 <code>/usr/share/nginx/html/static/xxx.js</code> 文件。</li></ul><h4 id="🔁-如果你想保留-es-前缀，则删除-rewrite-行。"><a href="#🔁-如果你想保留-es-前缀，则删除-rewrite-行。" class="headerlink" title="🔁 如果你想保留 /es/ 前缀，则删除 rewrite 行。"></a>🔁 如果你想保留 <code>/es/</code> 前缀，则删除 <code>rewrite</code> 行。</h4><hr><p>在启动服务后，当我们通过浏览器访问 Nginx 时，页面会弹出身份验证窗口。需要注意的是，这里的认证提示实际上来自后端的 EasySearch 服务，而非 Nginx 本身，说明请求中的认证信息未在 Nginx 层被处理或透传，因此由 EasySearch 发起了再次认证的要求。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250424165739653.png" alt="image-20250424165739653"></p><p>在输入正确的用户名和密码后，我们可以看到 Nginx 成功代理请求，并返回了来自 EasySearch 的 API 响应，说明认证流程已顺利通过，后端服务正常可用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250424165019942.png" alt="image-20250424165019942"></p><p>如果希望由 Nginx 代为完成 EasySearch 的身份验证，也就是实现自动登录的效果，可以在配置文件中添加如下指令，将认证信息通过 HTTP 头部传递给后端：</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">proxy_set_header Authorization &quot;Basic YWRtaW46YWRtaW4=&quot;;</span><br></pre></td></tr></table></figure><p>其中，<code>YWRtaW46YWRtaW4xMjM=</code> 是使用 Base64 编码后的 <code>用户名:密码</code> 字符串（例如 <code>admin:admin</code>）。Nginx 在转发请求时会自动携带该 Header，从而实现对 EasySearch 的自动认证。</p><p>需要注意的是，Nginx 的配置文件修改后不会自动生效。为了确保配置被正确加载，需在每次更改配置文件后重启对应的容器服务。这是容器化部署中常见的操作流程，确保新配置被正确应用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250424165420050.png" alt="image-20250424165420050"></p><p>为了更直观地观察请求行为，我们使用了 Postman 进行测试。可以发现，即使在 Postman 中未显式添加任何认证信息，依然能够成功访问 EasySearch 集群。这说明前端未输入认证信息，但由于 Nginx 曾经缓存了认证状态，或配置了自动透传，导致后端依旧接收到了有效的认证头，从而允许了访问。</p><p>这种现象虽然在测试中提升了访问便利性，但在实际部署中可能带来安全风险，因此在生产环境中建议对认证流程进行严格控制，确保后端服务不会因为前端认证机制的疏漏而被绕过。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250424165032915.png" alt="image-20250424165032915"></p><p>在一些教程中，常会提到一个名为 <strong><code>.htpasswd</code></strong> 的文件，它用于在 Nginx 层实现基本认证。当启用该机制后，Nginx 会对所有访问进行用户身份验证。</p><p>此时，是否将认证信息透传给后端服务，则由 <code>proxy_pass_request_headers</code> 参数决定。该参数默认值为 <code>on</code>，也就是说，当认证通过后，客户端发送的 Authorization 头部信息会被 Nginx 一并转发给后端服务。</p><p>为了验证这一行为，我编写了一个简单的 Flask 程序作为后端，用于观察请求中的 Header 内容。在真正将请求代理至 EasySearch 之前，我先让 Nginx 将请求反向代理到这个 Flask 应用，从而可以直观地查看是否存在 Authorization 头被透传的情况。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request,abort</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello_world</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;📥 Headers received from Nginx:&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Host:&quot;</span>, request.headers.get(<span class="string">&#x27;Host&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;X-Real-IP:&quot;</span>, request.headers.get(<span class="string">&#x27;X-Real-IP&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;X-Forwarded-For:&quot;</span>, request.headers.get(<span class="string">&#x27;X-Forwarded-For&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;X-Forwarded-Proto:&quot;</span>, request.headers.get(<span class="string">&#x27;X-Forwarded-Proto&#x27;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(request.headers)</span><br><span class="line">    auth_header = request.headers.get(<span class="string">&#x27;Authorization&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Authorization:&quot;</span>, auth_header)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> auth_header <span class="keyword">or</span> <span class="keyword">not</span> auth_header.startswith(<span class="string">&#x27;Basic &#x27;</span>):</span><br><span class="line">        abort(<span class="number">401</span>, description=<span class="string">&quot;Missing or invalid Authorization header&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 解码 base64</span></span><br><span class="line">    encoded = auth_header.split(<span class="string">&#x27; &#x27;</span>)[<span class="number">1</span>]</span><br><span class="line">    decoded = base64.b64decode(encoded).decode(<span class="string">&#x27;utf-8&#x27;</span>)  <span class="comment"># e.g. admin:123456</span></span><br><span class="line"></span><br><span class="line">    username, password = decoded.split(<span class="string">&#x27;:&#x27;</span>, <span class="number">1</span>)</span><br><span class="line">    <span class="built_in">print</span>(username, password)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;Hello World!&#x27;</span></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">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8000</span>,debug=<span class="literal">True</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这个是 flask 的打印的结果.</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></pre></td><td class="code"><pre><span class="line">Host: secure-nginx.orb.local</span><br><span class="line">X-Real-IP: 192.168.215.1</span><br><span class="line">X-Forwarded-For: 192.168.215.1</span><br><span class="line">X-Forwarded-Proto: http</span><br><span class="line">Host: secure-nginx.orb.local</span><br><span class="line">X-Real-Ip: 192.168.215.1</span><br><span class="line">X-Forwarded-For: 192.168.215.1</span><br><span class="line">X-Forwarded-Proto: http</span><br><span class="line">Authorization: Basic YWRtaW46YWRtaW4=</span><br><span class="line">Upgrade-Insecure-Requests: 1</span><br><span class="line">User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36</span><br><span class="line">Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7</span><br><span class="line">Accept-Encoding: gzip, deflate</span><br><span class="line">Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8</span><br><span class="line">Cookie: perf_dv6Tr4n=1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Authorization: Basic YWRtaW46YWRtaW4=</span><br><span class="line">admin admin</span><br><span class="line">192.168.X.X - - [24/Apr/2025 15:55:59] <span class="string">&quot;GET / HTTP/1.1&quot;</span> 200 -</span><br></pre></td></tr></table></figure><p>为了解决双重认证的问题，我们启用了认证信息透传的配置(默认的 roxy_pass_request_headers on;)。启用该配置后，用户只需在访问 Nginx 时进行一次手动身份验证。Nginx 会将用户提供的凭证通过 HTTP Header 透传至后端的 EasySearch 服务，从而避免二次认证。当用户直接访问 EasySearch 时，依然需要单独输入凭证进行认证；但通过 Nginx 访问时，只需在前端认证一次即可完成整个请求流程。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">curl -k https://easysearch:9200</span><br><span class="line"></span><br><span class="line">&#123;<span class="string">&quot;error&quot;</span>:&#123;<span class="string">&quot;root_cause&quot;</span>:[&#123;<span class="string">&quot;type&quot;</span>:<span class="string">&quot;security_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Missing authentication information for REST request [/]&quot;</span>,<span class="string">&quot;header&quot;</span>:&#123;<span class="string">&quot;WWW-Authenticate&quot;</span>:<span class="string">&quot;Basic realm=\&quot;Security\&quot; charset=\&quot;UTF-8\&quot;&quot;</span>&#125;&#125;],<span class="string">&quot;type&quot;</span>:<span class="string">&quot;security_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Missing authentication information for REST request [/]&quot;</span>,<span class="string">&quot;header&quot;</span>:&#123;<span class="string">&quot;WWW-Authenticate&quot;</span>:<span class="string">&quot;Basic realm=\&quot;Security\&quot; charset=\&quot;UTF-8\&quot;&quot;</span>&#125;&#125;,<span class="string">&quot;status&quot;</span>:401&#125;⏎</span><br><span class="line">-------</span><br><span class="line"></span><br><span class="line"> curl -v -u <span class="string">&quot;admin:admin&quot;</span> http://nginxhost/es/</span><br><span class="line"></span><br><span class="line">*   Trying 192.168.5.171:9201...</span><br><span class="line">* Connected to 192.168.5.171 (192.168.5.171) port 9201</span><br><span class="line">* ALPN: curl offers h2,http/1.1</span><br><span class="line">* (304) (OUT), TLS handshake, Client hello (1):</span><br><span class="line">*  CAfile: /etc/ssl/cert.pem</span><br><span class="line">*  CApath: none</span><br><span class="line">* (304) (IN), TLS handshake, Server hello (2):</span><br><span class="line">* (304) (IN), TLS handshake, Unknown (8):</span><br><span class="line">* (304) (IN), TLS handshake, Request CERT (13):</span><br><span class="line">* (304) (IN), TLS handshake, Certificate (11):</span><br><span class="line">* SSL certificate problem: self signed certificate</span><br><span class="line">* Closing connection</span><br><span class="line">curl: (60) SSL certificate problem: self signed certificate</span><br><span class="line">More details here: https://curl.se/docs/sslcerts.html</span><br><span class="line"></span><br><span class="line">curl failed to verify the legitimacy of the server and therefore could not</span><br><span class="line">establish a secure connection to it. To learn more about this situation and</span><br><span class="line">how to fix it, please visit the web page mentioned above.</span><br><span class="line">* Host localhost:80 was resolved.</span><br><span class="line">* IPv6: ::1</span><br><span class="line">* IPv4: 127.0.0.1</span><br><span class="line">*   Trying [::1]:80...</span><br><span class="line">* Connected to localhost (::1) port 80</span><br><span class="line">* Server auth using Basic with user <span class="string">&#x27;admin&#x27;</span></span><br><span class="line">&gt; GET /es/ HTTP/1.1</span><br><span class="line">&gt; Host: localhost</span><br><span class="line">&gt; Authorization: Basic YWRtaW46YWRtaW4=</span><br><span class="line">&gt; User-Agent: curl/8.7.1</span><br><span class="line">&gt; Accept: */*</span><br><span class="line">&gt;</span><br><span class="line">* Request completely sent off</span><br><span class="line">&lt; HTTP/1.1 200 OK</span><br><span class="line">&lt; Server: nginx/1.27.4</span><br><span class="line">&lt; Date: Thu, 24 Apr 2025 07:45:10 GMT</span><br><span class="line">&lt; Content-Type: application/json; charset=UTF-8</span><br><span class="line">&lt; Content-Length: 552</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Access-Control-Allow-Origin: *</span><br><span class="line">&lt; Access-Control-Allow-Methods: GET, POST, OPTIONS</span><br><span class="line">&lt; Access-Control-Allow-Headers: Authorization,Content-Type</span><br><span class="line">&lt;</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;name&quot;</span> : <span class="string">&quot;easysearch-node1&quot;</span>,</span><br><span class="line">  <span class="string">&quot;cluster_name&quot;</span> : <span class="string">&quot;infinilabs&quot;</span>,</span><br><span class="line">  <span class="string">&quot;cluster_uuid&quot;</span> : <span class="string">&quot;VcMD__DwSYSUqear8wp-XA&quot;</span>,</span><br><span class="line">  <span class="string">&quot;version&quot;</span> : &#123;</span><br><span class="line">    <span class="string">&quot;distribution&quot;</span> : <span class="string">&quot;easysearch&quot;</span>,</span><br><span class="line">    <span class="string">&quot;number&quot;</span> : <span class="string">&quot;1.11.1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;distributor&quot;</span> : <span class="string">&quot;INFINI Labs&quot;</span>,</span><br><span class="line">    <span class="string">&quot;build_hash&quot;</span> : <span class="string">&quot;4d0be0343919fb1a605e3c8284326b7e069eb9bf&quot;</span>,</span><br><span class="line">    <span class="string">&quot;build_date&quot;</span> : <span class="string">&quot;2025-03-14T09:33:12.182925Z&quot;</span>,</span><br><span class="line">    <span class="string">&quot;build_snapshot&quot;</span> : <span class="literal">false</span>,</span><br><span class="line">    <span class="string">&quot;lucene_version&quot;</span> : <span class="string">&quot;8.11.4&quot;</span>,</span><br><span class="line">    <span class="string">&quot;minimum_wire_lucene_version&quot;</span> : <span class="string">&quot;7.7.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;minimum_lucene_index_compatibility_version&quot;</span> : <span class="string">&quot;7.7.0&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;tagline&quot;</span> : <span class="string">&quot;You Know, For Easy Search!&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">* Connection <span class="comment">#0 to host localhost left intact</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>本次将 Nginx 的访问认证密码修改为 <code>admin123</code> 后，发现在请求过程中出现了两次身份验证的提示。具体表现为：当用户输入错误的密码时，Nginx 会首先返回一次 401 Unauthorized。由于 Nginx 与 EasySearch 使用了不同的认证信息，Nginx 在将请求头（包括 Authorization 字段）转发至 EasySearch 时，EasySearch 检测到凭据不匹配，也会返回一次 401。由此导致了双重身份认证失败的现象，影响了正常访问流程。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><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></pre></td><td class="code"><pre><span class="line">curl -v -u <span class="string">&quot;admin:admin&quot;</span> http://localhost/es/</span><br><span class="line"></span><br><span class="line">*   Trying 192.168.5.171:9201...</span><br><span class="line">* Connected to 192.168.5.171 (192.168.5.171) port 9201</span><br><span class="line">* ALPN: curl offers h2,http/1.1</span><br><span class="line">* (304) (OUT), TLS handshake, Client hello (1):</span><br><span class="line">*  CAfile: /etc/ssl/cert.pem</span><br><span class="line">*  CApath: none</span><br><span class="line">* (304) (IN), TLS handshake, Server hello (2):</span><br><span class="line">* (304) (IN), TLS handshake, Unknown (8):</span><br><span class="line">* (304) (IN), TLS handshake, Request CERT (13):</span><br><span class="line">* (304) (IN), TLS handshake, Certificate (11):</span><br><span class="line">* SSL certificate problem: self signed certificate</span><br><span class="line">* Closing connection</span><br><span class="line">curl: (60) SSL certificate problem: self signed certificate</span><br><span class="line">More details here: https://curl.se/docs/sslcerts.html</span><br><span class="line"></span><br><span class="line">curl failed to verify the legitimacy of the server and therefore could not</span><br><span class="line">establish a secure connection to it. To learn more about this situation and</span><br><span class="line">how to fix it, please visit the web page mentioned above.</span><br><span class="line">* Host localhost:80 was resolved.</span><br><span class="line">* IPv6: ::1</span><br><span class="line">* IPv4: 127.0.0.1</span><br><span class="line">*   Trying [::1]:80...</span><br><span class="line">* Connected to localhost (::1) port 80</span><br><span class="line">* Server auth using Basic with user <span class="string">&#x27;admin&#x27;</span></span><br><span class="line">&gt; GET /es/ HTTP/1.1</span><br><span class="line">&gt; Host: localhost</span><br><span class="line">&gt; Authorization: Basic YWRtaW46YWRtaW4=</span><br><span class="line">&gt; User-Agent: curl/8.7.1</span><br><span class="line">&gt; Accept: */*</span><br><span class="line">&gt;</span><br><span class="line">* Request completely sent off</span><br><span class="line">&lt; HTTP/1.1 401 Unauthorized</span><br><span class="line">&lt; Server: nginx/1.27.4</span><br><span class="line">&lt; Date: Thu, 24 Apr 2025 09:21:09 GMT</span><br><span class="line">&lt; Content-Type: text/html</span><br><span class="line">&lt; Content-Length: 179</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">* Authentication problem. Ignoring this.</span><br><span class="line">&lt; WWW-Authenticate: Basic realm=<span class="string">&quot;Restricted Area&quot;</span></span><br><span class="line">&lt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;<span class="built_in">head</span>&gt;&lt;title&gt;401 Authorization Required&lt;/title&gt;&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;center&gt;&lt;h1&gt;401 Authorization Required&lt;/h1&gt;&lt;/center&gt;</span><br><span class="line">&lt;hr&gt;&lt;center&gt;nginx/1.27.4&lt;/center&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br><span class="line">* Connection <span class="comment">#0 to host localhost left intact</span></span><br><span class="line">❰xu❙~/OrbStack/docker/containers/secure-nginx/etc/nginx❱✔≻ curl -v https://192.168.5.171:9201/         (base) 17:21:09</span><br><span class="line">                                                           curl -v -u <span class="string">&quot;admin:admin123&quot;</span> http://localhost/es/</span><br><span class="line"></span><br><span class="line">*   Trying 192.168.5.171:9201...</span><br><span class="line">* Connected to 192.168.5.171 (192.168.5.171) port 9201</span><br><span class="line">* ALPN: curl offers h2,http/1.1</span><br><span class="line">* (304) (OUT), TLS handshake, Client hello (1):</span><br><span class="line">*  CAfile: /etc/ssl/cert.pem</span><br><span class="line">*  CApath: none</span><br><span class="line">* (304) (IN), TLS handshake, Server hello (2):</span><br><span class="line">* (304) (IN), TLS handshake, Unknown (8):</span><br><span class="line">* (304) (IN), TLS handshake, Request CERT (13):</span><br><span class="line">* (304) (IN), TLS handshake, Certificate (11):</span><br><span class="line">* SSL certificate problem: self signed certificate</span><br><span class="line">* Closing connection</span><br><span class="line">curl: (60) SSL certificate problem: self signed certificate</span><br><span class="line">More details here: https://curl.se/docs/sslcerts.html</span><br><span class="line"></span><br><span class="line">curl failed to verify the legitimacy of the server and therefore could not</span><br><span class="line">establish a secure connection to it. To learn more about this situation and</span><br><span class="line">how to fix it, please visit the web page mentioned above.</span><br><span class="line">* Host localhost:80 was resolved.</span><br><span class="line">* IPv6: ::1</span><br><span class="line">* IPv4: 127.0.0.1</span><br><span class="line">*   Trying [::1]:80...</span><br><span class="line">* Connected to localhost (::1) port 80</span><br><span class="line">* Server auth using Basic with user <span class="string">&#x27;admin&#x27;</span></span><br><span class="line">&gt; GET /es/ HTTP/1.1</span><br><span class="line">&gt; Host: localhost</span><br><span class="line">&gt; Authorization: Basic YWRtaW46YWRtaW4xMjM=</span><br><span class="line">&gt; User-Agent: curl/8.7.1</span><br><span class="line">&gt; Accept: */*</span><br><span class="line">&gt;</span><br><span class="line">* Request completely sent off</span><br><span class="line">&lt; HTTP/1.1 401 Unauthorized</span><br><span class="line">&lt; Server: nginx/1.27.4</span><br><span class="line">&lt; Date: Thu, 24 Apr 2025 09:21:16 GMT</span><br><span class="line">&lt; Content-Type: application/json; charset=UTF-8</span><br><span class="line">&lt; Content-Length: 381</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">* Authentication problem. Ignoring this.</span><br><span class="line">&lt; WWW-Authenticate: Basic realm=<span class="string">&quot;Security&quot;</span> charset=<span class="string">&quot;UTF-8&quot;</span></span><br><span class="line">&lt;</span><br><span class="line">* Connection <span class="comment">#0 to host localhost left intact</span></span><br><span class="line">&#123;<span class="string">&quot;error&quot;</span>:&#123;<span class="string">&quot;root_cause&quot;</span>:[&#123;<span class="string">&quot;type&quot;</span>:<span class="string">&quot;security_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Missing authentication information for REST request [/]&quot;</span>,<span class="string">&quot;header&quot;</span>:&#123;<span class="string">&quot;WWW-Authenticate&quot;</span>:<span class="string">&quot;Basic realm=\&quot;Security\&quot; charset=\&quot;UTF-8\&quot;&quot;</span>&#125;&#125;],<span class="string">&quot;type&quot;</span>:<span class="string">&quot;security_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Missing authentication information for REST request [/]&quot;</span>,<span class="string">&quot;header&quot;</span>:&#123;<span class="string">&quot;WWW-Authenticate&quot;</span>:<span class="string">&quot;Basic realm=\&quot;Security\&quot; charset=\&quot;UTF-8\&quot;&quot;</span>&#125;&#125;,<span class="string">&quot;status&quot;</span>:401&#125;⏎</span><br></pre></td></tr></table></figure><table><thead><tr><th>场景编号</th><th>Nginx 是否开启认证</th><th>EasySearch 是否开启认证</th><th>实际认证次数</th><th>说明</th></tr></thead><tbody><tr><td>①</td><td>否</td><td>否</td><td>0 次</td><td>完全开放，任何请求无需验证。</td></tr><tr><td>②</td><td>否</td><td>✅ 是</td><td>1 次</td><td>访问时直接弹出 EasySearch 的认证窗口，用户需输入凭证。</td></tr><tr><td>③</td><td>✅ 是</td><td>否</td><td>1 次</td><td>仅在 Nginx 层验证，验证通过后直接访问后端。</td></tr><tr><td>④</td><td>✅ 是</td><td>✅ 是</td><td>2 次（默认）</td><td>Nginx 和 EasySearch 各自认证，用户需连续输入两次密码。</td></tr><tr><td>⑤</td><td>✅ 是</td><td>✅ 是</td><td>1 次（透传,proxy_pass_request_headers on;）</td><td>Nginx 开启认证，并通过 <code>proxy_set_header Authorization</code> 透传给 EasySearch，用户仅需输入一次密码即可完成认证。</td></tr></tbody></table>]]></content>
    
    
    <summary type="html">使用 Nginx 为 Easysearch 搭建统一认证网关的实战教程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>威联通 QNAP 系列 (一) 全闪 NAS TBS-h574TX QuTS hero 初探</title>
    <link href="https://blog.no-claw.com/posts/7acb32ac/"/>
    <id>https://blog.no-claw.com/posts/7acb32ac/</id>
    <published>2025-04-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>字数 1371，阅读大约需 7 分钟</p></blockquote><p>都说 2024 是全闪 NAS 的元年，各个厂商也纷纷出品的自家的 NAS，独占鳌头的还是 QNAP 的<strong>TBS-h574TX</strong>，5 盘位 NVME，支持 10G 网口以及雷电网桥，甚至还有 12 代 i5 CPU 这个配置很难不让人心动。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwnO7Aic8zVW6tMPatN4O25CQOILhhRKIGkCuYDkadRicWHad2Sia2gnibJA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><h3 id="初始化系统安装"><a href="#初始化系统安装" class="headerlink" title="初始化系统安装"></a>初始化系统安装</h3><p>使用 Qfinder Pro 可以查找局域网内的 QNAP NAS, 免去手动查找 IP 的麻烦,软件支持全平台。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwzYxLDHhXkqJy7a5Pq0ficr2icc869F1dvMtBz7M4If2GCjCPjZFI5LFg/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>Qfinder 寻找结果如下，可以识别出 NAS 名称，IP 地址，MAC 地址，机器型号以及系统及其版本。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIw9eEsksIytcKk701AFIibC92Zp6Cf0piaiajGhvvCia0s5Nk83iakFgauicvg/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>除此之外，我们也可以在路由器后台寻找 IP 地址。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwoO2VX1BqZwfqnvntrichSp6lZNNx0MWVuJ4lXWP1cxyL8daNH3AMSCw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>开始初始化流程：</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwRqTphdlkdTlP4k3kCA8AKLxJpnRKrdAaaUiajA4Ljcmbxk84e8R4ibAw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>进入 web 页面，开始安装系统</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwvscFUjaE7WTKsDOOlgRibr78ia6uP2wMTCaiaRr0wUUOocJf3sSO4vakw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>授权条款如下：</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwKrTojDUbabH9XjlhoR2Faqg9a7W2GJVkOBS0GiaCbCYYSsn1L7Ric9icA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>选择固件版本，为了快速安装，一般选择当前版本，然后进入 OS 内部再进行升级</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIw9UHPgKpVicy7WxTd9WA2bE5YOPMTXGDjb0nsjJDib2IbyHiclscL1WFibw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>设置 nas 名称和用户名,密码,由于系统内置了 admin 用户，所以这里不能使用 admin</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwXyA6H4W1KiabYdfaCbW02ImbKjUQGwJqavCoYWLWcZD8qKscb4hjhvA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>设置时区以及 NTP 服务器同步时间。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwvS161ndibydZvc7sgXiacaibRnlnXzyr1aXdgfcjYSocRRic3HUV3J8NPQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>设置 IP 地址，可以选择 DHCP 或者静态地址，我一般为了方便选 DHCP，这些后期都可以系统内部进行修改。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwlO8nSxsiaewP9qOuPaxOckUQewxSsdcLesnbRWYAaWC227pejUzbOwg/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>如果设置静态地址需要的参数如下：IP 地址，子网掩码，网关，DNS 服务器，不过还是建议直接在路由器上设置静态 IP 方便管理。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwIfrXJ8ibsLknuSXNP0RiaiaFjnyicMCB39m5eXkDBOnWzYhlBRpAZknq1w/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>这个机器支持了雷电 4 的接口，原生支持了雷电网桥，所以这里多出来一个检测雷电的步骤，拔插雷电的时候机器会滴滴的响几声。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwthkBL4TAUKPrPxgeiakAia7DiaV4yBHCo3WHoEbJhlmDZogVumn2rZUpw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>固件更新设置，建议设置通知，手动更新。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwQFrmDicficmyAg9Sy3W5iabTOf1r5BxRD5ZnATJqtR4uZzcNNHebHQSjg/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>这个初始化的步骤会清除硬盘上的所有数据。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIw1j9YxldqT57LVpibJPxKsiaFlNt263jhG3JM289KWjdcVSiaRldsr7xQQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>接下来就几分钟的等待，主要是等待往硬盘上安装软件，可以看到进度条。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwBCxtBxcoFW8BAHq9q50Idm2ph4LAVibjwAslS5zB8Y6m0beMseb4DCQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>这个机器的配置比较高，全程用了五分钟左右，这里主要启动 SMB 和系统的一些进程。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwXJrXOgWLDtATWZXwf5cRa8jcE8PaVLmMusxwy58s9wCDwjdhc0BjMw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>设置完毕之后，我们就可以使用 NAS 了。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwkZL0ZNMRkB7xXWovXSicS6T34Sqic7UfLM7uvC2SdpR0TSCPkBgX8I7Q/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>使用之前设置好的用户名和密码进行登录</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwbozeKBoibkdLxm4yWfOg4QIdlm2keMIzlHLZ3gISUvhk7eBmh0uSfCQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><h3 id="初始化磁盘"><a href="#初始化磁盘" class="headerlink" title="初始化磁盘"></a>初始化磁盘</h3><p>第一步需要设置存储池，用我们安装的 NVME 硬盘组成一个 raid 存储池。</p><p>RAID 通过将多个硬盘组合在一起，形成一个更大的存储单元，以实现数据冗余备份或提升存储性能。它是一种存储虚拟化技术，可以让系统同时从多个硬盘中读取和写入数据，从而提高读写速度。</p><p>RAID 的常见级别如下：</p><ul><li>• <strong>RAID 0（条带化）</strong>：数据被分割成多个部分，分别存储在多个硬盘上，读写速度快，但没有冗余保护，任意一块硬盘损坏会导致数据丢失。（建议谨慎使用）</li><li>• <strong>RAID 1（镜像）</strong>：数据在两个硬盘上存储两份，每次写操作都会写到两个硬盘上，数据安全性高，但磁盘利用率低。</li><li>• <strong>RAID 5（分布式奇偶校验）</strong>：利用条带化和奇偶校验实现数据保护，至少需要三块硬盘，能够在提高存储性能的同时保证数据的冗余性，可以容忍一块盘故障。</li><li>• <strong>RAID 6（双奇偶校验）</strong>：与 RAID 5 类似，但使用双重奇偶校验，可以容忍两块硬盘同时故障。</li><li>• <strong>RAID 10（镜像+条带化）</strong>：结合了 RAID 1 和 RAID 0 的优点，先进行镜像操作，再进行条带化，提供高性能和高可靠性。</li></ul><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwTVNSp8KNpkSN2BU2Qqmm1tTSL4wHWhyTuxm7L6QHhR0lxNW9R8aicaw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>我只有两个 NVME，所以出于测试目的，组建了 Raid0。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwJ5fF9IbvbpwIicibYHnZVDzj6BNTeMOHkGvIYtVu3QjibmmhHaa5gLsag/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>设置存储池预留空间，快照预留空间以及警报阈值。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwmFOTYKLZ5dt4LvVXaoIl3Yg0zFNUd5vqWEmJt5h1Bgxd9EESnHBI8g/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>可以看到我的 4T 和 2T 的 NVME 组建的 RAID0 阵列， 设置完毕，除了保留空间外，最后之后 3.6T 可以用。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwBQLSkzcsCYLuba17OHflx9uXoa9oIdvBdha7EwYlFlMSrphaPn93BQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>同样存储池也会清除磁盘上的所有数据。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/IbSwDcwia5ice0zutOCVThUsZxdPhqicibIwIZXoYGfNe8QGRyAH9J2NHJQDLicw8W72ialcGc8VljXkHoZusRkmz08w/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><p>存储池磁盘位一览：</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><p>存储池目录结构如下：</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><h3 id="简单的监控"><a href="#简单的监控" class="headerlink" title="简单的监控"></a>简单的监控</h3><p>在用户管理处我们可以看到刚刚设置的用户，在这里也可以新建用户做一些额外的权限控制。</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><p>同时 NAS 还自带了监控，可以看到 CPU，内存以及磁盘使用率，还有运行时间，风扇转速，访问记录等等。</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><h3 id="日志处理"><a href="#日志处理" class="headerlink" title="日志处理"></a>日志处理</h3><p>QuLog Center 是一款集中日志管理应用程序，可将详细的系统事件、系统访问和在线用户状态记录到您的设备。收集的信息可用于有效地诊断和理解设备系统问题，例如与用户访问相关的记录</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><p>QuLog 服务用于将日志传输到其他设备的 QuLog Center。您可以将其他设备的日志集中起来管理。</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><p>日志也可以发送到 Syslog 服务器：</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><h3 id="公网访问"><a href="#公网访问" class="headerlink" title="公网访问"></a>公网访问</h3><p>如果想在公网上访问这个 NAS，那么也可以在路由器上设置端口转发，使用自家的 IP 地址进行访问。</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>全闪主要是风扇的声音比较大，用手机贴在上面测试了下，在 50 分贝左右，拿开一段距离的话在 40 分贝左右。有条件还是放在柜子里吧。</p><p>![图片](data:image&#x2F;svg+xml,%3C%3Fxml version&#x3D;’1.0’ encoding&#x3D;’UTF-8’%3F%3E%3Csvg width&#x3D;’1px’ height&#x3D;’1px’ viewBox&#x3D;’0 0 1 1’ version&#x3D;’1.1’ xmlns&#x3D;’<a href="http://www.w3.org/2000/svg">http://www.w3.org/2000/svg</a>‘ xmlns:xlink&#x3D;’<a href="http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg">http://www.w3.org/1999/xlink&#39;%3E%3Ctitle%3E%3C/title%3E%3Cg</a> stroke&#x3D;’none’ stroke-width&#x3D;’1’ fill&#x3D;’none’ fill-rule&#x3D;’evenodd’ fill-opacity&#x3D;’0’%3E%3Cg transform&#x3D;’translate(-249.000000, -126.000000)’ fill&#x3D;’%23FFFFFF’%3E%3Crect x&#x3D;’249’ y&#x3D;’126’ width&#x3D;’1’ height&#x3D;’1’%3E%3C&#x2F;rect%3E%3C&#x2F;g%3E%3C&#x2F;g%3E%3C&#x2F;svg%3E)</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;字数 1371，阅读大约需 7 分钟&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;都说 2024 是全闪 NAS 的元年，各个厂商也纷纷出品的自家的 NAS，独占鳌头的还是 QNAP 的&lt;strong&gt;TBS-h574TX&lt;/strong&gt;，5 盘位</summary>
      
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    <category term="QNAP" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/QNAP/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 服务昨天还好好的，为什么今天突然访问不了了？</title>
    <link href="https://blog.no-claw.com/posts/8d9611a5/"/>
    <id>https://blog.no-claw.com/posts/8d9611a5/</id>
    <published>2025-04-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 Easyearch 搭建本地或云端搜索服务时，难免会遇到服务偶发性或持续性无法访问的问题。本文将从服务状态、端口监听、网络配置、安全组等五个维度，系统梳理排查思路，帮助大家快速定位并解决访问失败的原因。</p><hr><h2 id="🚢-一、Docker-部署场景下的排查方法"><a href="#🚢-一、Docker-部署场景下的排查方法" class="headerlink" title="🚢 一、Docker 部署场景下的排查方法"></a>🚢 一、Docker 部署场景下的排查方法</h2><p>如果你是通过官方的 Docker Compose 部署 EasySearch，一般不会出现太大问题。但如果你像我一样在群晖或 NAS 上做过自定义配置，以下通用排查方法可以帮助你快速定位问题：</p><span id="more"></span><h3 id="示例-Docker-Compose-配置"><a href="#示例-Docker-Compose-配置" class="headerlink" title="示例 Docker Compose 配置"></a>示例 Docker Compose 配置</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">easysearch-node1:</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">&quot;602:602&quot;</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/easysearch:1.11.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">easysearch-node1</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">easysearch-node1</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">      <span class="attr">nofile:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">65536</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">65536</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/config:/app/easysearch/config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/data:/app/easysearch/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/logs:/app/easysearch/logs</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9201</span><span class="string">:9200</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9301</span><span class="string">:9300</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">easysearch-node2:</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">&quot;602:602&quot;</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/easysearch:1.11.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">easysearch-node2</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">easysearch-node2</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">      <span class="attr">nofile:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">65536</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">65536</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/config:/app/easysearch/config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/data:/app/easysearch/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/logs:/app/easysearch/logs</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9202</span><span class="string">:9200</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9302</span><span class="string">:9300</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">console:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/console:1.29.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">console</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">console</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/console/data:/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/console/log:/log</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9000</span><span class="string">:9000</span></span><br><span class="line">    <span class="attr">links:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">easysearch-node1:es1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">easysearch-node2:es2</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">esnet:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br><span class="line">    <span class="attr">ipam:</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">subnet:</span> <span class="number">172.24</span><span class="number">.0</span><span class="number">.0</span><span class="string">/16</span></span><br></pre></td></tr></table></figure><hr><h3 id="1️⃣-容器是否正常运行？"><a href="#1️⃣-容器是否正常运行？" class="headerlink" title="1️⃣ 容器是否正常运行？"></a>1️⃣ 容器是否正常运行？</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker ps -a | grep easysearch</span><br></pre></td></tr></table></figure><p>若容器状态为 <code>Exited</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">docker logs &lt;容器名&gt;</span><br></pre></td></tr></table></figure><p>如果你看到了如下错误信息，而你使用的是自签证书，可以暂时忽略：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">javax.net.ssl.SSLHandshakeException: Empty client certificate chain</span><br><span class="line">javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown</span><br></pre></td></tr></table></figure><hr><h3 id="2️⃣-容器是否监听端口？"><a href="#2️⃣-容器是否监听端口？" class="headerlink" title="2️⃣ 容器是否监听端口？"></a>2️⃣ 容器是否监听端口？</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">docker <span class="built_in">exec</span> -it &lt;容器名&gt; bash</span><br><span class="line">netstat -tlnp</span><br></pre></td></tr></table></figure><p>期望看到监听地址为 <code>0.0.0.0:9200</code> 和 <code>0.0.0.0:9300</code>，说明服务对外暴露成功。例如：</p><figure class="highlight plaintext"><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">tcp   0   0 0.0.0.0:9200   0.0.0.0:*   LISTEN   7/java</span><br><span class="line">tcp   0   0 0.0.0.0:9300   0.0.0.0:*   LISTEN   7/java</span><br></pre></td></tr></table></figure><hr><h3 id="3️⃣-Docker-端口映射是否配置正确？"><a href="#3️⃣-Docker-端口映射是否配置正确？" class="headerlink" title="3️⃣ Docker 端口映射是否配置正确？"></a>3️⃣ Docker 端口映射是否配置正确？</h3><p>检查 <code>docker-compose.yml</code> 中 <code>ports</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">docker ps</span><br></pre></td></tr></table></figure><p>确认是否已将容器内部端口映射到宿主机。</p><p>宿主机上也可以通过 <code>netstat</code> 或 <code>ss</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">netstat -tlnp | grep 9200</span><br></pre></td></tr></table></figure><p>确保监听地址是 <code>0.0.0.0:9200</code>，而非 <code>127.0.0.1</code>。</p><ul><li><code>-t</code>：显示 TCP 连接</li><li><code>-l</code>：仅显示监听状态（Listening）的端口</li><li><code>-n</code>：以数字方式显示地址和端口（避免 DNS 解析）</li><li><code>-p</code>：显示监听端口的程序 PID 和名称</li></ul><hr><h3 id="4️⃣-网络配置是否连通？"><a href="#4️⃣-网络配置是否连通？" class="headerlink" title="4️⃣ 网络配置是否连通？"></a>4️⃣ 网络配置是否连通？</h3><p>使用 <code>curl</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">curl http://yourhost:9200</span><br></pre></td></tr></table></figure><p>如需远程访问，需确认：</p><ul><li>容器监听的是 <code>0.0.0.0</code></li><li>映射端口已开放</li><li>网络桥接配置正常</li></ul><hr><h2 id="🧩-二、非-Docker-部署场景的排查方法"><a href="#🧩-二、非-Docker-部署场景的排查方法" class="headerlink" title="🧩 二、非 Docker 部署场景的排查方法"></a>🧩 二、非 Docker 部署场景的排查方法</h2><h3 id="1️⃣-服务是否启动？"><a href="#1️⃣-服务是否启动？" class="headerlink" title="1️⃣ 服务是否启动？"></a>1️⃣ 服务是否启动？</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">ps aux | grep easysearch</span><br></pre></td></tr></table></figure><p>也可查看 <code>nohup.out</code> 或 logs 目录中的日志文件，看是否存在环境变量、路径错误、权限不足等问题。</p><hr><h3 id="2️⃣-是否监听了正确的地址？"><a href="#2️⃣-是否监听了正确的地址？" class="headerlink" title="2️⃣ 是否监听了正确的地址？"></a>2️⃣ 是否监听了正确的地址？</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">netstat -tulnp | grep java</span><br></pre></td></tr></table></figure><p>EasySearch 默认只监听本地，建议修改配置文件：</p><figure class="highlight yml"><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"># config/easysearch.yml</span></span><br><span class="line"><span class="attr">network.host:</span> <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">http.port:</span> <span class="number">9200</span></span><br></pre></td></tr></table></figure><hr><h3 id="3️⃣-防火墙是否放行？"><a href="#3️⃣-防火墙是否放行？" class="headerlink" title="3️⃣ 防火墙是否放行？"></a>3️⃣ 防火墙是否放行？</h3><p>确认 Linux 主机的防火墙设置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ufw status</span><br><span class="line"><span class="built_in">sudo</span> iptables -L -n</span><br></pre></td></tr></table></figure><p>确保目标端口（如 9200、9300）已允许外部访问。</p><hr><h2 id="🌐-三、通用排查项（适用于所有部署方式）"><a href="#🌐-三、通用排查项（适用于所有部署方式）" class="headerlink" title="🌐 三、通用排查项（适用于所有部署方式）"></a>🌐 三、通用排查项（适用于所有部署方式）</h2><h3 id="✅-云服务器：检查安全组"><a href="#✅-云服务器：检查安全组" class="headerlink" title="✅ 云服务器：检查安全组"></a>✅ 云服务器：检查安全组</h3><p>云服务商（如 AWS、阿里云）通常还需配置安全组或防火墙规则，确保目标端口对外开放。</p><h3 id="✅-DNS-设置是否正确"><a href="#✅-DNS-设置是否正确" class="headerlink" title="✅ DNS 设置是否正确"></a>✅ DNS 设置是否正确</h3><p>使用 <code>dig</code> 和 <code>ping</code> 测试域名解析与连通性：</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">dig +short yourdomain.com</span><br><span class="line">ping yourdomain.com</span><br></pre></td></tr></table></figure><p>可用 <code>traceroute</code> 进一步分析路径：(我在 MacOS 下测试的)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> traceroute -P TCP -p 9200 192.168.X.X</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">traceroute to 192.168.X.X (192.168.X.X), 64 hops max, 40 byte packets</span><br><span class="line"> 1  192.168.X.X (192.168.X.X)  3.756 ms  3.208 ms  3.142 ms</span><br></pre></td></tr></table></figure><h2 id="✅-总结：排查-EasySearch-的四步法"><a href="#✅-总结：排查-EasySearch-的四步法" class="headerlink" title="✅ 总结：排查 EasySearch 的四步法"></a>✅ 总结：排查 EasySearch 的四步法</h2><figure class="highlight markdown"><table><tr><td class="gutter"><pre><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="bullet">1.</span> 服务是否启动？</span><br><span class="line"><span class="bullet">2.</span> 端口是否监听？</span><br><span class="line"><span class="bullet">3.</span> 网络是否打通？</span><br><span class="line"><span class="bullet">4.</span> 安全组是否放行？</span><br></pre></td></tr></table></figure><p>无论是本地部署还是云端部署，掌握上述排查方法，你就能迅速定位并解决 EasySearch 无法访问的问题。<br>如果你觉得有帮助，也欢迎你将本文加入收藏夹，或转发给其他使用 EasySearch 的小伙伴 👇</p>]]></content>
    
    
    <summary type="html">排查 Easysearch 服务突然无法访问的常见原因与解决方案</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 基础运维扫盲指南：从 HTTP 到 HTTPS、认证与安全访问全解析</title>
    <link href="https://blog.no-claw.com/posts/1f7436df/"/>
    <id>https://blog.no-claw.com/posts/1f7436df/</id>
    <published>2025-04-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>众所周知，原生 Elasticsearch 默认开启的是 <strong>HTTP 明文接口</strong>，并且不开启认证或加密。如果要启用 TLS&#x2F;SSL 加密和账号认证，通常需要额外配置一系列安全模块。</p><p>而在 EasySearch 中，官方通过 <code>initialize.sh</code> 初始化脚本，大大简化了这些安全配置，<strong>启动时就能自动生成证书并开启密码保护</strong>。不过在一些测试或开发环境中，我们可能希望临时使用 <strong>HTTP + 无密码</strong> 的简化方式来调试。</p><p>本文将从配置文件入手，逐步说明如何启用或关闭认证、如何从 HTTPS 切换回 HTTP，以及如何开放外网访问。</p><h2 id=""><a href="#" class="headerlink" title=""></a><span id="more"></span></h2><h2 id="🛠-初始化启动信息与默认密码"><a href="#🛠-初始化启动信息与默认密码" class="headerlink" title="🛠 初始化启动信息与默认密码"></a>🛠 初始化启动信息与默认密码</h2><p>当你执行 <code>bin/initialize.sh</code> 后，终端和 <code>initialize.log</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">curl -ku admin:160824cab0b02193226e https://localhost:9200</span><br></pre></td></tr></table></figure><p>默认情况下，服务已启用 HTTPS 与密码认证。</p><hr><h2 id="⚙-修改配置文件：easysearch-yml"><a href="#⚙-修改配置文件：easysearch-yml" class="headerlink" title="⚙ 修改配置文件：easysearch.yml"></a>⚙ 修改配置文件：easysearch.yml</h2><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">config/easysearch.yml</span><br></pre></td></tr></table></figure><p>可以在此文件中自定义集群名称：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cluster.name:</span> <span class="string">my-application</span></span><br></pre></td></tr></table></figure><p>修改前访问效果如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -ku admin:160824cab0b02193226e http://localhost:9200</span><br></pre></td></tr></table></figure><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="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;easysearch&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>修改后再次访问：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -ku admin:160824cab0b02193226e http://localhost:9200</span><br></pre></td></tr></table></figure><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="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my-application&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><hr><h2 id="🌐-开放外网访问（单节点部署）"><a href="#🌐-开放外网访问（单节点部署）" class="headerlink" title="🌐 开放外网访问（单节点部署）"></a>🌐 开放外网访问（单节点部署）</h2><p>如果你希望让其他设备或公网访问 EasySearch，可以添加以下参数：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">network.host:</span> <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">http.port:</span> <span class="number">9200</span></span><br><span class="line"><span class="attr">discovery.type:</span> <span class="string">single-node</span></span><br></pre></td></tr></table></figure><p>此外，为了提高对 Elasticsearch 客户端的兼容性，建议添加：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">elasticsearch.api_compatibility:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><hr><h2 id="🔒-控制-HTTPS-与密码认证"><a href="#🔒-控制-HTTPS-与密码认证" class="headerlink" title="🔒 控制 HTTPS 与密码认证"></a>🔒 控制 HTTPS 与密码认证</h2><p>EasySearch 的安全配置由以下两个核心参数控制：</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>security.enabled</code></td><td>是否启用认证（账号密码）和 HTTPS 模块</td></tr><tr><td><code>security.ssl.http.enabled</code></td><td>是否启用 HTTPS（SSL&#x2F;TLS 加密）</td></tr></tbody></table><h3 id="🔁-配置组合含义如下："><a href="#🔁-配置组合含义如下：" class="headerlink" title="🔁 配置组合含义如下："></a>🔁 配置组合含义如下：</h3><table><thead><tr><th><code>security.enabled</code></th><th><code>security.ssl.http.enabled</code></th><th>效果</th></tr></thead><tbody><tr><td>true</td><td>true</td><td>默认配置，启用认证 + HTTPS（推荐）</td></tr><tr><td>true</td><td>false</td><td>仅认证，无加密，使用明文 HTTP</td></tr><tr><td>false</td><td>true</td><td>无认证，HTTPS 加密，仅适合特殊用途</td></tr><tr><td>false</td><td>false</td><td>最开放，HTTP + 无密码，<strong>不推荐生产使用</strong></td></tr></tbody></table><p>你可以根据实际需求选择是否打开加密或认证，适配测试与生产环境。</p><hr><h2 id="📸-示例：启用-HTTP-且开启认证"><a href="#📸-示例：启用-HTTP-且开启认证" class="headerlink" title="📸 示例：启用 HTTP 且开启认证"></a>📸 示例：启用 HTTP 且开启认证</h2><p>如果你修改配置为 <code>security.ssl.http.enabled: false</code>，即可使用 HTTP，但仍要求输入用户名密码进行访问：</p><figure class="highlight yaml"><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="attr">security.enabled:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">security.ssl.http.enabled:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>访问效果如下图所示：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250425120123219.png" alt="image-20250425120123219"></p><hr><h2 id="✅-小结"><a href="#✅-小结" class="headerlink" title="✅ 小结"></a>✅ 小结</h2><table><thead><tr><th>场景</th><th>推荐配置</th></tr></thead><tbody><tr><td>本地测试（不加密、无密码）</td><td><code>security.enabled: false</code> + <code>security.ssl.http.enabled: false</code></td></tr><tr><td>本地测试（仅加认证）</td><td><code>security.enabled: true</code> + <code>security.ssl.http.enabled: false</code></td></tr><tr><td>安全访问（默认）</td><td><code>security.enabled: true</code> + <code>security.ssl.http.enabled: true</code></td></tr><tr><td>HTTPS 不认证</td><td><code>security.enabled: false</code> + <code>security.ssl.http.enabled: true</code></td></tr></tbody></table><p>EasySearch 提供了灵活的配置方式，适合不同场景自由切换。对于开发者来说，理解这两个参数的作用，是快速上手运维的第一步。</p><p>提到认证,我们再看看如何修改密码,由于 Easysearch 默认新建了一个 admin 的用户,并且存在 config&#x2F;security&#x2F;user.yml 下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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><br><span class="line"># This is the internal user database create by initialize</span><br><span class="line"># The hash value is a bcrypt hash and can be generated with hash_password.sh</span><br><span class="line"></span><br><span class="line">_meta:</span><br><span class="line">  type: &quot;user&quot;</span><br><span class="line">  config_version: 2</span><br><span class="line"></span><br><span class="line"># Define your internal users here</span><br><span class="line"></span><br><span class="line">## Demo users</span><br><span class="line">admin:</span><br><span class="line">  hash: &quot;$2y$12$rmNDJxpQdRDb3F1dqk.uweSZqH3VAqeEpkP298vJ6QS99K80kbRoO&quot;</span><br><span class="line">  reserved: true</span><br><span class="line">  external_roles:</span><br><span class="line">    - &quot;admin&quot;</span><br><span class="line">  description: &quot;Admin user&quot;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这个我们也可以使用 postman 开调用 API.<a href="http://localhost:9200//_security/account">http://localhost:9200/\_security/account</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250425124456187.png" alt="image-20250425124456187"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;http://192.168.5.8:9200&quot;</span></span><br><span class="line"></span><br><span class="line">payload = &#123;&#125;</span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">&#x27;Basic YWRtaW46MTYwODI0Y2FiMGIwMjE5MzIyNmU=&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;GET&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>新建用户</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250425153652216.png"></p><p>查看接口:<a href="https://localhost:9200//_security/user">https://localhost:9200/\_security/user</a></p><p>不过 yml 文件还是只有 admin,用 api 查看</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250425153959573.png" alt="image-20250425153959573"></p><p>使用新用户测试可以访问:</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250425153643049.png" alt="image-20250425153643049"></p><p>mysql -P 3306 -u admin -p -h mysqlsource.crtihcoeqzab.us-west-2.rds.amazonaws.com &lt; people.sql</p><p>mysql -P 3306 -u admin -p -h mysqlsource.crtihcoeqzab.us-west-2.rds.amazonaws.com people</p><p>psql -h cloudacademylabs-targetcluster-0zdokkxbiyyh.cluster-ro-crtihcoeqzab.us-west-2.rds.amazonaws.com -U postgres -p 5432 people</p>]]></content>
    
    
    <summary type="html">Easysearch 运维入门：HTTP/HTTPS 配置、认证机制与安全访问全解析</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Coco-AI 接入 Google drive</title>
    <link href="https://blog.no-claw.com/posts/10b05aec/"/>
    <id>https://blog.no-claw.com/posts/10b05aec/</id>
    <published>2025-04-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Coco-AI 最早的版本就提供了接入 Google drive 的视频，今天我终于实现了，而且借着全图形化的优势更加方便了。</p><p>参考这个文档新建 google SSO（好像也没啥参考性）</p><p><a href="https://developers.google.com/workspace/drive/api/quickstart/go?hl=zh-cn">https://developers.google.com/workspace/drive/api/quickstart/go?hl=zh-cn</a></p><span id="more"></span><p>创建客户端<br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214328588.png" alt="image-20250417214328588"></p><p>填入信息，</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214423627.png" alt="image-20250417214423627"></p><p>然后在数据访问中添加权限 -</p><p>缺少的权限加在这里（如图），<a href="https://www.googleapis.com/auth/drive">https://www.googleapis.com/auth/drive</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417211814068.png" alt="image-20250417211814068"></p><p>在 coco-sever 更新 google drive 的信息</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214739221.png" alt="image-20250417214739221"></p><p>然后在 coco-server 中连接</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214802320.png" alt="image-20250417214802320"></p><p>跳转 google sso</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214839097.png" alt="image-20250417214839097"></p><p>由于是测试账户，所以会有这个弹窗，继续就好</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214847470.png" alt="image-20250417214847470"></p><p>再次进行测试</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214908799.png" alt="image-20250417214908799"></p><p>获取权限</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417211919662.png" alt="image-20250417211919662"></p><p>显示登陆成功</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417214925986.png" alt="image-20250417214925986"></p><p>然后可以在数据源中看到对应数据</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417212018974.png" alt="image-20250417212018974"></p>]]></content>
    
    
    <summary type="html">零代码将 Google Drive 接入 Coco AI，实现云端资料智能检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco-AI 集成语雀作为语料库进行检索</title>
    <link href="https://blog.no-claw.com/posts/6a9ecc86/"/>
    <id>https://blog.no-claw.com/posts/6a9ecc86/</id>
    <published>2025-04-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="coco-AI-把语雀当作语料库做检索"><a href="#coco-AI-把语雀当作语料库做检索" class="headerlink" title="coco-AI 把语雀当作语料库做检索"></a>coco-AI 把语雀当作语料库做检索</h1><p>说在前面，这个功能需要在语雀后台申请 Personal Access Token。使用的需要超级会员的（不是邀请新用户给的专业会员），所以需要付费使用。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417184514581.png" alt="image-20250417184514581"></p><span id="more"></span><p>然后在语雀后台，也就是<a href="https://www.yuque.com/settings/tokens%E5%A4%84%E5%8F%AF%E4%BB%A5%E7%9C%8B%E8%A7%81%E7%94%B3%E8%AF%B7token%E7%9A%84%E5%9C%B0%E6%96%B9%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%BD%A0%E6%B2%A1%E6%9C%89%E8%B6%85%E7%BA%A7%E4%BC%9A%E5%91%98%EF%BC%8C%E8%BF%99%E4%B8%AA%E6%98%AF%E6%B2%A1%E5%8A%9E%E6%B3%95%E7%94%A8%E7%9A%84%E3%80%82">https://www.yuque.com/settings/tokens处可以看见申请token的地方，如果你没有超级会员，这个是没办法用的。</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417184608457.png" alt="image-20250417184608457"></p><p>点击新建，创建 token 分发权限，我这边给了所以的权限，语雀和 Notion 不同，这里给了权限就够了，其他地方无需在给权限。（手动@Notion 还要在文档或者文件夹授权）</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417184731432.png" alt="image-20250417184731432"></p><p>点击查看详情可以看到 token，这里的 token 是可以反复查看的，由此语雀这一侧的设置完毕。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417184902299.png" alt="image-20250417184902299"></p><p>回到 coco-AI,我这边使用的是这个镜像，这里添加了对个人版本语雀的支持。</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">infinilabs/coco:0.3.2_NIGHTLY-20250417</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">docker run -d --name cocoserver -p 9000:9000 infinilabs/coco:0.3.2_NIGHTLY-20250417</span><br></pre></td></tr></table></figure><p>然后进入后台初始化模型，我这里使用的本地部署的 deepseek：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417185219148.png" alt="image-20250417185219148"></p><p>点击数据源，选择 yuque connector</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417185638555.png" alt="**image-20250417185638555**"></p><p>配置的地方很简单，填入数据源名称和 Token 和刷新时间。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417185517578.png" alt="image-20250417185517578"></p><p>然后我们就可以看到刷新的数据了</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417190023214.png" alt="image-20250417190023214"></p>]]></content>
    
    
    <summary type="html">将语雀文档接入 Coco AI 作为语料库实现智能检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco-AI 接入自定义数据源</title>
    <link href="https://blog.no-claw.com/posts/271ab503/"/>
    <id>https://blog.no-claw.com/posts/271ab503/</id>
    <published>2025-04-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Coco-AI-接入自定义数据源"><a href="#Coco-AI-接入自定义数据源" class="headerlink" title="Coco-AI 接入自定义数据源"></a>Coco-AI 接入自定义数据源</h1><p>之前使用 Hugo Connector 接入和 hexo 和任意 Markdown，后来官方也支持了对于任意数据源的支持，主要是开放了这个接口：</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417194847718.png" alt="image-20250417194847718"></p><p>具体操作如下：</p><span id="more"></span><p>设置 - conntor - 新增，让输入名称和描述等信息，新建出来 conntor</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417194910829.png" alt="image-20250417194910829"></p><p>然后我们就能在数据源上的页面上看到刚刚添加的了 Customize Connector 了</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417195015291.png" alt="image-20250417195015291"></p><p>点开提示，给了一个 API</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417194847718.png" alt="image-20250417194847718"></p><p>然后我们去创建 token，如图</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417195602722.png" alt="image-20250417195602722"></p><p>我这边使用 Postman 进行设置</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417195729078.png"></p><p>如果你的请求没有带 token，就是这样的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250417195729078.png" alt="image-20250417195729078"></p><p>转成代码的是这样的，当然也可以开发自己的 agent。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;http://localhost:9000/datasource/d00eeolvf2xxx/_doc&quot;</span></span><br><span class="line"></span><br><span class="line">payload = json.dumps(&#123;</span><br><span class="line">  <span class="string">&quot;title&quot;</span>: <span class="string">&quot;I am just a Coco doc that you can search&quot;</span>,</span><br><span class="line">  <span class="string">&quot;summary&quot;</span>: <span class="string">&quot;Nothing but great start&quot;</span>,</span><br><span class="line">  <span class="string">&quot;content&quot;</span>: <span class="string">&quot;Coco is a unified private search engien that you can trust.&quot;</span>,</span><br><span class="line">  <span class="string">&quot;url&quot;</span>: <span class="string">&quot;http://coco.rs/&quot;</span>,</span><br><span class="line">  <span class="string">&quot;icon&quot;</span>: <span class="string">&quot;default&quot;</span></span><br><span class="line">&#125;)</span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;POST&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">为 Coco AI 接入自定义数据源，扩展智能检索范围</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco-AI 支持嵌入，让你的网站拥有 AI 搜索力</title>
    <link href="https://blog.no-claw.com/posts/e32dbde5/"/>
    <id>https://blog.no-claw.com/posts/e32dbde5/</id>
    <published>2025-04-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Coco-AI-支持嵌入，让你的网站支持-AI-搜索"><a href="#Coco-AI-支持嵌入，让你的网站支持-AI-搜索" class="headerlink" title="Coco-AI 支持嵌入，让你的网站支持 AI 搜索"></a>Coco-AI 支持嵌入，让你的网站支持 AI 搜索</h2><p>在之前的文章中，我们让 Hexo，hugo 博客 支持了 coco AI 检索，也就是说我们还得使用客户端来检索，那是不是把搜索放在博客上呢？</p><span id="more"></span><p>Coco-AI 在 0.3 的版本中</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250402213914361.png" alt="image-20250402213914361"></p><hr><p>先找一个 html 来看个效果。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh-CN&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>搜索组件嵌入示例<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">2rem</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-id">#searchbox</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin-top</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ccc</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">16px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">border-radius</span>: <span class="number">8px</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>欢迎使用 Cloudsmithy 搜索组件<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>下面是通过 ES Module 引入的搜索框：<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;searchbox&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">import</span> &#123; searchbox &#125; <span class="keyword">from</span> <span class="string">&quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">searchbox</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">container</span>: <span class="string">&quot;#searchbox&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">      &#125;);</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="💡-原始代码："><a href="#💡-原始代码：" class="headerlink" title="💡 原始代码："></a>💡 原始代码：</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;searchbox&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> &#123; searchbox &#125; <span class="keyword">from</span> <span class="string">&quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">searchbox</span>(&#123; <span class="attr">container</span>: <span class="string">&quot;#searchbox&quot;</span> &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><hr><h2 id="🧩-每部分解析"><a href="#🧩-每部分解析" class="headerlink" title="🧩 每部分解析"></a>🧩 每部分解析</h2><h3 id="1"><a href="#1" class="headerlink" title="1. &lt;div id=&quot;searchbox&quot;&gt;&lt;/div&gt;"></a>1. <code>&lt;div id=&quot;searchbox&quot;&gt;&lt;/div&gt;</code></h3><p>这是一个空的 <code>div</code> 元素，作为<strong>挂载容器</strong>。你的 <code>searchbox</code> 组件会被渲染进这个 <code>div</code>。</p><ul><li>就像 React 或 Vue 应用会挂载到 <code>&lt;div id=&quot;app&quot;&gt;</code> 一样</li><li>你在这里指定了 id 为 <code>searchbox</code>，用于后面初始化组件时绑定</li></ul><hr><h3 id="2"><a href="#2" class="headerlink" title="2. &lt;script type=&quot;module&quot;&gt;"></a>2. <code>&lt;script type=&quot;module&quot;&gt;</code></h3><p>这表示：这是一个 <strong>ES Module 格式的 JavaScript 脚本</strong>。</p><ul><li>现代浏览器支持原生的模块化加载（无需打包器）</li><li>可以使用 <code>import</code> 加载其他模块、组件</li></ul><hr><h3 id="3-import-searchbox-from-http-localhost-9000-integration-cvmhvjl92jog2dokvsd0-widget"><a href="#3-import-searchbox-from-http-localhost-9000-integration-cvmhvjl92jog2dokvsd0-widget" class="headerlink" title="3. import { searchbox } from &quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;;"></a>3. <code>import { searchbox } from &quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;;</code></h3><p>从远程地址（本地服务器）中导入 <code>searchbox</code> 函数：</p><ul><li><p>说明这个 URL 返回的是一个 JavaScript 模块，里面导出了 <code>searchbox</code></p></li><li><p>类似 Vue、React 中的组件库导入：</p><figure class="highlight js"><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> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&quot;element-plus&quot;</span>;</span><br></pre></td></tr></table></figure></li></ul><hr><h3 id="4-searchbox-container-searchbox"><a href="#4-searchbox-container-searchbox" class="headerlink" title="4. searchbox({container: &quot;#searchbox&quot;});"></a>4. <code>searchbox({container: &quot;#searchbox&quot;});</code></h3><p>调用 <code>searchbox</code> 函数，初始化组件，并挂载到页面中：</p><ul><li>参数是一个对象，指明组件要渲染在哪个 DOM 元素里</li><li><code>container: &quot;#searchbox&quot;</code> 通过 CSS 选择器指定了上面那个 <code>div</code></li></ul><hr><h2 id="🧠-通俗类比"><a href="#🧠-通俗类比" class="headerlink" title="🧠 通俗类比"></a>🧠 通俗类比</h2><p>就像你在网页上写：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;chat&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> &#123; chatWidget &#125; <span class="keyword">from</span> <span class="string">&quot;./chat.js&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">chatWidget</span>(&#123; <span class="attr">container</span>: <span class="string">&quot;#chat&quot;</span> &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>它就是在某个 <code>div</code> 中挂载一个聊天插件一样。你这段代码的本质就是嵌入并运行一个远程提供的 JS 组件。</p><hr><h2 id="✅-总结一句话："><a href="#✅-总结一句话：" class="headerlink" title="✅ 总结一句话："></a>✅ 总结一句话：</h2><p>这段代码的作用是：</p><blockquote><p><strong>把远程加载的 <code>searchbox</code> 组件挂载到网页上的 <code>&lt;div id=&quot;searchbox&quot;&gt;</code> 中，完成动态插入功能。</strong></p></blockquote><hr><p>如果你希望我反向帮你写出那个 <code>searchbox</code> 模块怎么写，或者做成本地文件测试，也可以告诉我~</p><p>下面是一个可以在任意网站自动嵌入你给出的 <code>&lt;div&gt;</code> 和 <code>&lt;script&gt;</code> 的油猴（Tampermonkey）脚本示例：</p><hr><h3 id="✅-功能说明"><a href="#✅-功能说明" class="headerlink" title="✅ 功能说明"></a>✅ 功能说明</h3><ul><li>在页面加载完成后，向 <code>&lt;body&gt;</code> 插入一个 <code>#searchbox</code> 的 <code>div</code></li><li>加载你的远程模块并初始化 <code>searchbox</code> 组件</li></ul><hr><p>一开始打算在 hexo 的组件上改，但是在编译阶段有问题，索性使用油猴脚本来实现</p><h3 id="🐵-Tampermonkey-脚本示例"><a href="#🐵-Tampermonkey-脚本示例" class="headerlink" title="🐵 Tampermonkey 脚本示例"></a>🐵 Tampermonkey 脚本示例</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="comment">// ==UserScript==</span></span><br><span class="line"><span class="comment">// @name         Searchbox Embedder</span></span><br><span class="line"><span class="comment">// @namespace    http://tampermonkey.net/</span></span><br><span class="line"><span class="comment">// @version      0.1</span></span><br><span class="line"><span class="comment">// @description  在任意网页中注入 searchbox 小部件</span></span><br><span class="line"><span class="comment">// @author       You</span></span><br><span class="line"><span class="comment">// @match        *://*/*</span></span><br><span class="line"><span class="comment">// @grant        none</span></span><br><span class="line"><span class="comment">// @run-at       document-end</span></span><br><span class="line"><span class="comment">// ==/UserScript==</span></span><br><span class="line"></span><br><span class="line">(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="string">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 创建 searchbox 容器</span></span><br><span class="line">  <span class="keyword">const</span> searchboxDiv = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">  searchboxDiv.<span class="property">id</span> = <span class="string">&quot;searchbox&quot;</span>;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(searchboxDiv);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 动态加载模块脚本（ESM）</span></span><br><span class="line">  <span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;script&quot;</span>);</span><br><span class="line">  script.<span class="property">type</span> = <span class="string">&quot;module&quot;</span>;</span><br><span class="line">  script.<span class="property">textContent</span> = <span class="string">`</span></span><br><span class="line"><span class="string">        import &#123; searchbox &#125; from &quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;;</span></span><br><span class="line"><span class="string">        searchbox(&#123; container: &quot;#searchbox&quot; &#125;);</span></span><br><span class="line"><span class="string">    `</span>;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(script);</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><hr><h3 id="⚠️-注意事项"><a href="#⚠️-注意事项" class="headerlink" title="⚠️ 注意事项"></a>⚠️ 注意事项</h3><ol><li>浏览器必须支持 ES Module。</li><li>若该域名不是 HTTPS，确保目标网页也是 HTTP，否则会被浏览器拦截跨协议内容。</li><li>若需局部生效，请将 <code>@match</code> 改为具体的页面，例如：<code>https://example.com/*</code></li></ol><hr><hr><h2 id="🌟-脚本整体作用"><a href="#🌟-脚本整体作用" class="headerlink" title="🌟 脚本整体作用"></a>🌟 脚本整体作用</h2><p>这个油猴脚本的作用是：<strong>在任何网页上自动插入一个 <code>div#searchbox</code> 容器，并加载你提供的远程模块脚本，渲染搜索框组件</strong>。</p><hr><h2 id="📜-脚本逐行解析"><a href="#📜-脚本逐行解析" class="headerlink" title="📜 脚本逐行解析"></a>📜 脚本逐行解析</h2><figure class="highlight javascript"><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">// ==UserScript==</span></span><br><span class="line"><span class="comment">// @name         Searchbox Embedder</span></span><br></pre></td></tr></table></figure><ul><li><code>@name</code> 是脚本的名字，显示在 Tampermonkey 的控制面板中。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @namespace    http://tampermonkey.net/</span></span><br></pre></td></tr></table></figure><ul><li>命名空间，可以用来区分多个脚本的作者或用途（不重要）。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @version      0.1</span></span><br></pre></td></tr></table></figure><ul><li>脚本版本号。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @description  在任意网页中注入 searchbox 小部件</span></span><br></pre></td></tr></table></figure><ul><li>简要说明这个脚本做什么。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @author       You</span></span><br></pre></td></tr></table></figure><ul><li>作者名，可以改成你自己的。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @match        *://*/*</span></span><br></pre></td></tr></table></figure><ul><li>匹配所有网站页面。如果你只想对某个特定网站注入，可将它改为：<br><code>// @match https://example.com/*</code></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @grant        none</span></span><br></pre></td></tr></table></figure><ul><li>没有使用 Tampermonkey 的特殊权限（如 GM_* 方法），所以可以写 <code>none</code>。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @run-at       document-end</span></span><br></pre></td></tr></table></figure><ul><li>脚本在 DOM 加载完成后执行（类似 <code>DOMContentLoaded</code>）。</li></ul><hr><h2 id="💻-主体逻辑解析"><a href="#💻-主体逻辑解析" class="headerlink" title="💻 主体逻辑解析"></a>💻 主体逻辑解析</h2><figure class="highlight javascript"><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="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="string">&#x27;use strict&#x27;</span>;</span><br></pre></td></tr></table></figure><ul><li>启用严格模式，避免某些低级错误。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><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">// 创建 searchbox 容器</span></span><br><span class="line"><span class="keyword">const</span> searchboxDiv = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">searchboxDiv.<span class="property">id</span> = <span class="string">&quot;searchbox&quot;</span>;</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(searchboxDiv);</span><br></pre></td></tr></table></figure><ul><li>创建一个 <code>&lt;div id=&quot;searchbox&quot;&gt;</code> 并插入到 <code>&lt;body&gt;</code> 中，作为挂载点。</li></ul><figure class="highlight javascript"><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">// 动态加载模块脚本（ESM）</span></span><br><span class="line"><span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;script&quot;</span>);</span><br><span class="line">script.<span class="property">type</span> = <span class="string">&quot;module&quot;</span>;</span><br></pre></td></tr></table></figure><ul><li>创建一个新的 <code>&lt;script type=&quot;module&quot;&gt;</code>，用来加载 ES 模块。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><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">script.<span class="property">textContent</span> = <span class="string">`</span></span><br><span class="line"><span class="string">        import &#123; searchbox &#125; from &quot;http://localhost:9000/integration/cvmhvjl92jog2dokvsd0/widget&quot;;</span></span><br><span class="line"><span class="string">        searchbox(&#123; container: &quot;#searchbox&quot; &#125;);</span></span><br><span class="line"><span class="string">    `</span>;</span><br></pre></td></tr></table></figure><ul><li>直接把模块内容写入 script 标签中：<ul><li>从你本地地址加载 <code>searchbox</code> 组件</li><li>调用 <code>searchbox({ container: &quot;#searchbox&quot; })</code> 初始化它</li></ul></li></ul><figure class="highlight javascript"><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="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(script);</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><ul><li>把这个 <code>&lt;script type=&quot;module&quot;&gt;</code> 插入到 <code>&lt;body&gt;</code> 中，触发模块加载和执行。</li></ul><hr><h2 id="✅-总结"><a href="#✅-总结" class="headerlink" title="✅ 总结"></a>✅ 总结</h2><p>这个脚本做了三件事：</p><ol><li>在网页中插入一个 <code>div#searchbox</code></li><li>动态加载你本地的模块组件</li><li>初始化这个组件并挂载到 <code>#searchbox</code> 上</li></ol>]]></content>
    
    
    <summary type="html">通过嵌入方式为网站集成 Coco AI 搜索能力</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>等了半年的 POE 系统，铭凡 S100 不完全测评</title>
    <link href="https://blog.no-claw.com/posts/a7aa5f43/"/>
    <id>https://blog.no-claw.com/posts/a7aa5f43/</id>
    <published>2025-04-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>这个电脑吸引我的点是 POE 供电，如果配上 POE 交换机岂不是妥妥的软路由圣体，抱着这样的目的下单的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250505113735318.png" alt="image-20250505113735318"></p> <span id="more"></span><p><strong>先写吐槽</strong></p><ol><li>原来店家宣传的是 16G 内存，后来改成 8G，之前还经常断货</li><li>差评率很高，基本是吐槽锁功耗和预装 win11（如果是准系统还能省点 lisence 的钱）</li><li>N100 这个价位性价比其实很低，可以购买 N305 了</li><li>这机器磁盘用的 UFS2.1，是在太落后了</li><li>运行的时候很热，风扇声音很大。</li></ol><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250505113810346.png" alt="image-20250505113810346"></p><p>而铭凡这个厂家也是比较口碑两极化，这几年国产小主机层出不穷，minisforum 应该算是最早的一批。一部分玩家为了性价比方案选择国产小主机，而另一部分玩家还是只认电脑三巨头再加上 Apple。就比如雷神被吐槽是，一线的价格，二线的产品，三线的售后。（来自网络，不代表本人观点）</p><p>回到电脑本身，自带的 windows 真的卡，非常卡！！！！我是觉得实际办公都卡，跑 geekbeanch 也很卡。</p><p>然后换了 Ubuntu，网上说要根据 UFS 做一些更改，卖家也表示不支持除了 windows 以外的系统。但是我实际用的 ubuntu24.0，直接随身碟也装上了，安装时候也很卡，甚至不如大学的笔记本&#x3D;。&#x3D;，不确定是性能是在拉垮还是这几年 ubuntu 越来越臃肿了。</p><p>网上是这个方案，我先贴出来，Mark 一下。<a href="https://www.reddit.com/r/MiniPCs/comments/1eb8dgv/minisforum_s100_only_runs_windows_anyone_else/">https://www.reddit.com/r/MiniPCs/comments/1eb8dgv/minisforum_s100_only_runs_windows_anyone_else/</a></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"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /dev <span class="string">&quot;<span class="variable">$PWD</span>/dev&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /proc <span class="string">&quot;<span class="variable">$PWD</span>/proc&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /sys <span class="string">&quot;<span class="variable">$PWD</span>/sys&quot;</span></span><br><span class="line"></span><br><span class="line">Type:</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chroot</span> <span class="string">&quot;<span class="variable">$PWD</span>&quot;</span> /bin/bash --login</span><br><span class="line"></span><br><span class="line">Type:</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;ufshcd&quot;</span> &gt;&gt; /etc/initramfs-tools/modules</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;ufshcd-pci&quot;</span> &gt;&gt; /etc/initramfs-tools/modules</span><br><span class="line"></span><br><span class="line">Type:</span><br><span class="line">update-initramfs -u -k all</span><br></pre></td></tr></table></figure><p>这是加了详细注释的版本（完全针对新手也能理解的程度）：</p><hr><p><strong>将 Ubuntu 系统的根分区挂载到&#x2F;mnt 目录</strong><br>（假设你已经挂载好了，这一步是之前完成的）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入挂载好的/mnt目录</span></span><br><span class="line"><span class="built_in">cd</span> /mnt</span><br></pre></td></tr></table></figure><hr><p><strong>挂载必要的系统目录，确保在 chroot 环境中能正常使用系统功能</strong></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"><span class="comment"># 绑定/dev目录，这样chroot后能访问设备文件（如磁盘、终端等）</span></span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /dev <span class="string">&quot;<span class="variable">$PWD</span>/dev&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 绑定/proc目录，这样chroot后能访问进程信息</span></span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /proc <span class="string">&quot;<span class="variable">$PWD</span>/proc&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 绑定/sys目录，这样chroot后能访问内核信息</span></span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">bind</span> /sys <span class="string">&quot;<span class="variable">$PWD</span>/sys&quot;</span></span><br></pre></td></tr></table></figure><hr><p><strong>切换到 chroot 环境，相当于“进入”你的 Ubuntu 系统中，像正常启动一样操作</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">chroot</span> <span class="string">&quot;<span class="variable">$PWD</span>&quot;</span> /bin/bash --login</span><br></pre></td></tr></table></figure><blockquote><p>说明：<br>这样之后你在这个终端里的所有操作，都会直接作用于你挂载的 Ubuntu 系统上（而不是 LiveCD 或恢复环境）。</p></blockquote><hr><p><strong>在 initramfs 的配置文件中添加模块，确保开机时加载 UFS 硬盘相关驱动</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;ufshcd&quot;</span> &gt;&gt; /etc/initramfs-tools/modules</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;ufshcd-pci&quot;</span> &gt;&gt; /etc/initramfs-tools/modules</span><br></pre></td></tr></table></figure><blockquote><p>说明：<br>这里<code>ufshcd</code>和<code>ufshcd-pci</code>是 UFS 存储设备的驱动模块，如果不加，可能开机找不到硬盘导致无法启动。</p></blockquote><hr><p><strong>更新 initramfs，将刚才加入的模块纳入系统启动时加载的内容</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">update-initramfs -u -k all</span><br></pre></td></tr></table></figure><blockquote><p>说明：<br><code>-u</code> 表示更新现有的 initramfs，<code>-k all</code>表示为所有内核版本更新。<br>这一步非常重要，否则刚才写入的模块不会生效。</p></blockquote><hr><p><strong>退出 chroot 环境</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exit</span></span><br></pre></td></tr></table></figure><blockquote><p>说明：<br>这会退出你刚才“进入”的 Ubuntu 系统，回到正常的恢复环境终端。</p></blockquote><hr><p><strong>总结一句话</strong><br>整个流程的作用是：<br><strong>在离线状态下手动为 Ubuntu 系统补充 UFS 硬盘驱动，确保系统下次启动时可以识别和加载 UFS 硬盘，从而正常启动。</strong></p><p>这个是在安装了 ubuntu 系统的 geekbench6 的跑分，这个是调整功耗到 9W 的，如果是默认的 6W 跑分只有 665，是完全没眼看的程度。期间也尝试过在 Bios 把功耗调整到 12.5W，但是分数反而降到 145 左右，不确定是不是降频或者功耗撞墙什么的。<a href="https://browser.geekbench.com/v6/cpu/11818077">https://browser.geekbench.com/v6/cpu/11818077</a></p><p>改功耗：<a href="https://www.youtube.com/watch?v=g6Dk3BMgoYw%E7%A8%8B%E5%BA%A6">https://www.youtube.com/watch?v=g6Dk3BMgoYw程度</a></p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/image-20250505092958833.png" alt="image-20250505092958833"></p><p>本来还想试试 openwrt 的，但是也没成功，UFS2.1 也没啥折腾的必要。用了前面修复 UFS 的命令，执行之后 boot 里也没有引导。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo dd if=immortalwrt-24.10.0-x86-64-generic-ext4-combined-efi.img of=/dev/sda bs=4M status=progress</span><br></pre></td></tr></table></figure><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>如果追求桌面办公的话，适合对性能要求不高，能容忍卡顿的。</p><p>如果安装 Linux 的话，不强制追求 X86 话，还是树莓派吧，不过价格能够降一半倒是一个可以接受的方案。</p><p>这个能 POE 一线通固然是优点，但是 Typec 供电+wifi 网卡也没差太多。虽然放弱电箱真的好看好管理，但是 N100 对于现在的我来说，也就只能跑跑路由器系统或者轻 NAS 了吧。</p>]]></content>
    
    
    <summary type="html">铭凡 S100 POE 供电小主机开箱测评与使用体验</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="电脑外设" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%84%91%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>Cudy TR3000 刷 Openwrt</title>
    <link href="https://blog.no-claw.com/posts/3e1838df/"/>
    <id>https://blog.no-claw.com/posts/3e1838df/</id>
    <published>2025-04-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>想了好久，还是有必要把软路由从 All in one 中分离开，基于性价比的原因，看上了这款 Cudy RT3000，相对于 400 价位的 MT3000 而言，内存只有 128M，而且没有风扇，不过也已经足够了。<img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250310223410001.png" alt="image-20250310223410001"></p><p>据说是出口转内销，不过我的这个是纯中文版本。成品长这样，和 MT3000 很像 。</p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250310224500782.png" alt="image-20250310224500782"></p><p>一下是刷了论坛上大分区的版本，如果想刷原版的 Openwrt，可以找客服要。</p><h3 id="1-接入路由器-WIFI-进入后台，"><a href="#1-接入路由器-WIFI-进入后台，" class="headerlink" title="1. 接入路由器 WIFI 进入后台，"></a>1. 接入路由器 WIFI 进入后台，</h3><p>地址是 192.168.1.1，这个是原厂固件，几乎没什么用</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201702683.png"></p><p>在这里刷入过渡包</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201735162.png"></p><p>重启之后，连接电脑和路由器 LAN 口，手动配置为静态 IP 地址, 路由器默认 IP 是 192.168.1.1.</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309204150368.png"></p><p>然后回出现登录页面，密码为空，直接登录</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201806360.png"></p><h3 id="2-升级过渡固件"><a href="#2-升级过渡固件" class="headerlink" title="2. 升级过渡固件"></a>2. 升级过渡固件</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201750765.png"></p><p>不保留当前配置</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201852610.png"></p><p>然后继续刷写</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201857968.png"></p><p>刷新之后自动跳转到这个页面, 登录进入到 openwrt 页面，密码 password</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309201928869.png"></p><h3 id="3-刷写-uboot-不死后台"><a href="#3-刷写-uboot-不死后台" class="headerlink" title="3. 刷写 uboot 不死后台"></a>3. 刷写 uboot 不死后台</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309202113910.png"></p><p>网页上传 uboot，打开 ttyd 刷入执行 mtd write &#x2F;tmp&#x2F;upload&#x2F;mt7981_cudy_tr3000-mod-u-boot.fip FIP</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309202122545.png"></p><p>电脑依旧更改静态 IP，长按 reset 按键，红灯闪烁即可松手进入 uboot 后台</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309202326061.png"></p><h3 id="4-选择固件进行更新，上传最后的固件之后，点击更新就可以了"><a href="#4-选择固件进行更新，上传最后的固件之后，点击更新就可以了" class="headerlink" title="4. 选择固件进行更新，上传最后的固件之后，点击更新就可以了"></a>4. 选择固件进行更新，上传最后的固件之后，点击更新就可以了</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309202338993.png"></p><p>这个是刷写之后的效果，可以看到 uboot mod</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/refs/heads/master/image-20250309202259037.png"></p><p>100 多元的小玩意，拿来做旁路由还可以，刷完之后发热也比原厂低了不少。</p>]]></content>
    
    
    <summary type="html">Cudy TR3000 路由器刷入 OpenWrt 固件的详细教程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="路由器" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
    
    <category term="路由器" scheme="https://blog.no-claw.com/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>如何使用 Iphone 拍摄 JPG 格式的照片</title>
    <link href="https://blog.no-claw.com/posts/4bc0fb74/"/>
    <id>https://blog.no-claw.com/posts/4bc0fb74/</id>
    <published>2025-04-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在写笔记的时候需要 Iphone 拍摄照片到 Macbook 上面，但是 Apple 默认的 HEIC 格式没办法在 Markdown 笔记以及图床上很好的进行渲染，这个时候要么截图，要么转码成 jpg&#x2F;png 的格式。</p> <span id="more"></span><p>设置里选择相机：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250223112832256.png" alt="image-20250223112832256"></p><p>选择格式：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250223112848652.png" alt="image-20250223112848652"></p><p>选择兼容性最佳：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250223112810618.png" alt="image-20250223112810618"></p><p>然后 AirDrop 到 MacBook 上面，两个格式的图片大小差不多。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250223112429881.png" alt="image-20250223112429881"></p><p>下一步就可以开心的把图片拖拽到笔记里了。</p>]]></content>
    
    
    <summary type="html">iPhone 默认拍 HEIC 格式，如何设置直接拍摄 JPG 格式照片。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="电脑外设" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%84%91%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>PXE 装机：iventoy 聊胜于无</title>
    <link href="https://blog.no-claw.com/posts/3ff07059/"/>
    <id>https://blog.no-claw.com/posts/3ff07059/</id>
    <published>2025-04-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>ventoy 是装机神器，iventoy 便是其网络版。只需将 ISO 文件放入指定目录，设置 DHCP 启动池，并启动服务即可。其操作简单，适合需要批量装机的场景。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2025/02/09/1739112219707-5aa8dd58-1b2b-4cc7-9291-f4c288f7cbd7.png"></p><p>这里的 DHCP 池是上级路由器的一小部分，一是为了防止 IP 冲突，而是免费版只能 20 个客户端</p> <span id="more"></span><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img10@main/2025/02/09/1739112541687-5e9ef8b8-546b-4df0-8c0c-99cb2beba8be.png"></p><p>首先要在 BIOS 上开启 PXE 启动，由于我的机器比较老，所以只能支持 legacy 的方式，启动之后是这样的：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/09/1739112388573-81f6b2d7-a3df-442f-8e8b-44132c5a68e5.png"></p><p>然后在启动项中选择网络启动：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2025/02/09/1739112431941-5ce37d98-0635-4b7c-836e-a9db9a632e7b.png"></p><p>客户端自动寻找 PXE 服务端：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/09/1739112378414-1082586f-a1b5-41cc-9d76-ee216aad8511.png"></p><p>然后就会自动跳出这个页面：<br><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/09/1739112510098-1d7bb325-53fa-42c3-bb42-6e792e92cac5.png"></p><p>在传输镜像的过程中并没有跑满 1G 内网，这个速度其实是不如大部分 U 盘的：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img4@main/2025/02/09/1739112140493-efef79b1-436a-4bff-a6ca-e6abf72e13ba.png"></p><p>尝试下来，聊胜于无。</p><ol><li>win11 的镜像没有加载出来，不知道是不是机器太老。</li><li>每次启动网启的提示要闪很多遍，没有用 U 盘装机干净利落。</li><li>走千兆网络速度不能跑慢，适合批量不急的时候装机。</li></ol><h2 id="王小波风格-by-deepseek"><a href="#王小波风格-by-deepseek" class="headerlink" title="王小波风格 by deepseek"></a>王小波风格 by deepseek</h2><h3 id="PXE-装机：iVentoy-这东西，聊胜于无吧"><a href="#PXE-装机：iVentoy-这东西，聊胜于无吧" class="headerlink" title="PXE 装机：iVentoy 这东西，聊胜于无吧"></a>PXE 装机：iVentoy 这东西，聊胜于无吧</h3><p>Ventoy 是个好东西，装机神器，iVentoy 就是它的网络版。这东西用起来倒也简单，扔几个 ISO 文件进去，设个 DHCP 启动池，启动服务，完事儿。听起来挺美好，可实际用起来嘛，总有点“理想很丰满，现实很骨感”的意思。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img9@main/2025/02/09/1739112219707-5aa8dd58-1b2b-4cc7-9291-f4c288f7cbd7.png" alt="DHCP 池设置"></p><p>DHCP 池是从上级路由器里划出来的一小块，一来怕 IP 冲突，二来免费版只支持 20 个客户端。这就像你请客吃饭，桌子太小，只能坐 20 个人，再多就得站着吃了。站着吃也行，但总归不太舒服。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/09/1739112388573-81f6b2d7-a3df-442f-8e8b-44132c5a68e5.png" alt="BIOS 启动界面"></p><p>我的机器比较老，BIOS 里只能开 Legacy 模式，启动后就是这么个界面。看着这老古董，我心里有点感慨：科技这东西，跑得飞快，可我的机器还停留在上个世纪。启动时得选网络启动，像极了在旧书店里翻一本发黄的书，翻来翻去，终于找到了想要的那一页。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2025/02/09/1739112431941-5ce37d98-0635-4b7c-836e-a9db9a632e7b.png" alt="网络启动"></p><p>客户端会自动找 PXE 服务端，像一只迷路的小狗，东闻闻西嗅嗅，终于找到了回家的路。然后，屏幕上跳出这么个页面：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/09/1739112378414-1082586f-a1b5-41cc-9d76-ee216aad8511.png" alt="PXE 服务端连接"></p><p>看起来挺顺利，可接下来的事情就不那么美好了。传输镜像时，速度没跑满 1G 内网，甚至还不如大部分 U 盘。这就像你开着一辆跑车，结果发现油门踩到底，速度还不如隔壁老王的自行车。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img4@main/2025/02/09/1739112140493-efef79b1-436a-4bff-a6ca-e6abf72e13ba.png" alt="镜像传输速度"></p><p>用下来，总结几点感受：</p><ol start="4"><li><p><strong>Windows 11 镜像没加载出来</strong>。可能是我的机器太老，老得连新系统都不愿意搭理它。这就像你去参加一个年轻人的派对，结果发现自己连门都进不去。</p></li><li><p><strong>每次启动网启的提示闪很多遍</strong>。这玩意儿像个唠叨的老太太，一遍又一遍地提醒你：“别忘了网络启动！”可我只想干净利落地装个系统，不想听它啰嗦。</p></li><li><p><strong>千兆网络速度跑不满</strong>。适合批量装机，但前提是你得有不急的耐心。这就像你去排队买票，队伍很长，但你又不赶时间，只好慢慢等。</p></li></ol><p>总的来说，iVentoy 这东西，聊胜于无吧。它有点像那种“能用，但别指望太好用”的工具。如果你有耐心，又不介意它的那些小毛病，那它还是能派上用场的。但如果你追求的是高效、稳定，那它可能还差点意思。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/09/1739112510098-1d7bb325-53fa-42c3-bb42-6e792e92cac5.png" alt="iVentoy 界面"></p><p>最后，我想说，科技这东西，总是让人又爱又恨。爱它的便利，恨它的不完美。可正是这些不完美，才让我们有了继续折腾的动力。折腾吧，朋友们，生活不就是一场永无止境的折腾吗？</p><h2 id="鲁迅风格-by-ChatGPT"><a href="#鲁迅风格-by-ChatGPT" class="headerlink" title="鲁迅风格 by ChatGPT"></a>鲁迅风格 by ChatGPT</h2><h3 id="PXE-装机：iventoy-也不过如此"><a href="#PXE-装机：iventoy-也不过如此" class="headerlink" title="PXE 装机：iventoy 也不过如此"></a>PXE 装机：iventoy 也不过如此</h3><p>Ventoy 是装机的利器，iventoy 便是它的网络版。把 ISO 文件丢进目录，设定 DHCP 启动池，开机，完事。操作简便，适合批量装机。</p><p>但凡事总有些许不尽如人意。</p><p>DHCP 池呢，不过是从上级路由器分出的一小块地方，一是怕 IP 冲突，二是免费版只能给二十个客户端，这点倒是想得周到。</p><p>BIOS 里得先开 PXE，我这机器年纪大了，只认得 legacy 的启动方式，没办法。好不容易启动了，见到个画面，心里稍安。</p><p>接着得在启动项里选网络启动，设备便开始四处张望，寻找 PXE 服务器。等它找着了，蹦出来个界面，算是进了正题。</p><p>然而这装机过程，谈不上利落。镜像传输时，千兆网络竟跑不满，速度竟不及寻常 U 盘。Win11 的镜像更是直接消失，或许是我这老机器力不从心。每次启动时，那网启提示闪来闪去，晃得人头晕，哪有 U 盘插上就干脆利索？</p><p>尝试一番，感慨一句：聊胜于无。</p>]]></content>
    
    
    <summary type="html">使用 iVentoy 搭建 PXE 网络装机环境的体验记录。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="装机" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%A3%85%E6%9C%BA/"/>
    
    
    <category term="电脑外设" scheme="https://blog.no-claw.com/tags/%E7%94%B5%E8%84%91%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>不建 Hugo、不用 Hexo，纯 Markdown 文件也能接入 Coco-AI！</title>
    <link href="https://blog.no-claw.com/posts/df454c34/"/>
    <id>https://blog.no-claw.com/posts/df454c34/</id>
    <published>2025-03-27T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前我们介绍过如何通过 Coco-AI 检索 Hugo 和 Hexo 的文件结构。这种方式虽然适合博客类内容，但对于一些<strong>零碎的笔记</strong>或者并非建站类的 Markdown 文件，显然不够灵活。</p><p>为了解决这个问题，我写了一个适配器（connector），并发布了对应的 Docker 镜像，来实现<strong>任意 Markdown 文件目录的元数据整理与 API 暴露</strong>：</p><span id="more"></span><p>👉 镜像地址：<a href="https://hub.docker.com/r/cloudsmithy/flask-markdown-connector">https://hub.docker.com/r/cloudsmithy/flask-markdown-connector</a></p><h2 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h2><p>该 connector 的主要逻辑是：</p><ol><li><strong>递归扫描目录及子目录下的 Markdown 文件</strong>；</li><li><strong>识别或补充元数据</strong>（即 YAML Front Matter）；</li><li><strong>通过 RESTful API 暴露这些 Markdown 的结构信息和元数据内容</strong>。</li></ol><p>如下图所示，我们会在每个 Markdown 文件开头添加或提取出 YAML 元数据：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328105358641.png" alt="元数据展示"></p><h2 id="一键部署"><a href="#一键部署" class="headerlink" title="一键部署"></a>一键部署</h2><p>使用以下命令即可快速拉起服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">docker run -d --name markdown-connector \</span><br><span class="line">  -p 1313:5000 \</span><br><span class="line">  -v <span class="string">&quot;<span class="subst">$(pwd)</span>:/app/markdown&quot;</span> \</span><br><span class="line">  --restart always \</span><br><span class="line">  cloudsmithy/flask-markdown-connector</span><br></pre></td></tr></table></figure><h3 id="参数说明："><a href="#参数说明：" class="headerlink" title="参数说明："></a>参数说明：</h3><ul><li><code>-d</code>：后台运行容器；</li><li><code>--name markdown-connector</code>：指定容器名称；</li><li><code>-p 1313:5000</code>：将宿主机的 1313 端口映射到容器的 5000 端口；</li><li><code>-v &quot;$(pwd):/app/markdown&quot;</code>：将当前目录挂载进容器；</li><li><code>--restart always</code>：配置容器异常退出后自动重启；</li><li><code>cloudsmithy/flask-markdown-connector</code>：镜像名称。</li></ul><h2 id="API-一览"><a href="#API-一览" class="headerlink" title="API 一览"></a>API 一览</h2><p>通过浏览器访问 <code>http://localhost:1313/apidocs</code> 即可打开内置 Swagger 文档。方便上层系统（如 Coco-AI）访问 Markdown 文件的元数据与内容。接口结构简洁直观，支持获取索引、刷新缓存、读取内容等功能。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328114039208.png" alt="Swagger 文档"></p><h3 id="🔹-GET"><a href="#🔹-GET" class="headerlink" title="🔹 GET /"></a>🔹 <code>GET /</code></h3><p><strong>功能</strong>：返回服务启动提示信息<br><strong>说明</strong>：用于检查服务是否正常运行<br><strong>示例响应</strong>：</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;Markdown Connector is running.&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="🔹-GET-api-posts"><a href="#🔹-GET-api-posts" class="headerlink" title="🔹 GET /api/posts"></a>🔹 <code>GET /api/posts</code></h3><p><strong>功能</strong>：获取生成的 <code>index.json</code>（缓存版）<br><strong>说明</strong>：返回 Markdown 文件的路径与元数据列表，从缓存中读取，响应速度快<br><strong>用途</strong>：Coco Server 可用作索引加载入口<br><strong>示例响应</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Docker 学习笔记&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dev/docker.md&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;docker&quot;</span><span class="punctuation">,</span> <span class="string">&quot;学习&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;created&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2024-11-22&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="punctuation">]</span></span><br></pre></td></tr></table></figure><h3 id="🔹-GET-index-json"><a href="#🔹-GET-index-json" class="headerlink" title="🔹 GET /index.json"></a>🔹 <code>GET /index.json</code></h3><p><strong>功能</strong>：实时渲染当前 Markdown 目录结构<br><strong>说明</strong>：等价于 <code>/api/posts</code>，但是实时读取文件而非使用缓存，适合调试或手动使用<br><strong>用途</strong>：确保数据为最新状态时使用</p><h3 id="🔹-GET-api-refresh"><a href="#🔹-GET-api-refresh" class="headerlink" title="🔹 GET /api/refresh"></a>🔹 <code>GET /api/refresh</code></h3><p><strong>功能</strong>：重新扫描并生成最新的 <code>index.json</code> 缓存<br><strong>说明</strong>：用于强制刷新目录索引，适用于新增 &#x2F; 修改了 Markdown 文件后<br><strong>响应示例</strong>：</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;Index cache refreshed.&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="🔹-GET-posts"><a href="#🔹-GET-posts" class="headerlink" title="🔹 GET /posts/"></a>🔹 <code>GET /posts/</code></h3><p><strong>功能</strong>：列出 Markdown 文件路径列表（不含元数据）<br><strong>说明</strong>：返回所有可访问的 Markdown 文件路径，用于构建下拉菜单、快速跳转<br><strong>响应示例</strong>：</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="string">&quot;notes/linux.md&quot;</span><span class="punctuation">,</span> <span class="string">&quot;dev/docker.md&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ideas/gpt-agent.md&quot;</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h3 id="🔹-GET-posts-filename"><a href="#🔹-GET-posts-filename" class="headerlink" title="🔹 GET /posts/{filename}"></a>🔹 <code>GET /posts/{filename}</code></h3><p><strong>功能</strong>：获取指定 Markdown 文件的内容<br><strong>参数</strong>：</p><ul><li><code>{filename}</code>：Markdown 文件路径（相对于挂载目录）</li></ul><p><strong>用途</strong>：用于前端点击跳转后展示具体笔记内容<br><strong>响应示例</strong>：</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;filename&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dev/docker.md&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;# Docker 学习笔记\n\n## 容器 vs 镜像 ...&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>用于 Coco Server，推荐使用 <code>/api/posts</code> 作为内容索引源；</li><li>使用 <code>/api/refresh</code> 后再拉一次 <code>/api/posts</code> 可确保内容最新；</li><li>若前端需要展示具体内容，可调用 <code>/posts/{filename}</code>；</li><li>若用在其他 AI 项目中，也可用于构建轻量级 Markdown 知识检索接口。</li></ul><h2 id="与-Coco-Server-集成"><a href="#与-Coco-Server-集成" class="headerlink" title="与 Coco Server 集成"></a>与 Coco Server 集成</h2><p>在 Orbstack 中，我们可以清晰地看到本地 Markdown 文件已经映射到容器中：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328110115287.png" alt="Orbstack 文件映射"></p><p>接着我们将 connector 的服务地址填入 Coco Server 配置中：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328114420941.png" alt="配置 Coco Server"></p><p>你可以使用 <code>/api/posts</code> 或 <code>/index.json</code> 接口，它们返回的内容基本一致：</p><ul><li><code>/api/posts</code> 是来自缓存的接口；</li><li><code>/index.json</code> 是实时渲染本地文件结构。</li></ul><p>另外，通过 <code>/api/refresh</code> 还可以手动触发缓存刷新：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328112740320.png" alt="刷新缓存"></p><h2 id="效果演示"><a href="#效果演示" class="headerlink" title="效果演示"></a>效果演示</h2><p>成功接入后，Coco Server 就可以读取 Markdown 文件的结构与元数据信息：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328112721540.png" alt="Coco 成功抓取"></p><p>点击条目还可以跳转到对应的网页：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328114249329.png" alt="跳转链接"></p><p>当然，和之前一样，也支持在搜索栏中直接搜索并跳转：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328114455959.png" alt="搜索跳转"></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过这个 Flask 版 Markdown Connector，我们可以将任意 Markdown 文件目录结构化暴露给 Coco-AI，实现：</p><ul><li>笔记内容统一索引</li><li>结构与元数据清晰可控</li><li>快速部署，无需 Hugo&#x2F;Hexo 建站</li></ul><p>这对于日常碎片化笔记管理来说，是一个非常轻量又灵活的解决方案。</p>]]></content>
    
    
    <summary type="html">无需静态站点框架，纯 Markdown 文件直接接入 Coco AI 检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Docker-Compose部署 EasySearch 异常问题排查</title>
    <link href="https://blog.no-claw.com/posts/719d7034/"/>
    <id>https://blog.no-claw.com/posts/719d7034/</id>
    <published>2025-03-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>近期将原本运行在 macOS 上的 EasySearch、Console 和 Coco-server 等服务迁移至群晖 NAS 平台。在迁移过程中遇到了 EasySearch 容器无法正常启动或运行中意外终止的问题。本文记录了这些问题的具体表现及解决方案，旨在为后续类似部署提供参考。</p><h2 id="基础部署配置"><a href="#基础部署配置" class="headerlink" title="基础部署配置"></a>基础部署配置</h2><p>以下是官方推荐的 docker-compose 配置文件：<br>地址如下：</p><span id="more"></span><p><a href="https://docs.infinilabs.com/easysearch/main/docs/getting-started/install/docker-compose/">https://docs.infinilabs.com/easysearch/main/docs/getting-started/install/docker-compose/</a></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">easysearch-node1:</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">&quot;602:602&quot;</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/easysearch:1.11.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">easysearch-node1</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">easysearch-node1</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">      <span class="attr">nofile:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">65536</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">65536</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/config:/app/easysearch/config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/data:/app/easysearch/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs1/logs:/app/easysearch/logs</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9201</span><span class="string">:9200</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9301</span><span class="string">:9300</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">easysearch-node2:</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">&quot;602:602&quot;</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/easysearch:1.11.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">easysearch-node2</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">easysearch-node2</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">      <span class="attr">nofile:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">65536</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">65536</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/config:/app/easysearch/config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/data:/app/easysearch/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/ezs2/logs:/app/easysearch/logs</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9202</span><span class="string">:9200</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9302</span><span class="string">:9300</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">console:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/console:1.29.1-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">console</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">console</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/console/data:/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$PWD/console/log:/log</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">esnet</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9000</span><span class="string">:9000</span></span><br><span class="line">    <span class="attr">links:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">easysearch-node1:es1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">easysearch-node2:es2</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">esnet:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br><span class="line">    <span class="attr">ipam:</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">subnet:</span> <span class="number">172.24</span><span class="number">.0</span><span class="number">.0</span><span class="string">/16</span></span><br></pre></td></tr></table></figure><h2 id="常见问题及解决方案"><a href="#常见问题及解决方案" class="headerlink" title="常见问题及解决方案"></a>常见问题及解决方案</h2><h3 id="问题-1：vm-max-map-count-参数不足"><a href="#问题-1：vm-max-map-count-参数不足" class="headerlink" title="问题 1：vm.max_map_count 参数不足"></a>问题 1：vm.max_map_count 参数不足</h3><p>错误提示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]</span><br></pre></td></tr></table></figure><h4 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h4><p>Lucene 搜索引擎在运行过程中需要创建大量内存映射文件(mmap)，而 Linux 系统默认的虚拟内存区域数量限制(65530)无法满足需求。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p><strong>临时设置（重启失效）</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sysctl -w vm.max_map_count=262144</span><br></pre></td></tr></table></figure><p><strong>永久生效配置</strong>：</p><ol><li>编辑 <code>/etc/sysctl.conf</code> 文件</li><li>添加配置项：<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">vm.max_map_count=262144</span><br></pre></td></tr></table></figure></li><li>应用配置：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sysctl -p</span><br></pre></td></tr></table></figure></li></ol><h3 id="问题-2：Java-堆内存溢出"><a href="#问题-2：Java-堆内存溢出" class="headerlink" title="问题 2：Java 堆内存溢出"></a>问题 2：Java 堆内存溢出</h3><p>错误信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Java heap space</span><br></pre></td></tr></table></figure><h4 id="问题分析-1"><a href="#问题分析-1" class="headerlink" title="问题分析"></a>问题分析</h4><p>容器化环境中，JVM 默认的内存分配策略往往无法正确识别可用的系统资源，导致：</p><ol><li>堆内存分配不足（默认通常仅 512MB-1GB）</li><li>内存使用超出容器限制后被系统强制终止</li></ol><h4 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h4><p>修改 docker-compose 配置，明确指定 JVM 堆内存：</p><figure class="highlight yaml"><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="attr">environment:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">ES_JAVA_OPTS=-Xms2g</span> <span class="string">-Xmx2g</span></span><br></pre></td></tr></table></figure><p>配置建议：</p><ol><li>堆内存设置为物理内存的 50%以内</li><li>最大堆内存不超过 32GB（避免指针压缩失效）</li><li>初始堆(-Xms)和最大堆(-Xmx)设为相同值，避免运行时动态调整</li></ol><p>这次是部署过程中踩的两个坑，写出来让大家避避雷。</p>]]></content>
    
    
    <summary type="html">排查 Docker Compose 部署 Easysearch 时遇到的常见异常问题</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>拓展 Coco AI 功能 - 智能检索 Hexo 博客</title>
    <link href="https://blog.no-claw.com/posts/9eab63c2/"/>
    <id>https://blog.no-claw.com/posts/9eab63c2/</id>
    <published>2025-03-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的文章中，我们成功让 <strong>Coco AI</strong> 检索 <strong>Hugo 博客</strong>，这对于博客作者来说是一大福音。然而，从 <strong>Hexo</strong> 迁移到 <strong>Hugo</strong> 的成本不容小觑，毕竟大多数开发者对 <strong>Node.js</strong> 更熟悉，而 <strong>Golang</strong> 相对陌生。那么，既然 Coco AI 官方尚未支持 Hexo，是否有办法让它兼容 Hexo 呢？</p><p>当然可以！💡 既然 <strong>Coco AI</strong> 依赖的是 <strong>Hugo 生成的 <code>index.json</code></strong> 进行检索，那我们干脆在 <strong>Hexo</strong> 中实现 **相同结构的 <code>index.json</code>**，这样就可以直接复用 Hugo 的数据结构，避免字段不兼容导致的潜在 Bug。</p><p><strong>接下来，我们将从 0 到 1 实现 Hexo 的智能检索功能！</strong> 🚀</p><h2 id="📌-1-安装-Hexo-并切换到-Next-主题"><a href="#📌-1-安装-Hexo-并切换到-Next-主题" class="headerlink" title="📌 1. 安装 Hexo 并切换到 Next 主题"></a><strong>📌 1. 安装 Hexo 并切换到 Next 主题</strong></h2><p>首先，我们需要安装 <strong>Hexo</strong> 并设置 <strong>Next 主题</strong>。</p><h3 id="安装-Hexo"><a href="#安装-Hexo" class="headerlink" title="安装 Hexo"></a><strong>安装 Hexo</strong></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></pre></td><td class="code"><pre><span class="line">pnpm install -g hexo-cli</span><br><span class="line">hexo init my-blog</span><br><span class="line"><span class="built_in">cd</span> my-blog</span><br><span class="line">pnpm install</span><br></pre></td></tr></table></figure><span id="more"></span><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">pnpm hexo s</span><br></pre></td></tr></table></figure><p>访问 <code>http://localhost:4000/</code>，确保 Hexo 站点运行正常。</p><h3 id="安装-Next-主题"><a href="#安装-Next-主题" class="headerlink" title="安装 Next 主题"></a><strong>安装 Next 主题</strong></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">pnpm add hexo-theme-next</span><br></pre></td></tr></table></figure><p>修改 <code>_config.yml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">theme:</span> <span class="string">next</span></span><br></pre></td></tr></table></figure><p>然后运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm hexo clean &amp;&amp; pnpm hexo s</span><br></pre></td></tr></table></figure><p>访问 <code>http://localhost:4000/</code>，确认 Next 主题已生效。</p><h2 id="📌-2-安装-hexo-generator-json-content"><a href="#📌-2-安装-hexo-generator-json-content" class="headerlink" title="📌 2. 安装 hexo-generator-json-content"></a><strong>📌 2. 安装 <code>hexo-generator-json-content</code></strong></h2><p>我们需要安装 <strong>JSON 生成插件</strong>，用于输出博客文章数据：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add hexo-generator-json-content</span><br></pre></td></tr></table></figure><p>这些添加到 <code>_config.yml</code>，确保 Hexo 生成完整的 JSON 数据：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">jsonContent:</span></span><br><span class="line">  <span class="attr">meta:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">pages:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">posts:</span></span><br><span class="line">    <span class="attr">title:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">date:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">path:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">permalink:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">excerpt:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">content:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">categories:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">tags:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm hexo clean &amp;&amp; pnpm hexo generate</span><br></pre></td></tr></table></figure><p>然后检查 <code>public/index.json</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> public/index.json</span><br></pre></td></tr></table></figure><p>此时 JSON 已经生成，但 <code>url</code> 不是 <strong>Hugo</strong> 风格的，我们需要进一步优化。</p><h2 id="📌-3-自定义-index-json"><a href="#📌-3-自定义-index-json" class="headerlink" title="**📌 3. 自定义 index.json **"></a>**📌 3. 自定义 <code>index.json</code> **</h2><p>默认情况下，<strong>Hexo 不会生成 <code>/YYYY/MM/DD/slug/</code> 格式的 URL</strong>，因此我们需要手动调整。</p><h3 id="📌-创建-scripts-generate-index-json-js"><a href="#📌-创建-scripts-generate-index-json-js" class="headerlink" title="📌 创建 scripts/generate_index_json.js"></a><strong>📌 创建 <code>scripts/generate_index_json.js</code></strong></h3><p>在 <strong>Hexo 站点目录</strong> 下，创建 <code>scripts/generate_index_json.js</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">hexo.<span class="property">extend</span>.<span class="property">generator</span>.<span class="title function_">register</span>(<span class="string">&quot;index_json&quot;</span>, <span class="keyword">function</span> (<span class="params">locals</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> posts = locals.<span class="property">posts</span>.<span class="title function_">sort</span>(<span class="string">&quot;-date&quot;</span>).<span class="title function_">map</span>(<span class="function">(<span class="params">post</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> category =</span><br><span class="line">      post.<span class="property">categories</span> &amp;&amp; post.<span class="property">categories</span>.<span class="property">length</span> &gt; <span class="number">0</span></span><br><span class="line">        ? post.<span class="property">categories</span>.<span class="property">data</span>[<span class="number">0</span>].<span class="property">name</span></span><br><span class="line">        : <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">let</span> subcategory =</span><br><span class="line">      post.<span class="property">categories</span> &amp;&amp; post.<span class="property">categories</span>.<span class="property">length</span> &gt; <span class="number">1</span></span><br><span class="line">        ? post.<span class="property">categories</span>.<span class="property">data</span>[<span class="number">1</span>].<span class="property">name</span></span><br><span class="line">        : <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">let</span> tags = post.<span class="property">tags</span> ? post.<span class="property">tags</span>.<span class="title function_">map</span>(<span class="function">(<span class="params">tag</span>) =&gt;</span> tag.<span class="property">name</span>) : <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 解析发布日期</span></span><br><span class="line">    <span class="keyword">let</span> date = post.<span class="property">date</span>;</span><br><span class="line">    <span class="keyword">let</span> formattedDate = <span class="string">`<span class="subst">$&#123;date.year()&#125;</span>/<span class="subst">$&#123;<span class="built_in">String</span>(date.month() + <span class="number">1</span>).padStart(</span></span></span><br><span class="line"><span class="subst"><span class="string">      <span class="number">2</span>,</span></span></span><br><span class="line"><span class="subst"><span class="string">      <span class="string">&quot;0&quot;</span></span></span></span><br><span class="line"><span class="subst"><span class="string">    )&#125;</span>/<span class="subst">$&#123;<span class="built_in">String</span>(date.date()).padStart(<span class="number">2</span>, <span class="string">&quot;0&quot;</span>)&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生成 Hugo 风格 URL: `/YYYY/MM/DD/slug/`</span></span><br><span class="line">    <span class="keyword">let</span> postUrl = <span class="string">`/<span class="subst">$&#123;formattedDate&#125;</span>/<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">      post.slug || post.title.replace(/\s+/g, <span class="string">&quot;-&quot;</span>).toLowerCase()</span></span></span><br><span class="line"><span class="subst"><span class="string">    &#125;</span>/`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">category</span>: category,</span><br><span class="line">      <span class="attr">subcategory</span>: subcategory,</span><br><span class="line">      <span class="attr">content</span>: post.<span class="property">content</span>.<span class="title function_">replace</span>(<span class="regexp">/(&lt;([^&gt;]+)&gt;)/gi</span>, <span class="string">&quot;&quot;</span>), <span class="comment">// 去除 HTML 标签</span></span><br><span class="line">      <span class="attr">created</span>: post.<span class="property">date</span>.<span class="title function_">toISOString</span>(),</span><br><span class="line">      <span class="attr">updated</span>: post.<span class="property">updated</span></span><br><span class="line">        ? post.<span class="property">updated</span>.<span class="title function_">toISOString</span>()</span><br><span class="line">        : post.<span class="property">date</span>.<span class="title function_">toISOString</span>(),</span><br><span class="line">      <span class="attr">lang</span>: <span class="string">&quot;en&quot;</span>, <span class="comment">// 你可以修改为动态语言检测</span></span><br><span class="line">      <span class="attr">summary</span>: post.<span class="property">excerpt</span> || post.<span class="property">content</span>.<span class="title function_">substring</span>(<span class="number">0</span>, <span class="number">150</span>) + <span class="string">&quot;...&quot;</span>,</span><br><span class="line">      <span class="attr">tags</span>: tags,</span><br><span class="line">      <span class="attr">title</span>: post.<span class="property">title</span>,</span><br><span class="line">      <span class="attr">url</span>: postUrl, <span class="comment">// 确保符合 Hugo 格式</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="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;index.json&quot;</span>,</span><br><span class="line">    <span class="attr">data</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(posts, <span class="literal">null</span>, <span class="number">2</span>),</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr><h2 id="📌-4-重新生成-index-json"><a href="#📌-4-重新生成-index-json" class="headerlink" title="📌 4. 重新生成 index.json"></a><strong>📌 4. 重新生成 <code>index.json</code></strong></h2><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">pnpm hexo clean &amp;&amp; pnpm hexo generate</span><br></pre></td></tr></table></figure><p>然后检查 <code>public/index.json</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> public/index.json</span><br></pre></td></tr></table></figure><p>你应该会看到 JSON 变成：</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Technology&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;subcategory&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Web Development&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This is a test post.&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;created&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-03-20T12:00:00+08:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-03-20T12:00:00+08:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;en&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;summary&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This is a test post.&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;Hexo&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Static Site&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hello World&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/2025/03/20/hello-world/&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>✅ <strong>成功让 URL 变成 <code>/YYYY/MM/DD/slug/</code> 格式！</strong></p><p><img src="https://i-blog.csdnimg.cn/img_convert/015ef21db615e6ffa6bb9f76fe5fb526.png" alt="image-20250320213141804"></p><h2 id="📌-5-让-Coco-AI-识别-Hexo-博客"><a href="#📌-5-让-Coco-AI-识别-Hexo-博客" class="headerlink" title="📌 5. 让 Coco AI 识别 Hexo 博客"></a><strong>📌 5. 让 Coco AI 识别 Hexo 博客</strong></h2><p>既然 <code>index.json</code> 已经生成，我们可以像 <strong>Hugo</strong> 那样，在 <strong>Coco AI</strong> 里添加 Hexo 博客的检索。</p><p>在 <strong>Coco AI</strong> 里，点击 <strong>添加 Hugo Site</strong>，然后输入：</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">http://host.docker.internal:4000/index.json</span><br></pre></td></tr></table></figure><p>如果想测试数据同步，我们可以修改同步时间为 <strong>1 秒</strong>，以便实时观察更新情况。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/19bf0e76116c75fbd221da3a0a730c0b.png" alt="image-20250320213908270"></p><h2 id="📌-6-观察数据同步情况"><a href="#📌-6-观察数据同步情况" class="headerlink" title="📌 6. 观察数据同步情况"></a><strong>📌 6. 观察数据同步情况</strong></h2><p>过了一会，我们可以在 <strong>Coco AI</strong> 界面看到博客数据已经同步，<strong>但前提是需要先添加模型！</strong> ✅</p><p>Coco AI 的 <strong>KNN（近邻搜索）</strong> 会按照相关性对内容进行智能排序，使检索更高效！</p><p><img src="https://i-blog.csdnimg.cn/img_convert/ab6eb846f17b9c9a642a87dd387fbbc4.png" alt="image-20250320214256274"></p><h2 id="🎯-总结"><a href="#🎯-总结" class="headerlink" title="🎯 总结"></a><strong>🎯 总结</strong></h2><table><thead><tr><th><strong>步骤</strong></th><th><strong>命令</strong></th></tr></thead><tbody><tr><td><strong>安装 Hexo 并切换到 Next 主题</strong></td><td><code>pnpm install -g hexo-cli &amp;&amp; hexo init my-blog</code></td></tr><tr><td><strong>安装 <code>hexo-generator-json-content</code></strong></td><td><code>pnpm add hexo-generator-json-content</code></td></tr><tr><td><strong>修改 <code>_config.yml</code></strong></td><td>让 Hexo 生成 <code>index.json</code></td></tr><tr><td><strong>创建 <code>scripts/generate_index_json.js</code></strong></td><td>确保 URL 变成 Hugo 风格</td></tr><tr><td><strong>生成 JSON</strong></td><td><code>pnpm hexo clean &amp;&amp; pnpm hexo generate</code></td></tr><tr><td><strong>在 Coco AI 里添加 Hexo 站点</strong></td><td>输入 <code>http://host.docker.internal:4000/index.json</code></td></tr></tbody></table><h2 id="🚀-结论"><a href="#🚀-结论" class="headerlink" title="🚀 结论"></a><strong>🚀 结论</strong></h2><p>🎉 通过本教程，你已经成功：</p><p>✅ <strong>让 Coco AI 兼容 Hexo 博客，实现智能检索</strong><br>✅ <strong>复用 Hugo 的 <code>index.json</code> 结构，避免迁移成本</strong><br>✅ <strong>让 URL 变成 <code>/YYYY/MM/DD/slug/</code> 以适配 Hugo Connector</strong><br>✅ <strong>在 Coco AI 里成功同步 Hexo 博客数据，并进行智能查询</strong></p><p>💡 现在，你可以愉快地使用 <strong>Hexo + Coco AI</strong> 进行智能检索了！如果有 <strong>更多定制需求</strong>（如 <code>author</code>、<code>word count</code>），可以继续优化 <code>generate_index_json.js</code>！🔥🚀</p>]]></content>
    
    
    <summary type="html">拓展 Coco AI 功能，实现 Hexo 博客内容的智能检索</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco AI 智能检索 Hugo Blog 集成指南</title>
    <link href="https://blog.no-claw.com/posts/afa68182/"/>
    <id>https://blog.no-claw.com/posts/afa68182/</id>
    <published>2025-03-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在此前的文章中，我们介绍了如何使用 <strong>Coco Server</strong> 连接 <strong>Notion</strong>，实现智能内容检索。本次，我们将进一步探索如何在 <strong>Coco Server 最新版本</strong> 中集成 <strong>Hugo Site</strong>，以便对 <strong>Hugo 站点</strong> 进行高效检索。</p><hr><h2 id="Coco-Server-部署方式"><a href="#Coco-Server-部署方式" class="headerlink" title="Coco Server 部署方式"></a>Coco Server 部署方式</h2><p>要在本地或服务器上运行 <strong>Coco Server</strong>，可以借助 <strong>Docker</strong> 进行快速部署。</p><h3 id="1-直接运行-Coco-Server（默认配置）"><a href="#1-直接运行-Coco-Server（默认配置）" class="headerlink" title="1. 直接运行 Coco Server（默认配置）"></a>1. 直接运行 Coco Server（默认配置）</h3><p>执行以下命令，快速启动 <strong>Coco Server</strong>（版本 <code>0.2.2-2000</code>）：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name cocoserver -p 9000:9000 infinilabs/coco:0.2.2-2000</span><br></pre></td></tr></table></figure><p>此命令将在后台运行 <strong>Coco Server</strong>，并将 <strong>9000 端口</strong> 映射到本机，以便通过 Web UI 进行访问。</p><span id="more"></span><h3 id="2-启用数据持久化（推荐）"><a href="#2-启用数据持久化（推荐）" class="headerlink" title="2. 启用数据持久化（推荐）"></a>2. 启用数据持久化（推荐）</h3><p>如果希望数据在容器重启或删除后仍然保留，建议启用 <strong>数据持久化</strong>，操作步骤如下：</p><h4 id="（1）创建数据目录并设置权限"><a href="#（1）创建数据目录并设置权限" class="headerlink" title="（1）创建数据目录并设置权限"></a>（1）创建数据目录并设置权限</h4><figure class="highlight sh"><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="built_in">mkdir</span> -p $(<span class="built_in">pwd</span>)/cocoserver/&#123;data,logs&#125;</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> -R 602:602 $(<span class="built_in">pwd</span>)/cocoserver</span><br></pre></td></tr></table></figure><h4 id="（2）启动-Coco-Server-并挂载数据目录"><a href="#（2）启动-Coco-Server-并挂载数据目录" class="headerlink" title="（2）启动 Coco Server 并挂载数据目录"></a>（2）启动 Coco Server 并挂载数据目录</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">docker run -d \</span><br><span class="line">           --name cocoserver \</span><br><span class="line">           --hostname coco-server \</span><br><span class="line">           --restart unless-stopped \</span><br><span class="line">           -m 4g \</span><br><span class="line">           --cpus=<span class="string">&quot;2&quot;</span> \</span><br><span class="line">           -p 9000:9000 \</span><br><span class="line">           -v $(<span class="built_in">pwd</span>)/cocoserver/data:/app/easysearch/data \</span><br><span class="line">           -v $(<span class="built_in">pwd</span>)/cocoserver/logs:/app/easysearch/logs \</span><br><span class="line">           -e EASYSEARCH_INITIAL_ADMIN_PASSWORD=coco-server \</span><br><span class="line">           -e ES_JAVA_OPTS=<span class="string">&quot;-Xms2g -Xmx2g&quot;</span> \</span><br><span class="line">           infinilabs/coco:0.2.2-2000</span><br></pre></td></tr></table></figure><p>这样，所有 <strong>检索数据</strong> 和 <strong>日志信息</strong> 都会存储在 <code>./cocoserver/data</code> 和 <code>./cocoserver/logs</code> 目录下，即使容器重启，数据仍然可用。</p><hr><h3 id="3-使用-Docker-Compose-部署"><a href="#3-使用-Docker-Compose-部署" class="headerlink" title="3. 使用 Docker Compose 部署"></a>3. 使用 Docker Compose 部署</h3><p>如果你希望使用 <code>docker-compose</code> 进行更便捷的管理，可以采用以下 <code>docker-compose.yml</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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">cocoserver:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">infinilabs/coco:0.2.2-2000</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">cocoserver</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">coco-server</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9000:9000&quot;</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">        <span class="attr">limits:</span></span><br><span class="line">          <span class="attr">memory:</span> <span class="string">4g</span></span><br><span class="line">          <span class="attr">cpus:</span> <span class="string">&quot;2&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./cocoserver/data:/app/easysearch/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./cocoserver/logs:/app/easysearch/logs</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">EASYSEARCH_INITIAL_ADMIN_PASSWORD:</span> <span class="string">&quot;coco-server&quot;</span></span><br><span class="line">      <span class="attr">ES_JAVA_OPTS:</span> <span class="string">&quot;-Xms2g -Xmx2g&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">  <span class="attr">logs:</span></span><br></pre></td></tr></table></figure><p>运行以下命令启动 <strong>Coco Server</strong>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p>如需停止并删除容器，可执行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose down</span><br></pre></td></tr></table></figure><p>如果需要同时删除存储数据，则执行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose down -v</span><br></pre></td></tr></table></figure><hr><h2 id="Hugo-站点智能检索"><a href="#Hugo-站点智能检索" class="headerlink" title="Hugo 站点智能检索"></a>Hugo 站点智能检索</h2><p>成功部署 <strong>Coco Server</strong> 后，即可将 <strong>Hugo 站点</strong> 作为数据源进行智能检索。</p><h3 id="1-添加-Hugo-Site-数据源"><a href="#1-添加-Hugo-Site-数据源" class="headerlink" title="1. 添加 Hugo Site 数据源"></a>1. 添加 Hugo Site 数据源</h3><p>在 <strong>Coco Server Web UI</strong> 中，进入 <strong>“数据源”</strong> 页面，并点击 <strong>“添加 Hugo Site”</strong>。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/82c67e8c341169346e06cda4e94552ca.png" alt="添加 Hugo 站点数据源"></p><hr><h3 id="2-设定-Hugo-站点-URL"><a href="#2-设定-Hugo-站点-URL" class="headerlink" title="2. 设定 Hugo 站点 URL"></a>2. 设定 Hugo 站点 URL</h3><p>以 <strong>Pizza 官网</strong> 为示例，在输入框中填入相应的 <strong>URL</strong> 并保存。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/e5bb20df263a54a350929196f299b5cb.png" alt="输入 Hugo 站点 URL"></p><p>配置完成后，<strong>Coco Server</strong> 会自动抓取 Hugo 站点的内容并进行索引。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c78da60d0e32b97ba58270d95950f2e3.png" alt="成功索引 Hugo 站点内容"></p><hr><h2 id="构建自己的-Hugo-站点，并让-Coco-进行检索"><a href="#构建自己的-Hugo-站点，并让-Coco-进行检索" class="headerlink" title="构建自己的 Hugo 站点，并让 Coco 进行检索"></a>构建自己的 Hugo 站点，并让 Coco 进行检索</h2><p>完成 Pizza 官网的 Hugo 站点检索测试后，我们可以创建 <strong>自己的 Hugo 站点</strong>，并让 <strong>Coco Server</strong> 进行智能索引。</p><h3 id="1-安装-Hugo"><a href="#1-安装-Hugo" class="headerlink" title="1. 安装 Hugo"></a>1. 安装 Hugo</h3><h4 id="Mac"><a href="#Mac" class="headerlink" title="Mac"></a>Mac</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install hugo</span><br></pre></td></tr></table></figure><h4 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop install hugo-extended</span><br></pre></td></tr></table></figure><h4 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install hugo</span><br></pre></td></tr></table></figure><p>安装完成后，可运行 <code>hugo version</code> 确认是否安装成功。</p><hr><h3 id="2-创建-Hugo-站点"><a href="#2-创建-Hugo-站点" class="headerlink" title="2. 创建 Hugo 站点"></a>2. 创建 Hugo 站点</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><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">hugo new site my-hugo-site</span><br><span class="line"><span class="built_in">cd</span> my-hugo-site</span><br><span class="line">git init</span><br><span class="line">git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke</span><br></pre></td></tr></table></figure><hr><h3 id="3-配置-Hugo-以生成-JSON-数据"><a href="#3-配置-Hugo-以生成-JSON-数据" class="headerlink" title="3. 配置 Hugo 以生成 JSON 数据"></a>3. 配置 Hugo 以生成 JSON 数据</h3><p>在 <code>hugo.toml</code> 文件中，添加 <strong>JSON 输出</strong> 配置：</p><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">baseURL</span> = <span class="string">&quot;https://example.com/&quot;</span></span><br><span class="line"><span class="attr">languageCode</span> = <span class="string">&quot;en-us&quot;</span></span><br><span class="line"><span class="attr">title</span> = <span class="string">&quot;My Hugo Site&quot;</span></span><br><span class="line"><span class="attr">theme</span> = <span class="string">&quot;ananke&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[outputs]</span></span><br><span class="line">  <span class="attr">home</span> = [<span class="string">&quot;HTML&quot;</span>, <span class="string">&quot;JSON&quot;</span>]</span><br><span class="line">  <span class="attr">section</span> = [<span class="string">&quot;HTML&quot;</span>, <span class="string">&quot;JSON&quot;</span>]</span><br><span class="line">  <span class="attr">taxonomy</span> = [<span class="string">&quot;HTML&quot;</span>, <span class="string">&quot;JSON&quot;</span>]</span><br><span class="line">  <span class="attr">term</span> = [<span class="string">&quot;HTML&quot;</span>, <span class="string">&quot;JSON&quot;</span>]</span><br><span class="line"></span><br></pre></td></tr></table></figure><hr><h3 id="4-创建-JSON-模板"><a href="#4-创建-JSON-模板" class="headerlink" title="4. 创建 JSON 模板"></a>4. 创建 JSON 模板</h3><p>在 <code>layouts/_default/list.json</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span>- range $index<span class="punctuation">,</span> $element <span class="punctuation">:</span>= .Site.RegularPages -<span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span>- if gt $index <span class="number">0</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> end <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Params.category | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Content | plainify | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;created&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Date | time.Format <span class="string">&quot;2006-01-02T15:04:05Z07:00&quot;</span> | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Lang | default <span class="string">&quot;en&quot;</span> | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;subcategory&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Params.subcategory | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Params.summary | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Params.tags | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Title | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;updated&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.Lastmod | time.Format <span class="string">&quot;2006-01-02T15:04:05Z07:00&quot;</span> | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span> $element.RelPermalink | jsonify <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#123;</span><span class="punctuation">&#123;</span>- end -<span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><hr><h3 id="5-运行-Hugo-并生成-JSON"><a href="#5-运行-Hugo-并生成-JSON" class="headerlink" title="5. 运行 Hugo 并生成 JSON"></a>5. 运行 Hugo 并生成 JSON</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hugo server -D</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">http://localhost:1313/index.json</span><br></pre></td></tr></table></figure><p>你将看到 Hugo 站点生成的 JSON 数据，例如：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;My First Post&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This is a test post...&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/posts/my-first-post/&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/3f91d9420f0c9c8ce2395a47cfb89601.png" alt="image-20250318120453955"></p><hr><h2 id="6-让-Coco-Server-索引-Hugo-JSON-API"><a href="#6-让-Coco-Server-索引-Hugo-JSON-API" class="headerlink" title="6. 让 Coco Server 索引 Hugo JSON API"></a>6. 让 Coco Server 索引 Hugo JSON API</h2><p>在 <strong>Coco Server 数据源管理</strong> 中，输入 Hugo 站点 JSON API 地址，例如：</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">http://host.docker.internal:1313/index.json</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/img_convert/ba0620305587b2c87ae3fd1dce769345.png" alt="配置 Hugo JSON API"></p><p>如果 <strong>Coco Server</strong> 运行在 <strong>Docker</strong> 内，而 <strong>Hugo 站点</strong> 运行在本机，则 <code>localhost</code> 访问可能会失效，此时应使用 <code>host.docker.internal</code> 访问宿主机。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/3a9e17be2e3ae3dc612ebd64dfbf482e.png" alt="Docker 访问 Hugo JSON"></p><p>添加成功后，<strong>Coco Server</strong> 会自动抓取并解析 Hugo 站点数据，实现智能检索。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/25b7660a162395c7079cfdce04d7e57c.png" alt="Hugo 站点智能检索"></p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本指南，我们已成功完成：</p><p>✅ 部署 <strong>Coco Server</strong>（支持 Docker &#x2F; Docker Compose）<br>✅ 添加 <strong>Hugo Site</strong> 作为数据源<br>✅ 配置 Hugo 站点，生成 JSON API<br>✅ 让 <strong>Coco Server</strong> 索引 Hugo 站点，实现智能检索</p><p>现在，我们可以使用 <strong>Coco AI</strong> 高效检索 Hugo 站点内容，大幅提升信息查找效率！🚀</p>]]></content>
    
    
    <summary type="html">将 Hugo 博客集成到 Coco AI 实现智能内容检索的配置指南</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>CoCo AI APP 初体验：开启智能知识管理新篇章</title>
    <link href="https://blog.no-claw.com/posts/eb0b4c83/"/>
    <id>https://blog.no-claw.com/posts/eb0b4c83/</id>
    <published>2025-03-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>近日，极限科技的 Coco AI 正式发布。作为一款完全开源、跨平台的统一 AI 搜索与效率工具，Coco AI 能够无缝连接并搜索多种数据源，包括本地应用程序、文件以及云端平台如 Google Drive、Notion、语雀和 Hugo 等。通过集成 DeepSeek 等先进的大模型，Coco AI 不仅实现了智能化的个人知识库管理，还特别注重用户隐私，支持私有部署，帮助用户更快速、更智能地访问和管理信息。</p><h3 id="Coco-AI"><a href="#Coco-AI" class="headerlink" title="Coco AI"></a>Coco AI</h3><p>本次发布的是 Coco AI 的首个预览版本，目前支持 MacOS 12 及以上操作系统。无论你是知识管理爱好者还是效率工具控，Coco AI 都值得一试。</p><ul><li><strong>项目主页</strong>: <a href="https://coco.rs/">https://coco.rs/</a></li><li><strong>开源地址</strong>:<ul><li>桌面应用端: <a href="https://github.com/infinilabs/coco-app/">https://github.com/infinilabs/coco-app/</a></li><li>服务端: <a href="https://github.com/infinilabs/coco-server">https://github.com/infinilabs/coco-server</a></li></ul></li></ul><p>Coco AI 分为客户端（APP）和服务端（Server），目前客户端仅提供 MacOS 版本，而服务端则支持 MacOS 和 Linux。今天，我们将重点体验客户端的使用。</p><h3 id="下载与安装"><a href="#下载与安装" class="headerlink" title="下载与安装"></a>下载与安装</h3><p>首先，从项目主页或 GitHub 仓库下载 Coco AI 的安装包。安装过程简单直观，只需几步即可完成。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/f0c9a3a9ab65f527efaa707dcb499947.png" alt="下载安装"></p><span id="more"></span><p>安装完成后，启动 Coco AI，你会在导航栏中看到它的图标。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c77fb9e6f4d50764862006a7158589d2.png" alt="启动界面"></p><h3 id="初始设置"><a href="#初始设置" class="headerlink" title="初始设置"></a>初始设置</h3><p>在设置页面，你可以根据个人喜好进行一些基本配置，如设置自动启动、自定义搜索栏热键、以及是否跟随系统主题等。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/5cf474bed33108ba1c1c604ea4d3669f.png" alt="设置页面"></p><h3 id="登录与连接"><a href="#登录与连接" class="headerlink" title="登录与连接"></a>登录与连接</h3><p>默认情况下，Coco AI 会连接到 Coco Cloud。你可以选择登录以解锁更多功能。目前，Coco AI 支持通过 GitHub 和 Google 进行单点登录（SSO）。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/e0cb00c46fa1aeb3fbe2fee5b871861a.png" alt="登录界面"></p><p>登录成功后，系统会自动重定向到 Coco AI 的主界面。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/33d5b1b77fab0b6ef323e71c4c939eff.png" alt="登录后界面"></p><h3 id="语料库管理"><a href="#语料库管理" class="headerlink" title="语料库管理"></a>语料库管理</h3><p>登录后，Coco AI 默认会添加极限科技官方及相关衍生产品作为初始语料库。如果你有自己的服务器，还可以添加更多个性化的语料库，如 Google Docs、语雀、Notion 等。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/3958d654673a367b4bb2850e1a8a7044.png" alt="语料库管理"></p><h3 id="RAG-搜索体验"><a href="#RAG-搜索体验" class="headerlink" title="RAG 搜索体验"></a>RAG 搜索体验</h3><p>Coco AI 的强大之处在于其 RAG（Retrieval-Augmented Generation）搜索功能。通过这一功能，你可以快速检索到自己之前撰写的文章或其他文档。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/e9c48ba331aa880b992d2e29496503e2.png" alt="RAG 搜索"></p><p>此外，搜索栏还可以作为一个站内聊天机器人使用，默认 Coco Cloud 的后端接入的大模型是 deeseek，进一步提升交互体验。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/956986b027c25fc7f5f382e7d35d1742.png" alt="站内聊天机器人"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Coco AI 作为一款全新的 AI 搜索与效率工具，凭借其开源、跨平台、智能化等特性，为用户提供了极大的便利。无论是个人知识管理还是团队协作，Coco AI 都能胜任。期待未来更多功能的加入，也欢迎更多用户加入 Coco AI 的体验之旅！</p>]]></content>
    
    
    <summary type="html">Coco AI 桌面应用初体验，探索智能知识管理的全新方式</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Coco AI 全新升级：全图形化 RAG 配置，一键开启智能知识管理！</title>
    <link href="https://blog.no-claw.com/posts/b126c84d/"/>
    <id>https://blog.no-claw.com/posts/b126c84d/</id>
    <published>2025-03-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Coco-AI-再升级，全图形化配置-RAG"><a href="#Coco-AI-再升级，全图形化配置-RAG" class="headerlink" title="Coco AI 再升级，全图形化配置 RAG"></a>Coco AI 再升级，全图形化配置 RAG</h1><p>在《Coco AI APP 初体验：开启智能知识管理新篇章》和《打造智能语料库：通过 Coco AI Server 实现 Notion 笔记 RAG 检索功能》中，我介绍了 Coco AI 的第一个版本 ，我们我需要调用服务端的接口来手动添加数据源和配置登录信息，见那么在 0.2 的版本中，极限科技又开发了一个管理页面用来处理这些繁琐的信息。</p><p>同时这次更新也带来了全平台的支持，当然如果你愿意也可以自己编译源代码。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311084132109.png" alt="image-20250311084132109"></p><p>我们先来用 Docker 启动服务端，一行命令搞定，不需要再传递负载的命令行参数，也无需在启动 Easysearch。</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">docker run -d --name cocoserver -p 9000:9000 infinilabs/coco:0.2.1-1998</span><br></pre></td></tr></table></figure><p>服务启动在 9000 端口，那么在浏览器中打开欢迎页面：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311084417025.png" alt="image-20250311084417025"></p><p>创建账户，选择模型，和之前一样，我还是选择本地部署的 ollama 并且使用 deepseek 模型。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311084436647.png" alt="image-20250311084436647">设置完成后我们看到主页面，我们的目的是添加数据源，这样就不用使用复杂的请求。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311084532045.png" alt="image-20250311084532045"></p><p>可以进一步设置 LLM 推理后端：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085647597.png" alt="image-20250311085647597"></p><p>AI 助手这个功能还在开发中，后续可以期待一下。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311084620396.png" alt="image-20250311084620396"></p><p>连接数据源这里我认为是最大的亮点之一，不用再想之前改配置文件了，需要做的事情简单，以 Notion 为例简单填入 Token 和设置刷新时间即可。后按与 Notion 数据源的详细配置，可以回看之前写的《打造智能语料库：通过 Coco AI Server 实现 Notion 笔记 RAG 检索功能》</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085859669.png" alt="image-20250311085859669"></p><p>过了一会就可以在数据源中刷新出来的文档了，目前 Notion 只导入了标题，后期后导入具体内容的计划。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085953756.png" alt="image-20250311085953756"></p><p>甚至还可以在这里进行搜索，小小的站内搜索吧。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085918935.png" alt="image-20250311085918935"></p><p>然后回到客户端，进行登录，同时也支持暂时关闭不再需要的数据源。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085831872.png" alt="image-20250311085831872"></p><p>登录这里不再需要手动抓 token 了，直接重定向，很方便。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311090435162.png" alt="image-20250311090435162"></p><p>登录之后，和之前一样检索，总体来说比之前省心很多。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311091534010.png" alt="image-20250311091534010"></p><p>我之前使用的是 0.1 的版本，再登录之后会遇到这个错误，只要重新安装最新版本的客户端就好了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085932264.png" alt="image-20250311085932264"></p><p>升级之后同样这个 chat 也进化了，还支持深度思考。由于这里我只导入了文档的标题，所以它也只给我回了文档。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250311085515060.png" alt="image-20250311085515060"></p><p>这次更新体验直接提升了一大截，精简了很多配置，也简化了登录信息。感觉差不多下一步可以把这个服务部署到 NAS 上做日常使用了。</p>]]></content>
    
    
    <summary type="html">Coco AI 升级全图形化 RAG 配置界面，一键开启智能知识管理</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>飞牛OS虚拟机初体验</title>
    <link href="https://blog.no-claw.com/posts/3608dae8/"/>
    <id>https://blog.no-claw.com/posts/3608dae8/</id>
    <published>2025-03-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间，飞牛 OS 上线了虚拟机功能，尽管目前仍处于公测阶段，但已经可以尝鲜体验。官方文档也相当详细，感兴趣的可以参考：<a href="https://help.fnnas.com/articles/fnosV1/virtual-machine/install.md">虚拟机文档</a>。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2025/02/08/1738986517925-87ab4dc4-009c-49ad-b012-981c27831baa.png"></p><h3 id="公测声明"><a href="#公测声明" class="headerlink" title="公测声明"></a>公测声明</h3><p>官方的公测声明如下：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img19@main/2025/02/08/1738986651672-27741772-bbad-4fc3-825b-6927f0d37dde.png"></p><p>通俗来说，这个虚拟机功能可以看作是一个精简版的 PVE（Proxmox Virtual Environment）。在使用时，磁盘和网卡建议选择 <strong>virtio</strong>，这是一种半虚拟化方案，能够提供更好的性能和兼容性。</p><h3 id="配置磁盘和网卡"><a href="#配置磁盘和网卡" class="headerlink" title="配置磁盘和网卡"></a>配置磁盘和网卡</h3><p>磁盘部分推荐选择 <strong>virtio</strong> 驱动，以获得更好的 I&#x2F;O 性能：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img15@main/2025/02/08/1738987080763-9edab5a9-df99-46fd-967a-cd103a9ab1ae.png"></p><p>网卡同样支持 <strong>半虚拟化</strong>，可以在创建虚拟机时选择：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/08/1738987033508-81d29596-2bcf-426f-85de-a1f8d3be9bc4.png"></p><h3 id="Windows-虚拟机驱动安装"><a href="#Windows-虚拟机驱动安装" class="headerlink" title="Windows 虚拟机驱动安装"></a>Windows 虚拟机驱动安装</h3><p>由于 Windows 默认不包含 <strong>virtio</strong> 驱动，因此需要手动下载并安装：<a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/">VirtIO 官方下载地址</a></p><p>安装后，需要手动选择对应的驱动，这样才能正确识别到磁盘：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img4@main/2025/02/08/1738986969521-726f7e25-7f24-4857-8aae-782c8fadc540.png"></p><p>如果安装过程中未能识别到网卡驱动，可以在进入系统后再安装：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2025/02/08/1738986953186-9a8f8914-031b-422e-ae15-2fdb8b84f1e2.png"></p><h3 id="Guest-tools-安装"><a href="#Guest-tools-安装" class="headerlink" title="Guest-tools 安装"></a>Guest-tools 安装</h3><p>ISO 镜像中包含 <strong>Guest-tools</strong>，相当于虚拟机的 <strong>agent</strong>，需要安装，以提升性能和兼容性：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/08/1738986962306-e20fd35e-c1b9-4140-95b7-a0ae9d070e27.png"></p><p>安装方式很简单，可以直接从 <strong>ISO</strong> 镜像里安装，里面包含所有必要的驱动和工具：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img12@main/2025/02/08/1738987388483-336fd548-9314-457b-bf0d-02320124370f.png"></p><p>安装过程如下：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/08/1738987502926-926f9aac-eac1-4988-be17-97bfce6f64cb.png"></p><h3 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h3><p>安装完成后，可以看到网卡已正常识别，并且协商速率为 <strong>10G</strong>：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2025/02/08/1738986943168-c470d98b-1201-493b-9456-d23076700e78.png"></p><p>测速结果表明，CPU 占用率相对较低，性能表现良好：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img4@main/2025/02/08/1738986928791-86941ac3-6220-46dd-8a0c-c372505e50bc.png"></p><p>在内网环境下，测速几乎可以跑满 <strong>10G</strong> 网桥带宽：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img5@main/2025/02/08/1738986911764-1ab35eed-ac6f-467d-b1fd-83dc8cbed55d.png"></p><hr><p>总体来看，飞牛 OS 的虚拟机功能虽处于公测阶段，但体验已经相当不错。对于有轻量级虚拟化需求的用户来说，已经具备一定可玩性。如果你也对这项功能感兴趣，不妨动手试试！🚀</p>]]></content>
    
    
    <summary type="html">飞牛 NAS 系统虚拟机安装初体验，上手感受与基本配置。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
    <category term="飞牛OS" scheme="https://blog.no-claw.com/tags/%E9%A3%9E%E7%89%9BOS/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 中 PUT 和 POST 更新索引的区别及常见错误解析</title>
    <link href="https://blog.no-claw.com/posts/d5a28fff/"/>
    <id>https://blog.no-claw.com/posts/d5a28fff/</id>
    <published>2025-03-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 <strong>Easysearch</strong> 中，<code>PUT</code> 和 <code>POST</code> 都用于创建或更新文档，但它们的使用方式和行为有所不同。理解这些区别并正确使用，可以避免常见的错误，并确保数据操作符合预期。</p><hr><h2 id="1-PUT-用于创建或完全替换文档"><a href="#1-PUT-用于创建或完全替换文档" class="headerlink" title="1. PUT 用于创建或完全替换文档"></a><strong>1. <code>PUT</code> 用于创建或完全替换文档</strong></h2><p><code>PUT</code> 方法要求<strong>必须提供文档 ID</strong>，用于创建或完全替换已有文档。如果指定的文档 ID 不存在，则 <code>PUT</code> 会创建一个新文档；如果该 ID 已存在，则会<strong>完全覆盖</strong>原有数据，不会保留任何旧字段。</p><h3 id="✅-示例"><a href="#✅-示例" class="headerlink" title="✅ 示例"></a>✅ <strong>示例</strong></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></pre></td><td class="code"><pre><span class="line">PUT my_index/_doc/<span class="number">1</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;myindex&quot;</span><span class="punctuation">:</span> <span class="number">123</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>执行效果</strong></p><ul><li><strong>如果 ID <code>1</code> 存在</strong>，原有文档会被<strong>完全覆盖</strong>，只保留 <code>myindex</code> 字段。</li><li><strong>如果 ID <code>1</code> 不存在</strong>，则创建一个新文档。</li></ul><h3 id="❌-常见错误"><a href="#❌-常见错误" class="headerlink" title="❌ 常见错误"></a>❌ <strong>常见错误</strong></h3><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">PUT my_index/_doc</span><br></pre></td></tr></table></figure><span id="more"></span><h4 id="错误返回"><a href="#错误返回" class="headerlink" title="错误返回"></a><strong>错误返回</strong></h4><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;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Incorrect HTTP method for uri [/my_index/_doc] and method [PUT], allowed: [POST]&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">405</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a><strong>错误原因</strong></h4><ul><li><code>PUT</code> 需要<strong>指定文档 ID</strong>，但该请求缺少 ID，导致错误。</li></ul><hr><h2 id="2-POST-用于创建或部分更新文档"><a href="#2-POST-用于创建或部分更新文档" class="headerlink" title="2. POST 用于创建或部分更新文档"></a><strong>2. <code>POST</code> 用于创建或部分更新文档</strong></h2><p><code>POST</code> 既可以用于创建文档，也可以用于<strong>部分更新</strong>文档。其最大特点是<strong>可以省略文档 ID</strong>，让 Easysearch 自动生成唯一 ID。</p><h3 id="✅-示例-1：创建文档（自动生成-ID）"><a href="#✅-示例-1：创建文档（自动生成-ID）" class="headerlink" title="✅ 示例 1：创建文档（自动生成 ID）"></a>✅ <strong>示例 1：创建文档（自动生成 ID）</strong></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></pre></td><td class="code"><pre><span class="line">POST my_index/_doc</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;Bob&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">30</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>Easysearch <strong>自动分配 ID</strong> 并存储数据。</li></ul><h3 id="✅-示例-2：部分更新"><a href="#✅-示例-2：部分更新" class="headerlink" title="✅ 示例 2：部分更新"></a>✅ <strong>示例 2：部分更新</strong></h3><p>如果要仅修改某个字段，而不影响其他数据，应该使用 <code>_update</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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">POST my_index/_update/<span class="number">1</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;doc&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">26</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><ul><li>只修改 <code>age</code>，不会删除 <code>name</code> 等其他字段。</li></ul><h3 id="❌-常见错误-1"><a href="#❌-常见错误-1" class="headerlink" title="❌ 常见错误"></a>❌ <strong>常见错误</strong></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></pre></td><td class="code"><pre><span class="line">POST my_index/_doc/<span class="number">1</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;a&quot;</span><span class="punctuation">:</span> <span class="number">123</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="错误原因-1"><a href="#错误原因-1" class="headerlink" title="错误原因"></a><strong>错误原因</strong></h4><ul><li><code>POST</code> 传入了<strong>指定 ID</strong>，但 <code>POST</code> 的默认行为是<strong>创建新文档</strong>，不适用于替换已有文档，可能会导致数据不一致。因此，**替换文档应使用 <code>PUT</code>，部分更新应使用 <code>_update</code>**。</li></ul><p>🚨 <strong>不推荐使用 <code>POST</code> 替代 <code>PUT</code> 进行替换</strong>，官方推荐：</p><ul><li><strong><code>PUT</code> 明确用于创建&#x2F;替换。</strong></li><li><strong><code>POST</code> 适用于新增（不带 ID）或部分更新（<code>_update</code> API）。</strong></li></ul><hr><h2 id="3-POST-my-index（省略-doc）的错误解析"><a href="#3-POST-my-index（省略-doc）的错误解析" class="headerlink" title="3. POST my_index（省略 _doc）的错误解析"></a><strong>3. <code>POST my_index</code>（省略 <code>_doc</code>）的错误解析</strong></h2><h3 id="❌-错误示例"><a href="#❌-错误示例" class="headerlink" title="❌ 错误示例"></a>❌ <strong>错误示例</strong></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></pre></td><td class="code"><pre><span class="line">POST my_index</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;Alice&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="❌-错误返回"><a href="#❌-错误返回" class="headerlink" title="❌ 错误返回"></a>❌ <strong>错误返回</strong></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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Incorrect HTTP method for uri [/my_index] and method [POST], allowed: [HEAD, DELETE, PUT, GET]&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">405</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="错误原因-2"><a href="#错误原因-2" class="headerlink" title="错误原因"></a><strong>错误原因</strong></h4><p>这个是新建索引设置 mapping 的格式，不能用于创建索引数据。</p><p>✅ <strong>正确做法</strong></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">POST my_index/_doc</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;Alice&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></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">PUT my_index/_doc/<span class="number">1</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;Alice&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h2 id="4-PUT-和-POST-的区别总结"><a href="#4-PUT-和-POST-的区别总结" class="headerlink" title="4. PUT 和 POST 的区别总结"></a><strong>4. <code>PUT</code> 和 <code>POST</code> 的区别总结</strong></h2><table><thead><tr><th>操作</th><th><code>PUT my_index/_doc/1</code>（替换）</th><th><code>POST my_index/_doc/1</code>（不推荐）</th><th><code>POST my_index/_doc</code>（创建）</th><th><code>POST my_index</code>（错误）</th></tr></thead><tbody><tr><td><strong>是否需要 ID</strong></td><td>✅ 需要</td><td>✅ 需要（不推荐）</td><td>❌ 不需要（自动生成）</td><td>❌ 不能直接 <code>POST my_index</code></td></tr><tr><td><strong>文档是否存在</strong></td><td>✅ 存在则完全替换</td><td>✅ 存在时完全替换（不推荐）</td><td>✅ 创建新文档</td><td>❌ 报错</td></tr><tr><td><strong>是否部分更新</strong></td><td>❌ 不支持</td><td>❌ 不支持</td><td>❌ 不支持</td><td>❌ 报错</td></tr><tr><td><strong>适用场景</strong></td><td>创建&#x2F;替换整个文档</td><td>（不推荐）</td><td>创建新文档</td><td>❌ 需要 <code>/_doc</code></td></tr></tbody></table><hr><h2 id="5-结论与最佳实践"><a href="#5-结论与最佳实践" class="headerlink" title="5. 结论与最佳实践"></a><strong>5. 结论与最佳实践</strong></h2><p>为了保证数据的正确性和操作的高效性，推荐使用以下方式：</p><ul><li><strong>新增数据（自动 ID）</strong> → <code>POST my_index/_doc</code></li><li><strong>新增数据（指定 ID）</strong> → <code>PUT my_index/_doc/{id}</code></li><li><strong>修改部分字段</strong> → <code>POST my_index/_update/{id}</code></li><li><strong>完全替换文档</strong> → <code>PUT my_index/_doc/{id}</code></li></ul><p>通过正确使用 <code>PUT</code> 和 <code>POST</code>，可以避免 <code>POST my_index</code> 这种格式错误，并正确管理 <strong>Easysearch</strong> 的索引和文档，确保数据操作符合预期。</p><p>💡 <strong>推荐大家在 Easysearch Console 进行实践，以更直观地理解这些区别！</strong></p><p><img src="https://i-blog.csdnimg.cn/direct/9dcf4799deaa458e814cbbd6297f2ae8.png" alt="Easysearch Console"> 🚀</p>]]></content>
    
    
    <summary type="html">解析 Easysearch 中 PUT 与 POST 更新索引的区别及常见报错处理</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 使用 AWS S3 进行快照备份与还原：完整指南及常见错误排查</title>
    <link href="https://blog.no-claw.com/posts/eab170f2/"/>
    <id>https://blog.no-claw.com/posts/eab170f2/</id>
    <published>2025-03-08T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Easysearch 可以使用 <strong>AWS S3</strong> 作为远程存储库，进行索引的快照（Snapshot）备份和恢复。同时，Easysearch 内置了 S3 插件，无需额外安装。以下是完整的配置和操作步骤。</p><hr><h2 id="1-在-AWS-S3-上创建存储桶"><a href="#1-在-AWS-S3-上创建存储桶" class="headerlink" title="1. 在 AWS S3 上创建存储桶"></a>1. 在 AWS S3 上创建存储桶</h2><ol><li>登录 <strong>AWS 控制台</strong>，进入 <strong>S3</strong> 服务。</li><li><strong>创建一个新存储桶</strong>（例如 <code>easysearch-backups</code>）。</li><li><strong>启用版本控制</strong>（可选，但推荐）。</li><li><strong>权限配置</strong>：确保 IAM 角色具有访问 S3 的权限。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&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;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;s3:ListBucket&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;arn:aws:s3:::s3-bucket-name&quot;</span><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="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span> <span class="string">&quot;s3:PutObject&quot;</span><span class="punctuation">,</span> <span class="string">&quot;s3:DeleteObject&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;arn:aws:s3:::s3-bucket-name/*&quot;</span><span class="punctuation">]</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><h2 id="2-在-Console-上注册-S3-作为快照存储库"><a href="#2-在-Console-上注册-S3-作为快照存储库" class="headerlink" title="2. 在 Console 上注册 S3 作为快照存储库"></a>2. 在 Console 上注册 S3 作为快照存储库</h2><span id="more"></span><h3 id="使用-Console-DevTools-或-API"><a href="#使用-Console-DevTools-或-API" class="headerlink" title="使用 Console DevTools 或 API"></a><strong>使用 Console DevTools 或 API</strong></h3><p>在 Easysearch 的 DevTools 执行：</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">PUT _snapshot/my_s3_repository</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;easysearch-backups&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;base_path&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><blockquote><p><strong>注意</strong>：</p><ul><li><code>bucket</code> 需要填写你的 S3 存储桶名称。</li><li><code>region</code> 需要替换成你的 AWS S3 所在区域，SDK 默认美东区。</li><li>如果 Bucket 在中国区，还需添加 <code>endpoint: https://s3.&lt;region&gt;.amazonaws.com.cn</code> 参数。</li></ul></blockquote><hr><h2 id="3-创建快照"><a href="#3-创建快照" class="headerlink" title="3. 创建快照"></a>3. 创建快照</h2><p>一旦 <code>my_s3_repository</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">PUT _snapshot/my_s3_repository/my_snapshot_001</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;include_global_state&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><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">GET _snapshot/my_s3_repository/_all</span><br></pre></td></tr></table></figure><hr><p><img src="https://i-blog.csdnimg.cn/img_convert/8ac6983caa20c4deeefb80ff3d41eafe.png" alt="image-20250309161102887"></p><h2 id="4-从-AWS-S3-还原快照"><a href="#4-从-AWS-S3-还原快照" class="headerlink" title="4. 从 AWS S3 还原快照"></a>4. 从 AWS S3 还原快照</h2><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></pre></td><td class="code"><pre><span class="line">POST _snapshot/my_s3_repository/my_snapshot_001/_restore</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rename_pattern&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rename_replacement&quot;</span><span class="punctuation">:</span> <span class="string">&quot;restored_my_index&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>说明</strong>：这会从 <code>my_snapshot_001</code> 还原 <code>my_index</code>，但以 <code>restored_my_index</code> 命名，避免与现有索引冲突。</p></blockquote><p>如果要直接覆盖原索引（确保 <code>my_index</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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">POST _snapshot/my_s3_repository/my_snapshot_001/_restore</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;ignore_unavailable&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;include_global_state&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><br></pre></td></tr></table></figure><hr><h2 id="5-可能的错误与解决方案"><a href="#5-可能的错误与解决方案" class="headerlink" title="5. 可能的错误与解决方案"></a>5. 可能的错误与解决方案</h2><table><thead><tr><th><strong>错误信息</strong></th><th><strong>可能原因</strong></th><th><strong>解决方案</strong></th></tr></thead><tbody><tr><td><code>repository_s3 plugin not installed</code></td><td>没有安装 <code>repository-s3</code> 插件</td><td>运行 <code>bin/elasticsearch-plugin install repository-s3</code> 并重启</td></tr><tr><td><code>NoSuchBucket</code></td><td>S3 存储桶不存在</td><td>确保 S3 存储桶名称正确</td></tr><tr><td><code>AccessDenied</code></td><td>权限不足</td><td>确保 S3 存储桶策略正确，检查 IAM 角色</td></tr><tr><td><code>index_closed_exception</code></td><td>目标索引已关闭</td><td>先 <code>POST my_index/_open</code> 再恢复</td></tr><tr><td><code>index_already_exists_exception</code></td><td>目标索引已存在</td><td>先 <code>DELETE my_index</code> 再恢复</td></tr></tbody></table><hr><h2 id="6-快照恢复常见错误排查"><a href="#6-快照恢复常见错误排查" class="headerlink" title="6. 快照恢复常见错误排查"></a>6. 快照恢复常见错误排查</h2><h3 id="报错-1：无法连接到-S3"><a href="#报错-1：无法连接到-S3" class="headerlink" title="报错 1：无法连接到 S3"></a><strong>报错 1：无法连接到 S3</strong></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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;repository_verification_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repository] path [/] is not accessible on master node&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;repository_verification_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repository] path [/] is not accessible on master node&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;i_o_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unable to upload object [//tests-sXkmh3q5ThCCIX2VJp609g/master.dat] using a single upload&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sdk_client_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Failed to connect to service endpoint: &quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;socket_timeout_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Connect timed out&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="解决方案："><a href="#解决方案：" class="headerlink" title="解决方案："></a><strong>解决方案</strong>：</h4><ol><li>在 keystore 中添加 AWS 凭证：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ./bin/easysearch-keystore add s3.client.default.access_key</span><br><span class="line"><span class="built_in">sudo</span> ./bin/easysearch-keystore add s3.client.default.secret_key</span><br></pre></td></tr></table></figure></li><li>如果运行在 EC2 上，确保实例挂载了 IAM Role。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;repository_verification_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repositor1] path  is not accessible on master node&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;repository_verification_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repositor1] path  is not accessible on master node&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;i_o_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unable to upload object [tests-sUUzs-mTSZeYw1qk372DkQ/master.dat] using a single upload&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sdk_client_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The requested metadata is not found at http://169.254.169.254/latest/meta-data/iam/security-credentials/&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><hr><h3 id="报错-2：索引已存在，无法恢复"><a href="#报错-2：索引已存在，无法恢复" class="headerlink" title="报错 2：索引已存在，无法恢复"></a>报错 2：索引已存在，无法恢复</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repository:1/9gIDCgSySwKzQqEYvaGM_w] cannot restore index [my_index] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[my_s3_repository:1/9gIDCgSySwKzQqEYvaGM_w] cannot restore index [my_index] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name&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;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="解决方案：-1"><a href="#解决方案：-1" class="headerlink" title="解决方案："></a><strong>解决方案</strong>：</h4><ol><li><strong>删除现有索引后恢复</strong>：<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 /my_index</span><br></pre></td></tr></table></figure></li><li><strong>关闭索引后恢复</strong>：<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">POST /my_index/_close</span><br></pre></td></tr></table></figure></li><li><strong>恢复为新的索引名称</strong>：<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></pre></td><td class="code"><pre><span class="line">POST _snapshot/my_s3_repository/<span class="number">1</span>/_restore</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rename_pattern&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rename_replacement&quot;</span><span class="punctuation">:</span> <span class="string">&quot;restored_my_index&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol><hr><h3 id="报错-3：权限错误"><a href="#报错-3：权限错误" class="headerlink" title="报错 3：权限错误"></a>报错 3：权限错误</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;security_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;no permissions for [] and User [name=admin, external_roles=[admin]]&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;security_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;no permissions for [] and User [name=admin, external_roles=[admin]]&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;status&quot;</span><span class="punctuation">:</span> <span class="number">403</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="解决方案：-2"><a href="#解决方案：-2" class="headerlink" title="解决方案："></a><strong>解决方案</strong>：</h4><ol><li><strong>确保用户有 <code>manage_snapshots</code> 角色权限</strong>。</li><li><strong>排除 <code>.security</code> 索引或全局状态</strong>，否则无法恢复。</li></ol><p><img src="https://i-blog.csdnimg.cn/direct/31e63b73d7fd427f84f0079872962f37.png" alt="在这里插入图片描述"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">POST _snapshot/my_s3_repositor1/snapshot_002/_restore</span><br><span class="line">&#123;</span><br><span class="line">  &quot;indices&quot;: &quot;-.security&quot;,</span><br><span class="line">  &quot;ignore_unavailable&quot;: true,</span><br><span class="line">  &quot;include_global_state&quot;: false</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="📌-存储库（Repository）管理-API"><a href="#📌-存储库（Repository）管理-API" class="headerlink" title="📌 存储库（Repository）管理 API"></a><strong>📌 存储库（Repository）管理 API</strong></h1><p>存储库用于存储快照，Elasticsearch 支持 AWS S3、GCS、本地等存储。</p><h2 id="1️⃣-查看所有已注册的存储库"><a href="#1️⃣-查看所有已注册的存储库" class="headerlink" title="1️⃣ 查看所有已注册的存储库"></a>1️⃣ 查看所有已注册的存储库</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/_all</span><br></pre></td></tr></table></figure><p><strong>示例返回</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;my_s3_repository&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;es-snapshots-bucket&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;us-east-1&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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="2️⃣-查看特定存储库信息"><a href="#2️⃣-查看特定存储库信息" class="headerlink" title="2️⃣ 查看特定存储库信息"></a>2️⃣ 查看特定存储库信息</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/my_s3_repository</span><br></pre></td></tr></table></figure><h2 id="3️⃣-创建存储库（AWS-S3-示例）"><a href="#3️⃣-创建存储库（AWS-S3-示例）" class="headerlink" title="3️⃣ 创建存储库（AWS S3 示例）"></a>3️⃣ 创建存储库（AWS S3 示例）</h2><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">PUT _snapshot/my_s3_repository</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;type&quot;</span>: <span class="string">&quot;s3&quot;</span>,</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;bucket&quot;</span>: <span class="string">&quot;es-snapshots-bucket&quot;</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4️⃣-删除存储库"><a href="#4️⃣-删除存储库" class="headerlink" title="4️⃣ 删除存储库"></a>4️⃣ 删除存储库</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE _snapshot/my_s3_repository</span><br></pre></td></tr></table></figure><p><strong>⚠ 删除存储库不会删除快照，需要手动删除快照！</strong></p><hr><h1 id="📌-快照（Snapshot）管理-API"><a href="#📌-快照（Snapshot）管理-API" class="headerlink" title="📌 快照（Snapshot）管理 API"></a><strong>📌 快照（Snapshot）管理 API</strong></h1><p>快照用于备份和恢复索引数据。</p><h2 id="1️⃣-创建快照"><a href="#1️⃣-创建快照" class="headerlink" title="1️⃣ 创建快照"></a>1️⃣ 创建快照</h2><p><strong>备份特定索引</strong></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">PUT _snapshot/my_s3_repository/snapshot_001</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;indices&quot;</span>: <span class="string">&quot;my_index&quot;</span>,</span><br><span class="line">  <span class="string">&quot;include_global_state&quot;</span>: <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>备份所有索引</strong></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">PUT _snapshot/my_s3_repository/snapshot_002</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;include_global_state&quot;</span>: <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2️⃣-查看所有快照"><a href="#2️⃣-查看所有快照" class="headerlink" title="2️⃣ 查看所有快照"></a>2️⃣ 查看所有快照</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/my_s3_repository/_all</span><br></pre></td></tr></table></figure><h2 id="3️⃣-查看特定快照信息"><a href="#3️⃣-查看特定快照信息" class="headerlink" title="3️⃣ 查看特定快照信息"></a>3️⃣ 查看特定快照信息</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/my_s3_repository/snapshot_001</span><br></pre></td></tr></table></figure><h2 id="4️⃣-删除快照"><a href="#4️⃣-删除快照" class="headerlink" title="4️⃣ 删除快照"></a>4️⃣ 删除快照</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE _snapshot/my_s3_repository/snapshot_001</span><br></pre></td></tr></table></figure><hr><h1 id="📌-快照恢复（Restore）API"><a href="#📌-快照恢复（Restore）API" class="headerlink" title="📌 快照恢复（Restore）API"></a><strong>📌 快照恢复（Restore）API</strong></h1><p>恢复已备份的索引。</p><h2 id="1️⃣-还原单个索引"><a href="#1️⃣-还原单个索引" class="headerlink" title="1️⃣ 还原单个索引"></a>1️⃣ 还原单个索引</h2><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">POST _snapshot/my_s3_repository/snapshot_001/_restore</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;indices&quot;</span>: <span class="string">&quot;my_index&quot;</span>,</span><br><span class="line">  <span class="string">&quot;ignore_unavailable&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&quot;include_global_state&quot;</span>: <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2️⃣-还原索引并重命名"><a href="#2️⃣-还原索引并重命名" class="headerlink" title="2️⃣ 还原索引并重命名"></a>2️⃣ 还原索引并重命名</h2><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">POST _snapshot/my_s3_repository/snapshot_001/_restore</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;indices&quot;</span>: <span class="string">&quot;my_index&quot;</span>,</span><br><span class="line">  <span class="string">&quot;rename_pattern&quot;</span>: <span class="string">&quot;my_index&quot;</span>,</span><br><span class="line">  <span class="string">&quot;rename_replacement&quot;</span>: <span class="string">&quot;restored_my_index&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3️⃣-还原所有索引"><a href="#3️⃣-还原所有索引" class="headerlink" title="3️⃣ 还原所有索引"></a>3️⃣ 还原所有索引</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST _snapshot/my_s3_repository/snapshot_002/_restore</span><br></pre></td></tr></table></figure><hr><h1 id="📌-快照状态-API"><a href="#📌-快照状态-API" class="headerlink" title="📌 快照状态 API"></a><strong>📌 快照状态 API</strong></h1><p>查询快照的执行状态。</p><h2 id="1️⃣-查看当前快照任务"><a href="#1️⃣-查看当前快照任务" class="headerlink" title="1️⃣ 查看当前快照任务"></a>1️⃣ 查看当前快照任务</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/_status</span><br></pre></td></tr></table></figure><h2 id="2️⃣-查看特定快照状态"><a href="#2️⃣-查看特定快照状态" class="headerlink" title="2️⃣ 查看特定快照状态"></a>2️⃣ 查看特定快照状态</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/my_s3_repository/snapshot_001/_status</span><br></pre></td></tr></table></figure><hr><table><thead><tr><th><strong>API</strong></th><th><strong>用途</strong></th></tr></thead><tbody><tr><td><code>GET _snapshot/_all</code></td><td>查看所有存储库</td></tr><tr><td><code>GET _snapshot/my_s3_repository</code></td><td>查看特定存储库</td></tr><tr><td><code>PUT _snapshot/my_s3_repository</code></td><td>创建存储库</td></tr><tr><td><code>DELETE _snapshot/my_s3_repository</code></td><td>删除存储库</td></tr><tr><td><code>PUT _snapshot/my_s3_repository/snapshot_001</code></td><td>创建快照</td></tr><tr><td><code>GET _snapshot/my_s3_repository/_all</code></td><td>查看所有快照</td></tr><tr><td><code>GET _snapshot/my_s3_repository/snapshot_001</code></td><td>查看快照详情</td></tr><tr><td><code>DELETE _snapshot/my_s3_repository/snapshot_001</code></td><td>删除快照</td></tr><tr><td><code>POST _snapshot/my_s3_repository/snapshot_001/_restore</code></td><td>还原快照</td></tr><tr><td><code>GET _snapshot/_status</code></td><td>查看快照状态</td></tr></tbody></table><p>🚀 通过本文，你可以高效地使用 <strong>AWS S3 进行 Easysearch 快照备份和恢复</strong>，并排查可能的错误，确保集群数据安全无忧！</p>]]></content>
    
    
    <summary type="html">Easysearch 使用 AWS S3 快照备份还原的完整教程与错误排查</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>从 Flask 项目打包为多平台 Docker 镜像并上传 Docker Hub（含 GitHub Actions</title>
    <link href="https://blog.no-claw.com/posts/e51b087c/"/>
    <id>https://blog.no-claw.com/posts/e51b087c/</id>
    <published>2025-03-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="🧱-1-创建一个基础-Flask-项目"><a href="#🧱-1-创建一个基础-Flask-项目" class="headerlink" title="🧱 1. 创建一个基础 Flask 项目"></a>🧱 1. 创建一个基础 Flask 项目</h2><p><strong>项目结构：</strong></p><p>flask-demo&#x2F;<br>├── app.py<br>├── requirements.txt<br>├── Dockerfile<br>├── start.sh<br>└── .github&#x2F;<br>   └── workflows&#x2F;<br>       └── docker.yml</p><h3 id="app-py"><a href="#app-py" class="headerlink" title="app.py"></a><code>app.py</code></h3><p>from flask import Flask<br>app &#x3D; Flask(<strong>name</strong>)<br>​<br>@app.route(‘&#x2F;‘)<br>def hello():<br>    return “Hello from multi-arch Flask Docker in production mode!”</p><span id="more"></span><h3 id="requirements-txt"><a href="#requirements-txt" class="headerlink" title="requirements.txt"></a><code>requirements.txt</code></h3><p>flask<br>gunicorn</p><h3 id="start-sh"><a href="#start-sh" class="headerlink" title="start.sh"></a><code>start.sh</code></h3><p>#!&#x2F;bin&#x2F;bash</p><h1 id="start-sh-1"><a href="#start-sh-1" class="headerlink" title="start.sh"></a>start.sh</h1><p>​</p><h1 id="默认使用-4-个-Gunicorn-worker"><a href="#默认使用-4-个-Gunicorn-worker" class="headerlink" title="默认使用 4 个 Gunicorn worker"></a>默认使用 4 个 Gunicorn worker</h1><p>WORKERS&#x3D;${WORKERS:-4}<br>​<br>echo “🚀 Starting Gunicorn with $WORKERS workers…”<br>​</p><h1 id="启动-Flask-应用"><a href="#启动-Flask-应用" class="headerlink" title="启动 Flask 应用"></a>启动 Flask 应用</h1><p>exec gunicorn -w “$WORKERS” -b 0.0.0.0:5000 app:app<br>​</p><hr><h2 id="🐋-2-编写多平台-Dockerfile"><a href="#🐋-2-编写多平台-Dockerfile" class="headerlink" title="🐋 2. 编写多平台 Dockerfile"></a>🐋 2. 编写多平台 Dockerfile</h2><p>FROM python:3.12-slim<br>​<br>WORKDIR &#x2F;app<br>​<br>COPY requirements.txt .<br>RUN pip install –no-cache-dir -r requirements.txt<br>​<br>COPY . .<br>​<br>RUN chmod +x start.sh<br>​<br>CMD [“.&#x2F;start.sh”]</p><hr><h2 id="⚙️-3-本地构建-推送多平台镜像（可选）"><a href="#⚙️-3-本地构建-推送多平台镜像（可选）" class="headerlink" title="⚙️ 3. 本地构建 &amp; 推送多平台镜像（可选）"></a>⚙️ 3. 本地构建 &amp; 推送多平台镜像（可选）</h2><p>#!&#x2F;bin&#x2F;bash<br>​<br>set -e<br>​</p><h1 id="配置区域"><a href="#配置区域" class="headerlink" title="&#x3D;&#x3D;&#x3D;&#x3D; 配置区域 &#x3D;&#x3D;&#x3D;&#x3D;"></a>&#x3D;&#x3D;&#x3D;&#x3D; 配置区域 &#x3D;&#x3D;&#x3D;&#x3D;</h1><p>IMAGE_NAME&#x3D;”cloudsmithy&#x2F;flask-demo”             # Docker Hub 镜像名<br>PLATFORMS&#x3D;”linux&#x2F;amd64,linux&#x2F;arm64”             # 多架构支持<br>BUILDER_NAME&#x3D;”multiarch”                        # buildx 构建器名</p><h1 id=""><a href="#" class="headerlink" title="&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;"></a>&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</h1><p>​</p><h1 id="获取-TAG，优先使用-Git-tag，其次-fallback-为时间戳"><a href="#获取-TAG，优先使用-Git-tag，其次-fallback-为时间戳" class="headerlink" title="获取 TAG，优先使用 Git tag，其次 fallback 为时间戳"></a>获取 TAG，优先使用 Git tag，其次 fallback 为时间戳</h1><p>TAG&#x3D;$(git describe –tags –abbrev&#x3D;0 2&gt;&#x2F;dev&#x2F;null || date +%Y%m%d)<br>​<br>echo “🔖 使用镜像 tag：$TAG”<br>echo “📦 构建并推送镜像：”<br>echo “ - $IMAGE_NAME:$TAG”<br>echo “ - $IMAGE_NAME:latest”<br>​</p><h1 id="登录-Docker-Hub（如果没有缓存登录状态）"><a href="#登录-Docker-Hub（如果没有缓存登录状态）" class="headerlink" title="登录 Docker Hub（如果没有缓存登录状态）"></a>登录 Docker Hub（如果没有缓存登录状态）</h1><p>if ! docker info | grep -q “Username: cloudsmithy”; then<br>  echo “🔐 正在登录 Docker Hub…”<br> docker login -u cloudsmithy<br>fi<br>​</p><h1 id="创建-buildx-builder（如不存在）"><a href="#创建-buildx-builder（如不存在）" class="headerlink" title="创建 buildx builder（如不存在）"></a>创建 buildx builder（如不存在）</h1><p>if ! docker buildx inspect “$BUILDER_NAME” &amp;&gt; &#x2F;dev&#x2F;null; then<br>  docker buildx create –name “$BUILDER_NAME” –use<br>else<br> docker buildx use “$BUILDER_NAME”<br>fi<br>​<br>docker buildx inspect –bootstrap<br>​</p><h1 id="构建并推送镜像"><a href="#构建并推送镜像" class="headerlink" title="构建并推送镜像"></a>构建并推送镜像</h1><p>docker buildx build –platform “$PLATFORMS” \<br>  -t “$IMAGE_NAME:$TAG” \<br>  -t “$IMAGE_NAME:latest” \<br>  –push .<br>​</p><hr><h2 id="🚀-4-设置-GitHub-Actions-自动推送镜像"><a href="#🚀-4-设置-GitHub-Actions-自动推送镜像" class="headerlink" title="🚀 4. 设置 GitHub Actions 自动推送镜像"></a>🚀 4. 设置 GitHub Actions 自动推送镜像</h2><p>在 <code>.github/workflows/docker.yml</code> 中创建以下内容：</p><p>name: Build and Push Docker Image<br>​<br>on:<br> push:<br>   tags:<br>     - ‘v*‘  # 仅在 tag push（如 v1.0.0）时触发<br>​<br>jobs:<br> build-and-push:<br>   runs-on: ubuntu-latest<br>​<br>   steps:<br>   - name: Checkout source code<br>     uses: actions&#x2F;checkout@v4<br>​<br>   - name: Check DockerHub secrets<br>     run: |<br>       if [ -z “$“ ] || [ -z “$“ ]; then<br>         echo “❌ ERROR: DOCKER_USERNAME or DOCKER_PASSWORD is missing”<br>         exit 1<br>       fi<br>​<br>   - name: Set up QEMU<br>     uses: docker&#x2F;setup-qemu-action@v3<br>​<br>   - name: Set up Docker Buildx<br>     uses: docker&#x2F;setup-buildx-action@v3<br>     with:<br>       install: true  # ✅ 自动创建默认 builder<br>​<br>   - name: Docker login<br>     uses: docker&#x2F;login-action@v3<br>     with:<br>       username: $<br>        password: $<br>​<br>    - name: Extract tag name<br>      id: vars<br>      run: echo “TAG&#x3D;${GITHUB_REF#refs&#x2F;tags&#x2F;}” &gt;&gt; $GITHUB_ENV<br>​<br>    - name: Build and push Docker image (multi-arch + latest)<br>      uses: docker&#x2F;build-push-action@v5<br>      with:<br>        context: .<br>        push: true<br>        platforms: linux&#x2F;amd64,linux&#x2F;arm64<br>        tags: |<br>          cloudsmithy&#x2F;flask-demo:$<br>         cloudsmithy&#x2F;flask-demo:latest<br>​</p><hr><h2 id="🔐-5-配置-GitHub-Secrets"><a href="#🔐-5-配置-GitHub-Secrets" class="headerlink" title="🔐 5. 配置 GitHub Secrets"></a>🔐 5. 配置 GitHub Secrets</h2><p>在仓库的 <strong>Settings → Secrets → Actions</strong> 中添加：</p><table><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td><code>DOCKER_USERNAME</code></td><td>你的 Docker Hub 用户名</td></tr><tr><td><code>DOCKER_PASSWORD</code></td><td>你的 Docker Hub Token（推荐）</td></tr></tbody></table><hr><h2 id="🏁-6-触发构建-发布流程"><a href="#🏁-6-触发构建-发布流程" class="headerlink" title="🏁 6. 触发构建 &amp; 发布流程"></a>🏁 6. 触发构建 &amp; 发布流程</h2><p>git tag v1.0.0<br>git push origin v1.0.0</p><p>GitHub Actions 会自动：</p><ol><li><p>构建支持 x86 + ARM 的镜像</p></li><li><p>推送到 Docker Hub：</p><ul><li><code>cloudsmithy/flask-demo:v1.0.0</code></li><li><code>cloudsmithy/flask-demo:latest</code></li></ul></li></ol><hr><h2 id="✅-7-结果验证"><a href="#✅-7-结果验证" class="headerlink" title="✅ 7. 结果验证"></a>✅ 7. 结果验证</h2><p>docker pull cloudsmithy&#x2F;flask-demo:latest<br>docker run -p 5000:5000 cloudsmithy&#x2F;flask-demo:latest</p><hr>]]></content>
    
    
    <summary type="html">Flask 项目打包为多平台 Docker 镜像的完整流程，含 GitHub Actions 自动构建与推送 Docker Hub。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="开发" scheme="https://blog.no-claw.com/tags/%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>群晖导入磁盘大法 - 安装img</title>
    <link href="https://blog.no-claw.com/posts/8c366e49/"/>
    <id>https://blog.no-claw.com/posts/8c366e49/</id>
    <published>2025-03-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多年之后再用群晖的虚拟机，发现越来越像云了，可能也有在云计算行业摸爬滚打了几年的原因吧，总喜欢一些比较稀奇古怪的玩法，在家里常常玩公有云那一套。</p><h2 id="一、导入磁盘映像"><a href="#一、导入磁盘映像" class="headerlink" title="一、导入磁盘映像"></a>一、导入磁盘映像</h2><p>得到 Img 之后，点击映像，然后点击导入磁盘映像，我这里有两块盘，随便选一个就好。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105417636.png" alt="image-20250322105417636.png"></p><p>选中上传的 img 文件上传到磁盘映像。</p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322110125732.png" alt="image-20250322110125732.png"></p><p>然后可以观察到群晖根据这个 img 正在创建卷文件。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105500706.png" alt="image-20250322105500706.png"></p><p>这个是创建好的卷。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105529539.png" alt="image-20250322105529539.png"></p><h2 id="二、导入虚拟机并启动"><a href="#二、导入虚拟机并启动" class="headerlink" title="二、导入虚拟机并启动"></a>二、导入虚拟机并启动</h2><p>下面开始启动虚拟机，新增附近有一个三角箭头点击，有个导入的选项。（藏的挺深）</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322110401490.png" alt="image-20250322110401490.png"></p><p>可以导入 OVA，也可以导入上面的磁盘映像。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105605240.png" alt="image-20250322105605240.png"></p><p>同样也是选择磁盘。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105627911.png" alt="image-20250322105627911.png"></p><p>唯一不同的是，在虚拟磁盘这里我们可以选择刚刚创建的<strong>硬盘映像</strong>，然后后面下一步就可以了，不再需要 ISO 啥的。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322105649130.png" alt="image-20250322105649130.png"></p><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>其实吧，在公有云这算基础操作，在群晖这藏的这么深。随便玩玩，差不多该有的都有了。</p>]]></content>
    
    
    <summary type="html">群晖通过导入磁盘镜像安装系统的方法记录。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/NAS/%E7%BE%A4%E6%99%96/"/>
    
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>实现 INFINI Console 与 GitHub 的单点登录集成：一站式身份验证解决方案</title>
    <link href="https://blog.no-claw.com/posts/86ef0187/"/>
    <id>https://blog.no-claw.com/posts/86ef0187/</id>
    <published>2025-02-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文将为您详细解析如何通过 GitHub OAuth 2.0 协议，为 INFINI Console 实现高效、安全的单点登录（Single Sign-On, SSO）集成。通过此方案，用户可直接使用 GitHub 账户无缝登录 INFINI Console，简化身份验证流程，提升系统安全性与用户体验。</p><hr><h2 id="一、GitHub-OAuth-应用配置"><a href="#一、GitHub-OAuth-应用配置" class="headerlink" title="一、GitHub OAuth 应用配置"></a>一、GitHub OAuth 应用配置</h2><h3 id="1-创建-OAuth-应用程序"><a href="#1-创建-OAuth-应用程序" class="headerlink" title="1. 创建 OAuth 应用程序"></a>1. 创建 OAuth 应用程序</h3><ul><li><p>登录 GitHub，导航至 <strong>Settings</strong> -&gt; <strong>Developer settings</strong> -&gt; <strong>OAuth Apps</strong>。</p></li><li><p>点击 <strong>New OAuth App</strong>，创建新的 OAuth 应用程序。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/2b55471e441f7be84a64b56aaf5eefca.png" alt="创建 OAuth 应用"></p><span id="more"></span></li></ul><h3 id="2-配置应用信息"><a href="#2-配置应用信息" class="headerlink" title="2. 配置应用信息"></a>2. 配置应用信息</h3><ul><li><p>填写应用的基本信息，包括：</p><ul><li><strong>Application Name</strong>：应用名称（如 “INFINI Console SSO”）</li><li><strong>Homepage URL</strong>：应用主页 URL</li><li><strong>Authorization callback URL</strong>：回调 URL（格式：<code>http://localhost:9000/oauth/callback</code>）</li></ul><p><img src="https://i-blog.csdnimg.cn/img_convert/09c482af58f80c6fbed95c365f5e69e7.png" alt="配置应用信息"></p></li></ul><h3 id="3-获取客户端凭证"><a href="#3-获取客户端凭证" class="headerlink" title="3. 获取客户端凭证"></a>3. 获取客户端凭证</h3><ul><li><p>创建应用后，系统将生成 <strong>Client ID</strong> 和 <strong>Client Secret</strong>。</p></li><li><p>这些凭证将用于 INFINI Console 的 OAuth 配置。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c74e049c38f998c1965513938b859656.png" alt="获取客户端凭证"></p></li></ul><h3 id="4-查看已注册的-OAuth-应用"><a href="#4-查看已注册的-OAuth-应用" class="headerlink" title="4. 查看已注册的 OAuth 应用"></a>4. 查看已注册的 OAuth 应用</h3><ul><li><p>创建完成后，您可以在 OAuth 应用列表中查看应用的详细信息。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/c5b366939ab25fd22bfc2a7db66465eb.png" alt="已注册的 OAuth 应用"></p></li></ul><hr><h2 id="二、INFINI-Console-的-OAuth-集成配置"><a href="#二、INFINI-Console-的-OAuth-集成配置" class="headerlink" title="二、INFINI Console 的 OAuth 集成配置"></a>二、INFINI Console 的 OAuth 集成配置</h2><h3 id="1-修改配置文件"><a href="#1-修改配置文件" class="headerlink" title="1. 修改配置文件"></a>1. 修改配置文件</h3><ul><li><p>编辑 INFINI Console 的配置文件，添加以下 OAuth 配置：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">security:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">oauth:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">client_id:</span> <span class="string">&quot;xxxx&quot;</span> <span class="comment"># 替换为您的 Client ID</span></span><br><span class="line">    <span class="attr">client_secret:</span> <span class="string">&quot;xxxx&quot;</span> <span class="comment"># 替换为您的 Client Secret</span></span><br><span class="line">    <span class="attr">default_roles:</span> [<span class="string">&quot;ReadonlyUI&quot;</span>, <span class="string">&quot;AllClusters&quot;</span>] <span class="comment"># 默认角色</span></span><br><span class="line">    <span class="attr">role_mapping:</span></span><br><span class="line">      <span class="attr">medcl:</span> [<span class="string">&quot;Administrator&quot;</span>] <span class="comment"># 特定用户的角色映射</span></span><br><span class="line">    <span class="attr">authorize_url:</span> <span class="string">&quot;https://github.com/login/oauth/authorize&quot;</span></span><br><span class="line">    <span class="attr">token_url:</span> <span class="string">&quot;https://github.com/login/oauth/access_token&quot;</span></span><br><span class="line">    <span class="attr">redirect_url:</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="attr">scopes:</span> []</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-配置角色权限"><a href="#2-配置角色权限" class="headerlink" title="2. 配置角色权限"></a>2. 配置角色权限</h3><ul><li><p><strong>AllClusters</strong> 角色：用于管理集群的全局权限。</p></li><li><p><strong>ReadonlyUI</strong> 角色：为只读用户分配受限权限。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/a66c8d089d3a123ee52f564187b18238.png" alt="AllClusters 角色配置"></p><p><img src="https://i-blog.csdnimg.cn/img_convert/22c5cbaad09ce16755d22c219a3f2418.png" alt="Readonly 角色配置"></p></li></ul><hr><h2 id="三、单点登录流程演示"><a href="#三、单点登录流程演示" class="headerlink" title="三、单点登录流程演示"></a>三、单点登录流程演示</h2><h3 id="1-访问-INFINI-Console"><a href="#1-访问-INFINI-Console" class="headerlink" title="1. 访问 INFINI Console"></a>1. 访问 INFINI Console</h3><ul><li><p>打开浏览器，访问 <code>http://localhost:9000</code>。</p></li><li><p>点击 <strong>单点登录</strong> 按钮，进入登录流程。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/dad06ed04e30d2a4fb0f13d09cc3e81d.png" alt="单点登录入口"></p></li></ul><h3 id="2-使用-GitHub-登录"><a href="#2-使用-GitHub-登录" class="headerlink" title="2. 使用 GitHub 登录"></a>2. 使用 GitHub 登录</h3><ul><li><p>点击 <strong>GitHub</strong> 图标，跳转至 GitHub 登录页面。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/b64cc1db411aaeb31502511873cb3322.png" alt="GitHub 登录界面"></p></li></ul><h3 id="3-授权应用访问"><a href="#3-授权应用访问" class="headerlink" title="3. 授权应用访问"></a>3. 授权应用访问</h3><ul><li><p>在 GitHub 授权页面，确认授权 INFINI Console 访问您的 GitHub 账户。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/1ef73d2f3eac4e88369b76346e51cfe9.png" alt="GitHub 授权页面"></p></li></ul><h3 id="4-登录成功"><a href="#4-登录成功" class="headerlink" title="4. 登录成功"></a>4. 登录成功</h3><ul><li><p>授权成功后，系统将自动跳转回 INFINI Console，并显示您的 GitHub 用户名。</p><p><img src="https://i-blog.csdnimg.cn/img_convert/d6c6bdf6c8899e1c5de5f6b80ac1d0d9.png" alt="登录成功页面"></p><p><img src="https://i-blog.csdnimg.cn/img_convert/e905f2a3f0bec0fa02473ddc413f0d0d.png" alt="显示 GitHub 用户名"></p></li></ul><hr><h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><p>通过以上步骤，您已成功将 INFINI Console 与 GitHub 的单点登录功能集成。此方案不仅简化了用户的登录流程，还通过 GitHub 的 OAuth 2.0 协议确保了身份验证的安全性。未来，希望 INFINI Console 进一步扩展角色权限管理，或集成其他身份提供者（如 Google、Microsoft 等），打造更加灵活的身份验证体系。</p>]]></content>
    
    
    <summary type="html">配置 INFINI Console 与 GitHub 单点登录集成的完整方案</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>使用 INFINI Console 配置集群监控 Webhook 通知指南</title>
    <link href="https://blog.no-claw.com/posts/361d01a9/"/>
    <id>https://blog.no-claw.com/posts/361d01a9/</id>
    <published>2025-02-20T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在集群管理中，监控关键指标如 CPU、内存、磁盘、JVM 等是至关重要的。对于 Easysearch 及 ES 生态系统，还需要关注集群本身的指标，例如搜索延迟、集群状态、节点移除等。INFINI Console 不仅提供了默认的监控指标，还支持用户自定义监控项。当监控数值达到预设阈值时，系统可以通过 Webhook 发送通知至 Slack、飞书等平台。</p><h2 id="监控配置流程"><a href="#监控配置流程" class="headerlink" title="监控配置流程"></a>监控配置流程</h2><h3 id="1-告警对象与通知渠道设置"><a href="#1-告警对象与通知渠道设置" class="headerlink" title="1. 告警对象与通知渠道设置"></a>1. 告警对象与通知渠道设置</h3><p>在 INFINI Console 中，首先需要配置监控对象和通知渠道：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/538575c336afcb2dc23ccf58f640e79b.png" alt="告警对象与通知渠道设置"></p><h3 id="2-告警中心管理"><a href="#2-告警中心管理" class="headerlink" title="2. 告警中心管理"></a>2. 告警中心管理</h3><span id="more"></span><p>通过告警中心可以集中管理所有监控告警：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/5567b0c6d58cea73cd5c65f6762f8719.png" alt="告警中心"></p><h3 id="3-告警详情查看"><a href="#3-告警详情查看" class="headerlink" title="3. 告警详情查看"></a>3. 告警详情查看</h3><p>每个告警事件都提供详细信息查看功能：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/261d152fe8bdabd58d7a0612214381b3.png" alt="告警详情"></p><h3 id="4-告警历史记录"><a href="#4-告警历史记录" class="headerlink" title="4. 告警历史记录"></a>4. 告警历史记录</h3><p>系统完整记录所有历史告警信息：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/96c0420bdb598598ffefb67de36b5fd5.png" alt="告警历史"></p><h2 id="Webhook-实现示例"><a href="#Webhook-实现示例" class="headerlink" title="Webhook 实现示例"></a>Webhook 实现示例</h2><p>以下是用 Python 实现的 Webhook 接收服务：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request, jsonify</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> pprint <span class="keyword">import</span> pprint</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/webhook&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">webhook</span>():</span><br><span class="line">    <span class="comment"># 获取并处理请求数据</span></span><br><span class="line">    raw_data = request.data</span><br><span class="line">    decoded_data = raw_data.decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">    data = json.loads(decoded_data)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 打印接收到的数据</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Received data:&quot;</span>)</span><br><span class="line">    pprint(data)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 返回响应</span></span><br><span class="line">    <span class="keyword">return</span> jsonify(&#123;</span><br><span class="line">        <span class="string">&quot;status&quot;</span>: <span class="string">&quot;success&quot;</span>,</span><br><span class="line">        <span class="string">&quot;message&quot;</span>: <span class="string">&quot;Webhook received&quot;</span></span><br><span class="line">    &#125;), <span class="number">200</span></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">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8000</span>)</span><br></pre></td></tr></table></figure><h2 id="Webhook-配置步骤"><a href="#Webhook-配置步骤" class="headerlink" title="Webhook 配置步骤"></a>Webhook 配置步骤</h2><ol><li>在 Console 中添加 Webhook 配置：</li></ol><p><img src="https://i-blog.csdnimg.cn/img_convert/d958ec2c9b1d388d118133b343f10cf4.png" alt="Webhook配置"></p><ol start="2"><li>查看捕获的告警信息：</li></ol><p><img src="https://i-blog.csdnimg.cn/img_convert/a3b8fb1254294fdd8ffa43b68a7bb96c.png" alt="告警捕获"></p><h2 id="飞书-Webhook-集成"><a href="#飞书-Webhook-集成" class="headerlink" title="飞书 Webhook 集成"></a>飞书 Webhook 集成</h2><h3 id="1-创建飞书群组"><a href="#1-创建飞书群组" class="headerlink" title="1. 创建飞书群组"></a>1. 创建飞书群组</h3><p>在飞书客户端创建新的群组：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/5b1d540e964f56fc6e21d1d792fcbdb9.png" alt="创建群组"></p><h3 id="2-添加自定义机器人"><a href="#2-添加自定义机器人" class="headerlink" title="2. 添加自定义机器人"></a>2. 添加自定义机器人</h3><p>选择添加自定义机器人：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/e4ec3a7ad1a2954b79bc91269ef3638e.png" alt="添加机器人"></p><h3 id="3-配置机器人信息"><a href="#3-配置机器人信息" class="headerlink" title="3. 配置机器人信息"></a>3. 配置机器人信息</h3><p>设置机器人名称和描述：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/8368a27cc85bbfd67b03281b6066532b.png" alt="机器人配置"></p><h3 id="4-获取-Webhook-URL"><a href="#4-获取-Webhook-URL" class="headerlink" title="4. 获取 Webhook URL"></a>4. 获取 Webhook URL</h3><p>完成配置后获取 Webhook 地址：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/5616ce87b3732646bd71d51c3a5554d7.png" alt="Webhook URL"></p><p>通过以上配置，即可实现集群监控告警的实时通知，确保系统运维人员能够及时响应各种异常情况。</p>]]></content>
    
    
    <summary type="html">通过 INFINI Console 配置 Easysearch 集群监控的 Webhook 告警通知</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>推荐给 Easysearch 新用户的几个 Elasticsearch 可视化工具</title>
    <link href="https://blog.no-claw.com/posts/e3691a5b/"/>
    <id>https://blog.no-claw.com/posts/e3691a5b/</id>
    <published>2025-02-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Easysearch 作为国产化的 Elasticsearch（ES）替代方案，兼容 Elasticsearch 生态系统中的多种工具。本文将介绍几款适合 Easysearch 用户的可视化工具，帮助您更高效地管理和查询数据。</p><h3 id="1-Elasticsearch-Head-插件"><a href="#1-Elasticsearch-Head-插件" class="headerlink" title="1. Elasticsearch Head 插件"></a>1. Elasticsearch Head 插件</h3><p>在 ES 培训经常提到的 Elasticsearch Head 是一款基于浏览器的插件，适合不想部署 Kibana 等复杂工具的用户。它提供了简洁的界面，方便用户查看集群状态、索引分布、分片信息等。</p><h4 id="主要功能："><a href="#主要功能：" class="headerlink" title="主要功能："></a>主要功能：</h4><ul><li><p><strong>索引分布查看</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/3b97af37be16f5933582b9ff59cc1f0e.png" alt="image-20250220212800529"></p></li><li><p><strong>索引详细信息</strong><br> <img src="https://i-blog.csdnimg.cn/img_convert/48457d48ecd03439f8c5e5fdf522df28.png" alt="image-20250220214716206"></p><span id="more"></span></li><li><p><strong>分片信息查看</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/2b0dbf3a2b97198d122ba8b3f40e8662.png" alt="image-20250220214729464"></p></li><li><p><strong>DSL 查询</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/fb96526276868690ff0c01af4207f6ca.png"></p></li></ul><h3 id="2-Elasticvue-插件"><a href="#2-Elasticvue-插件" class="headerlink" title="2. Elasticvue 插件"></a>2. Elasticvue 插件</h3><p>Elasticvue 是一款高评分、高颜值的 Chrome 插件，功能全面，适合需要更丰富功能的用户。</p><h4 id="主要功能：-1"><a href="#主要功能：-1" class="headerlink" title="主要功能："></a>主要功能：</h4><ul><li><p><strong>节点信息查看</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/1fe678284f16178ec42847dcfd6f3857.png"></p></li><li><p><strong>索引查看</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/1fe678284f16178ec42847dcfd6f3857.png" alt="image-20250220214619935"></p></li><li><p><strong>DSL 查询</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/26f4d54f74c49a8c624a9c0b5a5937b8.png" alt="image-20250220214557277"></p></li><li><p><strong>快照存储库管理</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/6375e74b0e378bd8f7af75f9f9b4dc03.png" alt="image-20250220214520263"></p></li></ul><h3 id="3-Cerebro"><a href="#3-Cerebro" class="headerlink" title="3. Cerebro"></a>3. Cerebro</h3><p>Cerebro 是一款需要自行部署的工具，建议使用 Docker 进行安装。为了避免端口冲突和 TLS 认证错误，可以通过 Gateway 进行转发。</p><h4 id="部署步骤："><a href="#部署步骤：" class="headerlink" title="部署步骤："></a>部署步骤：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 9100:9000 lmenezes/cerebro</span><br></pre></td></tr></table></figure><h4 id="主要功能：-2"><a href="#主要功能：-2" class="headerlink" title="主要功能："></a>主要功能：</h4><ul><li><p><strong>集群管理</strong><br><img src="https://i-blog.csdnimg.cn/img_convert/c4684c2805620db2baa822137df95b00.png" alt="image-20250220214359979"></p></li><li><p><strong>网络请求处理</strong><br>Cerebro 有自己的后端服务，请求并非直接从浏览器发出。因此，启动 Docker 容器时，避免连接 <code>localhost</code>，以免进入容器内部。<br><img src="https://i-blog.csdnimg.cn/img_convert/1690bffb19a0cfeb124742d3ac18dd9b.png" alt="image-20250220213719116"></p></li></ul><p>查看索引信息：</p><p><img src="https://i-blog.csdnimg.cn/direct/9cbda99b13ef4d84827e69e7c3043a68.png" alt="在这里插入图片描述"></p><p>可视化功能一览：</p><p><img src="https://i-blog.csdnimg.cn/img_convert/165c89bef5d1d63f8a9e7ae63534f609.png" alt="image-20250220215239365"></p><h3 id="4-认证与安全"><a href="#4-认证与安全" class="headerlink" title="4. 认证与安全"></a>4. 认证与安全</h3><p>对于需要密码认证的连接，可以使用以下两种方式：</p><ol><li><p><strong>直接连接</strong>：<br><code>https://admin:xxxxx@localhost:9200/</code></p></li><li><p><strong>Base64 编码凭证</strong>：<br>可以使用 Postman 或其他工具生成 Base64 编码的凭证，并在请求头中传递。</p></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;https://localhost:9200&quot;</span></span><br><span class="line">payload = <span class="string">&quot;&quot;</span></span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">&#x27;Basic YWRtaW46NzllYTM4MzMwMmM2OGZiYWM0MDc=&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;GET&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以上工具各有特色，用户可以根据自己的需求选择合适的工具。无论是简单的浏览器插件，还是功能更强大的 Cerebro，都能帮助您更好地管理和查询 Easysearch 集群。</p>]]></content>
    
    
    <summary type="html">为 Easysearch 新手推荐几款实用的 Elasticsearch 可视化管理工具</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>打造智能语料库：通过Coco AI Server 实现 Notion 笔记 RAG 检索功能</title>
    <link href="https://blog.no-claw.com/posts/3d90967f/"/>
    <id>https://blog.no-claw.com/posts/3d90967f/</id>
    <published>2025-02-16T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="CoCo-Server-部署-RAG，使用-Notion-作为语料库（上）"><a href="#CoCo-Server-部署-RAG，使用-Notion-作为语料库（上）" class="headerlink" title="CoCo Server 部署 RAG，使用 Notion 作为语料库（上）"></a>CoCo Server 部署 RAG，使用 Notion 作为语料库（上）</h2><ol><li><p>启动 Easysearch，这里把 Easysearch 作为语料库，把 notion 的素材存在 Easysearch</p></li><li><p>启动 ollama，使用 LLM 进行推理</p></li><li><p>启动 Coco Server，端口在 9000</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217142649790.png" alt="image-20250217142649790"></p></li></ol><p>Coco App 连接 Sever，输入输入</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217142816787.png" alt="image-20250217142816787"></p><p>登录自己 server，依旧使用 Github 登录</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217142837884.png" alt="image-20250217142837884"></p><p>Github 登录之后的重定向，我们目前需要抓取最后的, 后面用这个 token 换取访问 Coco Server AI 的 key：</p><figure class="highlight plaintext"><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">coco://oauth_callback?code=cupibub55o1cfqbveps0q804ai6aj14in3u91xjhvuk8s7ixirjsq2j9mmyyeut91nmgjwz0b494ngpk&amp;request_id=eb94762b-f054-4710-9c6cf20889d3&amp;provider=coco-cloud</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217142925920.png" alt="image-20250217142925920"></p><p>认证步骤如下：</p><p>第一步:<br><a href="http://localhost:9000/sso/login/github?provider=coco-cloud&product=coco&request_id=dd9825e1-ebd3-4c84-9e3f-7ccb0421c508">http://localhost:9000/sso/login/github?provider=coco-cloud&amp;product=coco&amp;request_id=dd9825e1-ebd3-4c84-9e3f-7ccb0421c508</a></p><p>会返回一个 Token,记录下来,这个是只是临时的, 如 XXABC</p><p>第二步:<br>curl -H’X-API-TOKEN: XXABC’ “<a href="http://localhost:9000/auth/request_access_token?request_id=dd9825e1-ebd3-4c84-9e3f-7ccb0421c508">http://localhost:9000/auth/request_access_token?request_id=dd9825e1-ebd3-4c84-9e3f-7ccb0421c508</a>“</p><p>返回的才是你要的 Token</p><p>在 postman 中换 token，得到 access_token 和过期时间：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217143134946.png" alt="image-20250217143134946"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">parse_oauth_callback</span>(<span class="params">url</span>):</span><br><span class="line">    query_params = &#123;param.split(<span class="string">&#x27;=&#x27;</span>)[<span class="number">0</span>]: param.split(<span class="string">&#x27;=&#x27;</span>)[<span class="number">1</span>] <span class="keyword">for</span> param <span class="keyword">in</span> url.split(<span class="string">&#x27;?&#x27;</span>)[<span class="number">1</span>].split(<span class="string">&#x27;&amp;&#x27;</span>)&#125;</span><br><span class="line">    code = query_params.get(<span class="string">&quot;code&quot;</span>)</span><br><span class="line">    request_id = query_params.get(<span class="string">&quot;request_id&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> code, request_id</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">request_access_token</span>(<span class="params">code, base_url, request_id</span>):</span><br><span class="line">    url = <span class="string">f&quot;<span class="subst">&#123;base_url&#125;</span>/auth/request_access_token?request_id=<span class="subst">&#123;request_id&#125;</span>&quot;</span></span><br><span class="line">    headers = &#123;<span class="string">&quot;X-API-TOKEN&quot;</span>: code&#125;</span><br><span class="line">    response = requests.get(url, headers=headers)</span><br><span class="line">    <span class="keyword">return</span> response.json()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 示例输入</span></span><br><span class="line">oauth_callback_url = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">coco://oauth_callback?code=cupibub55o1cfqbveps0q804ai6aj151wu4in3u91xjhvuk8s7ixirjsq2j9mmyyeut91nmgjwz0b494ngpk&amp;request_id=eb94762b-f054-4710-9c6a-0cf2088729d3&amp;provider=coco-cloud</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line">base_url = <span class="string">&quot;http://localhost:9000&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解析 code 和 request_id</span></span><br><span class="line">code, request_id = parse_oauth_callback(oauth_callback_url)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发送请求</span></span><br><span class="line">token_response = request_access_token(code, base_url, request_id)</span><br><span class="line"><span class="built_in">print</span>(token_response)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以用 access_key 查看用户信息：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217143244767.png" alt="image-20250217143244767"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;http://localhost:9000/account/profile&quot;</span></span><br><span class="line"></span><br><span class="line">payload = &#123;&#125;</span><br><span class="line">headers = &#123;</span><br><span class="line">  <span class="string">&#x27;X-API-TOKEN&#x27;</span>: <span class="string">&#x27;cupichb55o1cfqbveq90zwomyxs791ul3esbxxt480c8dzgvdtjtvmcnsld4a5v0wvx9l9ofcf1&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response = requests.request(<span class="string">&quot;GET&quot;</span>, url, headers=headers, data=payload)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(response.text)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注册 Notion connector：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">update_connector</span>(<span class="params">base_url, api_token, connector_name, data</span>):</span><br><span class="line">    url = <span class="string">f&quot;<span class="subst">&#123;base_url&#125;</span>/connector/<span class="subst">&#123;connector_name&#125;</span>?replace=true&quot;</span></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;X-API-TOKEN&quot;</span>: api_token,</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    response = requests.put(url, headers=headers, data=json.dumps(data))</span><br><span class="line">    <span class="keyword">return</span> response.json()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">base_url = <span class="string">&quot;http://localhost:9000&quot;</span></span><br><span class="line">api_token = <span class="string">&quot;&lt;token&gt;&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">notion_data = &#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Notion Docs Connector&quot;</span>,</span><br><span class="line">    <span class="string">&quot;description&quot;</span>: <span class="string">&quot;Fetch the docs metadata for notion.&quot;</span>,</span><br><span class="line">    <span class="string">&quot;icon&quot;</span>: <span class="string">&quot;/assets/connector/notion/icon.png&quot;</span>,</span><br><span class="line">    <span class="string">&quot;category&quot;</span>: <span class="string">&quot;website&quot;</span>,</span><br><span class="line">    <span class="string">&quot;tags&quot;</span>: [<span class="string">&quot;docs&quot;</span>, <span class="string">&quot;notion&quot;</span>, <span class="string">&quot;web&quot;</span>],</span><br><span class="line">    <span class="string">&quot;url&quot;</span>: <span class="string">&quot;http://coco.rs/connectors/notion&quot;</span>,</span><br><span class="line">    <span class="string">&quot;assets&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;icons&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;default&quot;</span>: <span class="string">&quot;/assets/connector/notion/icon.png&quot;</span>,</span><br><span class="line">            <span class="string">&quot;web_page&quot;</span>: <span class="string">&quot;/assets/connector/notion/icon.png&quot;</span>,</span><br><span class="line">            <span class="string">&quot;database&quot;</span>: <span class="string">&quot;/assets/connector/notion/database.png&quot;</span>,</span><br><span class="line">            <span class="string">&quot;page&quot;</span>: <span class="string">&quot;/assets/connector/notion/page.png&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><br><span class="line">response_notion = update_connector(base_url, api_token, <span class="string">&quot;notion&quot;</span>, notion_data)</span><br><span class="line"><span class="built_in">print</span>(response_notion)</span><br></pre></td></tr></table></figure><p>修改 Notion 配置文件，激活检索 Notion：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217210840104.png" alt="image-20250217210840104"></p><p>在 Notion 这个网站申请 API key，<a href="https://www.notion.so/profile/integrations">https://www.notion.so/profile/integrations</a></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217212252851.png" alt="image-20250217212252851"></p><p>配置完成之后，设置权限和展示 apikey</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217212349650.png" alt="image-20250217212349650"></p><p>配置 Notion Connector，这里需要用到 Notion 的 API Key:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_datasource</span>(<span class="params">base_url, api_token, data</span>):</span><br><span class="line">    url = <span class="string">f&quot;<span class="subst">&#123;base_url&#125;</span>/datasource/&quot;</span></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;X-API-TOKEN&quot;</span>: api_token,</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    response = requests.post(url, headers=headers, data=json.dumps(data))</span><br><span class="line">    <span class="keyword">return</span> response.json()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 示例输入</span></span><br><span class="line">base_url = <span class="string">&quot;http://localhost:9000&quot;</span></span><br><span class="line">api_token = <span class="string">&quot;&lt;api-key&gt;&quot;</span></span><br><span class="line"></span><br><span class="line">datasource_data = &#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;My Notion&quot;</span>,</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;connector&quot;</span>,</span><br><span class="line">    <span class="string">&quot;connector&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;id&quot;</span>: <span class="string">&quot;notion&quot;</span>,</span><br><span class="line">        <span class="string">&quot;config&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;token&quot;</span>: <span class="string">&quot;&lt;notion token&gt;&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"># 发送 POST 请求</span></span><br><span class="line">response = create_datasource(base_url, api_token, datasource_data)</span><br><span class="line"><span class="built_in">print</span>(response)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>需要在 Notion 中设置集成，这样 Coco Server 才会搜索到：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217212508943.png" alt="image-20250217212508943"></p><p>Coco server 日志中可以检索到 notion 了：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217210810825.png" alt="image-20250217210810825"></p><p>终于可以在搜索栏检索到了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250217211620791.png" alt="image-20250217211620791"></p>]]></content>
    
    
    <summary type="html">用 Coco AI Server 将 Notion 笔记构建为 RAG 智能语料库</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Coco/"/>
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Coco-AI" scheme="https://blog.no-claw.com/tags/Coco-AI/"/>
    
  </entry>
  
  <entry>
    <title>Typora 和Obsidian 自动使用 Github 做在线图床</title>
    <link href="https://blog.no-claw.com/posts/1fc7f9f5/"/>
    <id>https://blog.no-claw.com/posts/1fc7f9f5/</id>
    <published>2025-02-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>从前写 Markdown 使用使用相对路径，后来上了 Vuepres&#x2F;Hexo 之后图床迁移一直有问题，最后采用了在线图床的办法，虽然在国内访问 Github 并不完美，但是毕竟都是都是一些折腾的记忆，如果放在不一直续费的公有云或者一些免费的小厂商，倘若有一天数据丢失那也是一个伤心的事情。</p><h3 id="设置-Github"><a href="#设置-Github" class="headerlink" title="设置 Github"></a>设置 Github</h3><p>先新建 Github 的图床目标仓库：</p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103303802.png" alt="image-20250215103303802"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103206785.png" alt="image-20250215103206785"></p><p>然后设置 Token</p><p>打开 Github –&gt; 点击头像 –&gt; Settings –&gt; Developer settings –&gt; 点击 Personal access tokens –&gt; 点击 Tokens(classic) –&gt; 选择 Generate new token –&gt; 填写 Note(token 名称)\选择过期时间\选择 token 权限 –&gt; 点击 Generate token 保存 token</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103617389.png" alt="image-20250215103617389"></p><h3 id="下载-Picgo"><a href="#下载-Picgo" class="headerlink" title="下载 Picgo"></a>下载 Picgo</h3><p>这个用来做上传图片的 Agent，实际上 Typora 和 Obsidian 都是调用了 PicGo 的 API</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215102641429.png" alt="image-20250215102641429"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215102734567.png" alt="image-20250215102734567"></p><p>安装时候可能需要这个问题：</p><p>因为 PicGo 没有签名，所以会被 macOS 的安全检查所拦下。</p><p>安装后打开遇到「文件已损坏」的情况，请按如下方式操作：<br>信任开发者，会要求输入密码:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> spctl --master-disable</span><br></pre></td></tr></table></figure><p>然后放行 PicGo :</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">xattr -cr /Applications/PicGo.app</span><br></pre></td></tr></table></figure><p>然后就能正常打开。<a href="https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md">https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md</a></p><p>填入仓库信息和 token：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103235460.png" alt="image-20250215103235460"></p><h3 id="设置-Typora-和-Obsidian"><a href="#设置-Typora-和-Obsidian" class="headerlink" title="设置 Typora 和 Obsidian"></a>设置 Typora 和 Obsidian</h3><p>在 Typora 中设置上传图片时候调用 Picgo。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215102926948.png" alt="image-20250215102926948"></p><p>Obsidian 需要安装插件，先关闭安装模式：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103044288.png" alt="image-20250215103044288"></p><p>安装 Image upload 插件：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103103811.png" alt="image-20250215103103811"></p><h3 id=""><a href="#" class="headerlink" title=""></a></h3><h3 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h3><p>Github 上可以看到 commit 记录：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103420945.png" alt="image-20250215103420945"></p><p>Obsidian 也能正常渲染：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103253797.png" alt="image-20250215103253797"></p><p>微信排版工具也能正常渲染 Github Raw 链接：<a href="https://doocs.github.io/md/">https://doocs.github.io/md/</a></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250215103517169.png" alt="image-20250215103517169"></p>]]></content>
    
    
    <summary type="html">配置 Typora 和 Obsidian 自动上传图片到 GitHub 图床</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>DSM 外接磁盘挂载记录</title>
    <link href="https://blog.no-claw.com/posts/f2e07cac/"/>
    <id>https://blog.no-claw.com/posts/f2e07cac/</id>
    <published>2025-02-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-外接磁盘识别情况"><a href="#1-外接磁盘识别情况" class="headerlink" title="1. 外接磁盘识别情况"></a>1. 外接磁盘识别情况</h2><p>将移动硬盘连接至群晖 NAS（DSM 7.2），系统右上角立即弹出外接设备提示：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121435537.png" alt="外接磁盘识别"></p><p>可在“控制面板 - 外接设备”中查看磁盘详情：</p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121751756.png" alt="控制面板识别磁盘"></p><h2 id="2-exFAT-支持插件提示"><a href="#2-exFAT-支持插件提示" class="headerlink" title="2. exFAT 支持插件提示"></a>2. exFAT 支持插件提示</h2><p>首次插入 exFAT 文件系统磁盘，系统提示需安装对应插件才能访问。可跳转 Package Center 安装 exFAT Access：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121733932.png" alt="exFAT 插件提示"></p><h2 id="3-Mac-格式化分区异常"><a href="#3-Mac-格式化分区异常" class="headerlink" title="3. Mac 格式化分区异常"></a>3. Mac 格式化分区异常</h2><p>在 Mac 上格式化为 exFAT 后插入群晖，系统将其识别为两个分区：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121611592.png" alt="两个分区"></p><h2 id="4-在群晖中重新格式化"><a href="#4-在群晖中重新格式化" class="headerlink" title="4. 在群晖中重新格式化"></a>4. 在群晖中重新格式化</h2><p>为避免多分区问题，使用群晖内置“格式化”功能重新整理磁盘。可在外接设备列表中操作：</p><ul><li>选择磁盘 → 点击“格式化”</li><li>文件系统建议继续使用 exFAT，以兼容 Mac&#x2F;Windows</li></ul><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121643508.png" alt="格式化选项"> <img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121603583.png" alt="格式化确认"></p><h2 id="5-结果验证"><a href="#5-结果验证" class="headerlink" title="5. 结果验证"></a>5. 结果验证</h2><p>格式化为 exFAT 后，无论在群晖还是 Mac 上均可正常挂载与读写：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250326121509162.png" alt="Mac 上读取"></p>]]></content>
    
    
    <summary type="html">群晖 DSM 外接 USB 磁盘的挂载方法与踩坑记录。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/NAS/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>追忆青春 - 黑群晖.md</title>
    <link href="https://blog.no-claw.com/posts/795ed575/"/>
    <id>https://blog.no-claw.com/posts/795ed575/</id>
    <published>2025-02-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>那么现在有一个叫做 RR 的项目简化了这一切，我把这些记录下来。他们提供了 release 文件，如下：</p><p><a href="https://github.com/RROrg/rr/releases/">https://github.com/RROrg/rr/releases/</a><br><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/02/10/1739196485668-58a25e29-30fd-4e25-92fe-e6f367b410f1.png"></p> <span id="more"></span><p>用 Etcher 来写盘，需要 U 盘引导的系统不适合用 Ventoy 启动，因为没办法保存，除非把系统安装在其他的磁盘上。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/02/10/1739196552527-55e508f9-c884-4f7e-8fcd-ccb32e62c987.png"></p><ol><li>从 U 盘启动页面，rr 会启动一个 web server，端口是 7681</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2025/02/10/1739196722316-a26990fa-f1c5-4dd9-a592-d987e3e6b9ee.png"></p><ol start="2"><li>然后就是更换语言，选择机型，系统版本，下载 PAT 文件，以及编译引导（图片来自参考链接）</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2025/02/10/1739196997316-a5ba2c12-d627-4d49-9a36-b2a1b238f66c.png"></p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img7@main/2025/02/10/1739197021973-b6262598-200d-4b25-ba14-49037c1a8336.png"></p><ol><li>选择的是 DS918+，没有额外编译任何插件</li></ol><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img17@main/2025/02/10/1739196919409-12daeafe-882b-4f63-bb32-a03edf0e113a.png"></p><p>使用 web 版本的 findsnoloy 没有搜到机器，最后使用手机 app 搜到了局域网的安装好的群晖，之前安装在 ec2 上的黑群晖无法启动可能也是这个原因。</p><p>存储池信息：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2025/02/10/1739196911158-a5f023b0-04f7-4f40-8ea4-373faff2bedb.png"></p><p>手机 APP 连接的信息：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2025/02/10/1739196657060-49396e39-4c67-499c-88da-385d04b087a0.png"></p><p>虚拟机也很好用，不输 EXSI（除了删除机器的时候强制输入密码）：</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img8@main/2025/02/10/1739197228147-a97293e4-3d04-4846-8d63-8e52df9e1013.png"></p><p>对于 VirtIO Block 控制器，官方只建议安装 Linux，实测也可以安装 Windows。</p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img6@main/2025/02/10/1739197256602-b2e5536c-7b2f-4d98-934e-3d1e702e00c3.png"></p><p>需要把官方的 guest agent 换成 VirtIO 的 ISO，对就是 fedora 那个：<a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/">https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/</a></p><p><img src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2025/02/10/1739197420400-aaa133a7-dd80-4002-8cd3-ec879060da23.png"></p><p>参考：</p><p><a href="https://www.wanjiachupin.com/1314.html">https://www.wanjiachupin.com/1314.html</a></p><p><a href="https://www.mspace.cc/archives/1002">https://www.mspace.cc/archives/1002</a></p>]]></content>
    
    
    <summary type="html">黑群晖折腾回忆录，从入门到放弃再到怀念的 NAS 之路。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>Macbook Pro快速搭建Easysearch学习环境</title>
    <link href="https://blog.no-claw.com/posts/8b29e69c/"/>
    <id>https://blog.no-claw.com/posts/8b29e69c/</id>
    <published>2025-02-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在学习过程中，我们有时身边没有可用的服务器，这时就需要借助自己的 Mac 来安装和学习 Easysearch。然而，Easysearch 官网并未提供 Mac 版本的安装教程，下面我将详细整理我在 Mac 上安装和使用 Easysearch 的折腾经历。<img src="https://i-blog.csdnimg.cn/direct/e549bc7f2a41481e8c7a49817bf9d76d.png" alt="在这里插入图片描述"></p><h3 id="Easysearch"><a href="#Easysearch" class="headerlink" title="Easysearch"></a>Easysearch</h3><p>Easysearch 的运行依赖于 Java，程序启动时会自动从当前目录的 JDK 中查找 Java 环境。因此，即便环境变量中已经配置了 Java，程序也可能无法找到。针对这个问题，有两种解决办法：<br>下载 JDK 的二进制文件，将其重命名为 “jdk”，并放置在 Easysearch 的根目录下。<br>下载 Easysearch 的 bundle 包，该包会自带一个 JDK。下载链接为：<a href="https://release.infinilabs.com/Easysearch/stable/bundle/">https://release.infinilabs.com/Easysearch/stable/bundle/</a><br>安装步骤如下：首先执行初始化脚本，此脚本会设置 TLS 证书和集群密码。在执行脚本之前，log 目录为空。</p><span id="more"></span><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">bin/initialize.sh</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">                               @@@@@@@@@&amp;@@@</span><br><span class="line">                              <span class="comment">#@@@@@@@@@@@@@</span></span><br><span class="line">        @@@                   @@@@@@@@@@@@@</span><br><span class="line">       &amp;@@@@@@@              &amp;@@@@@@@@@@@@@</span><br><span class="line">       @&amp;@@@@@@@&amp;@           @@@&amp;@@@@@@@&amp;@</span><br><span class="line">      @@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@</span><br><span class="line">      @@@@@@@@@@@@@@@@@@&amp;   @@@@@@@@@@@@@</span><br><span class="line">        %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@</span><br><span class="line">            @@@@@@@@@@@@&amp;@@@@@@@@@@@@@@@</span><br><span class="line">    @@         ,@@@@@@@@@@@@@@@@@@@@@@@&amp;</span><br><span class="line">    @@@@@.         @@@@@&amp;@@@@@@@@@@@@@@</span><br><span class="line">   @@@@@@@@@@          @@@@@@@@@@@@@@@#</span><br><span class="line">   @&amp;@@@&amp;@@@&amp;@@@          &amp;@&amp;@@@&amp;@@@&amp;@</span><br><span class="line">  @@@@@@@@@@@@@.              @@@@@@@*</span><br><span class="line">  @@@@@@@@@@@@@                  %@@@</span><br><span class="line"> @@@@@@@@@@@@@</span><br><span class="line">/@@@@@@@&amp;@@@@@</span><br><span class="line">@@@@@@@@@@@@@</span><br><span class="line">@@@@@@@@@@@@@</span><br><span class="line">@@@@@@@@@@@@        Welcome to INFINI Labs!</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Now attempting the initializing...</span><br><span class="line"></span><br><span class="line">RISK WARNING: The initialization script will overwrite certificates and passwords. If this is not the first run, please acknowledge the risks before proceeding. This is a dangerous operation. Do you want to proceed? [y/N]:y</span><br><span class="line"></span><br><span class="line">NOTICE: Do you want to <span class="built_in">log</span> credentials to /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/logs/initialize.log? Default is no logging. Press Enter to skip. [y/N]:y</span><br><span class="line">Using bundled JDK at /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/jdk</span><br><span class="line"></span><br><span class="line">Certificate request self-signature ok</span><br><span class="line">subject=C = IN, ST = FI, L = NI, O = ORG, OU = UNIT, CN = infini.cloud</span><br><span class="line">Certificate request self-signature ok</span><br><span class="line">subject=C = IN, ST = FI, L = NI, O = ORG, OU = UNIT, CN = admin.infini.cloud</span><br><span class="line">                DNS:infini.cloud, DNS:*.infini.cloud</span><br><span class="line">                DNS:infini.cloud, DNS:*.infini.cloud</span><br><span class="line"></span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@</span><br><span class="line">@    NOTICE: Please remember the bootstrap credential <span class="keyword">for</span> further usage    @</span><br><span class="line">@                        admin:db3e19a8c1c9f7755763                        @</span><br><span class="line">@    Usage:  curl -ku admin:db3e19a8c1c9f7755763 https://localhost:9200    @</span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@</span><br><span class="line"></span><br><span class="line">Your plugins directory is not empty. Please install the plugin manually.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Initialization successful! [Easysearch] is ready to use!</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">----------------------------------------------------------------</span><br><span class="line"><span class="built_in">cd</span> /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle &amp;&amp; bin/Easysearch</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">©INFINI.LTD, All Rights Reserved.</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 -ku admin:db3e19a8c1c9f7755763 https://localhost:9200</span><br></pre></td></tr></table></figure><p>然后启动集群，默认监听在 9200 端口：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><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">❰xu❙~/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle❱✔≻ bin/Easysearch        (base) 13:50:14</span><br><span class="line">[2025-02-11T13:50:28,686][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] unable to load JNA native support library, native methods will be disabled.</span><br><span class="line">java.lang.UnsatisfiedLinkError: /Users/xu/Library/Caches/JNA/temp/jna114104068097844929.tmp: dlopen(/Users/xu/Library/Caches/JNA/temp/jna114104068097844929.tmp, 0x0001): tried: <span class="string">&#x27;/Users/xu/Library/Caches/JNA/temp/jna114104068097844929.tmp&#x27;</span> (fat file, but missing compatible architecture (have <span class="string">&#x27;i386,x86_64&#x27;</span>, need <span class="string">&#x27;arm64e&#x27;</span> or <span class="string">&#x27;arm64&#x27;</span>)), <span class="string">&#x27;/System/Volumes/Preboot/Cryptexes/OS/Users/xu/Library/Caches/JNA/temp/jna114104068097844929.tmp&#x27;</span> (no such file), <span class="string">&#x27;/Users/xu/Library/Caches/JNA/temp/jna114104068097844929.tmp&#x27;</span> (fat file, but missing compatible architecture (have <span class="string">&#x27;i386,x86_64&#x27;</span>, need <span class="string">&#x27;arm64e&#x27;</span> or <span class="string">&#x27;arm64&#x27;</span>))</span><br><span class="line">at jdk.internal.loader.NativeLibraries.load(Native Method) ~[?:?]</span><br><span class="line">at jdk.internal.loader.NativeLibraries<span class="variable">$NativeLibraryImpl</span>.open(Unknown Source) ~[?:?]</span><br><span class="line">at jdk.internal.loader.NativeLibraries.loadLibrary(Unknown Source) ~[?:?]</span><br><span class="line">at jdk.internal.loader.NativeLibraries.loadLibrary(Unknown Source) ~[?:?]</span><br><span class="line">at java.lang.ClassLoader.loadLibrary(Unknown Source) ~[?:?]</span><br><span class="line">at java.lang.Runtime.load0(Unknown Source) ~[?:?]</span><br><span class="line">at java.lang.System.load(Unknown Source) ~[?:?]</span><br><span class="line">at com.sun.jna.Native.loadNativeDispatchLibraryFromClasspath(Native.java:1018) ~[jna-5.5.0.jar:5.5.0 (b0)]</span><br><span class="line">at com.sun.jna.Native.loadNativeDispatchLibrary(Native.java:988) ~[jna-5.5.0.jar:5.5.0 (b0)]</span><br><span class="line">at com.sun.jna.Native.&lt;clinit&gt;(Native.java:195) ~[jna-5.5.0.jar:5.5.0 (b0)]</span><br><span class="line">at java.lang.Class.forName0(Native Method) ~[?:?]</span><br><span class="line">at java.lang.Class.forName(Unknown Source) ~[?:?]</span><br><span class="line">at org.Easysearch.bootstrap.Natives.&lt;clinit&gt;(Natives.java:30) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:95) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Bootstrap.setup(Bootstrap.java:163) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Bootstrap.init(Bootstrap.java:378) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Easysearch.init(Easysearch.java:169) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Easysearch.execute(Easysearch.java:160) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:71) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.cli.Command.mainWithoutErrorHandling(Command.java:112) ~[Easysearch-cli-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.cli.Command.main(Command.java:75) ~[Easysearch-cli-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Easysearch.main(Easysearch.java:125) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">at org.Easysearch.bootstrap.Easysearch.main(Easysearch.java:67) ~[Easysearch-1.10.1.jar:1.10.1]</span><br><span class="line">[2025-02-11T13:50:28,689][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot check <span class="keyword">if</span> running as root because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,689][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot install system call filter because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,689][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot register console handler because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,690][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot getrlimit RLIMIT_NPROC because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,690][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot getrlimit RLIMIT_AS because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,690][WARN ][o.e.b.Natives            ] [xudeMacBook-Pro.<span class="built_in">local</span>] cannot getrlimit RLIMIT_FSIZE because JNA is not available</span><br><span class="line">[2025-02-11T13:50:28,757][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] version[1.10.1], pid[18338], build[default/tar/1505f74d355bb2bac84162077fe66e9a32fc1be2/2025-01-24T21:02:00.632579Z], OS[Mac OS X/15.3/aarch64], JVM[Azul Systems, Inc./OpenJDK 64-Bit Server VM/17.0.13/17.0.13+11-LTS]</span><br><span class="line">[2025-02-11T13:50:28,757][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] JVM home [/Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/jdk]</span><br><span class="line">[2025-02-11T13:50:28,758][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] JVM arguments [-Xshare:auto, -Des.networkaddress.cache.ttl=60, -Des.networkaddress.cache.negative.ttl=10, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=<span class="literal">true</span>, -Dfile.encoding=UTF-8, -Djna.nosys=<span class="literal">true</span>, -XX:-OmitStackTraceInFastThrow, -XX:+ShowCodeDetailsInExceptionMessages, -Dio.netty.noUnsafe=<span class="literal">true</span>, -Dio.netty.noKeySetOptimization=<span class="literal">true</span>, -Dio.netty.recycler.maxCapacityPerThread=0, -Dio.netty.allocator.numDirectArenas=0, -Dlog4j.shutdownHookEnabled=<span class="literal">false</span>, -Dlog4j2.disable.jmx=<span class="literal">true</span>, -Xms1g, -Xmx1g, -XX:+UseG1GC, -XX:G1ReservePercent=25, -XX:InitiatingHeapOccupancyPercent=30, -Djava.io.tmpdir=/var/folders/ft/rfbtvz_n5l9dg007x5lsk7jw0000gn/T/Easysearch-134350959148498936, -Djava.security.manager=allow, -Djava.locale.providers=SPI,COMPAT, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -XX:MaxDirectMemorySize=536870912, -Des.path.home=/Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle, -Des.path.conf=/Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config, -Des.distribution.flavor=oss, -Des.distribution.type=tar, -Des.bundled_jdk=<span class="literal">false</span>]</span><br><span class="line">[2025-02-11T13:50:29,116][INFO ][c.i.s.s.t.SSLConfig      ] [xudeMacBook-Pro.<span class="built_in">local</span>] SSL dual mode is disabled</span><br><span class="line">[2025-02-11T13:50:29,117][INFO ][c.i.s.SecurityPlugin     ] [xudeMacBook-Pro.<span class="built_in">local</span>] ES Config path is /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config</span><br><span class="line">[2025-02-11T13:50:29,184][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] JVM supports TLSv1.3</span><br><span class="line">[2025-02-11T13:50:29,185][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] Config directory is /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/, from there the key- and truststore files are resolved relatively</span><br><span class="line">[2025-02-11T13:50:29,389][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] TLS Transport Client Provider : JDK</span><br><span class="line">[2025-02-11T13:50:29,389][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] TLS Transport Server Provider : JDK</span><br><span class="line">[2025-02-11T13:50:29,389][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] TLS HTTP Provider             : JDK</span><br><span class="line">[2025-02-11T13:50:29,389][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] Enabled TLS protocols <span class="keyword">for</span> transport layer : [TLSv1.3, TLSv1.2]</span><br><span class="line">[2025-02-11T13:50:29,389][INFO ][c.i.s.s.DefaultSecurityKeyStore] [xudeMacBook-Pro.<span class="built_in">local</span>] Enabled TLS protocols <span class="keyword">for</span> HTTP layer      : [TLSv1.3, TLSv1.2]</span><br><span class="line">[2025-02-11T13:50:29,391][INFO ][c.i.s.SecurityPlugin     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Clustername: Easysearch</span><br><span class="line">[2025-02-11T13:50:29,745][INFO ][o.e.j.JobSchedulerPlugin ] [xudeMacBook-Pro.<span class="built_in">local</span>] Loaded scheduler extension: Easysearch-index-management, index: .Easysearch-ilm-config</span><br><span class="line">[2025-02-11T13:50:29,748][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [aggs-matrix-stats]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [analysis-common]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [custom-codecs]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [geo]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [job-scheduler]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [lang-expression]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [lang-mustache]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [lang-painless]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [mapper-extras]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [parent-join]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [percolator]</span><br><span class="line">[2025-02-11T13:50:29,749][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [rank-eval]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [reindex]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [repository-s3]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [repository-url]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [security]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded module [transport-netty4]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [analysis-icu]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [analysis-ik]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [analysis-pinyin]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [analysis-stconvert]</span><br><span class="line">[2025-02-11T13:50:29,750][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [cross-cluster-replication]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [index-management]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [ingest-common]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [ingest-geoip]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [ingest-user-agent]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [knn]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [mapper-annotated-text]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [mapper-murmur3]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [mapper-size]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [sql]</span><br><span class="line">[2025-02-11T13:50:29,751][INFO ][o.e.p.PluginsService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] loaded plugin [transport-nio]</span><br><span class="line">[2025-02-11T13:50:29,759][INFO ][c.i.s.SecurityPlugin     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Disabled https compression by default to mitigate BREACH attacks. You can <span class="built_in">enable</span> it by setting <span class="string">&#x27;http.compression: true&#x27;</span> <span class="keyword">in</span> Easysearch.yml</span><br><span class="line">[2025-02-11T13:50:29,767][INFO ][o.e.e.NodeEnvironment    ] [xudeMacBook-Pro.<span class="built_in">local</span>] using [1] data paths, mounts [[/System/Volumes/Data (/dev/disk3s5)]], net usable_space [57gb], net total_space [926.3gb], types [apfs]</span><br><span class="line">[2025-02-11T13:50:29,767][INFO ][o.e.e.NodeEnvironment    ] [xudeMacBook-Pro.<span class="built_in">local</span>] heap size [1gb], compressed ordinary object pointers [<span class="literal">true</span>]</span><br><span class="line">[2025-02-11T13:50:29,784][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] node name [xudeMacBook-Pro.<span class="built_in">local</span>], node ID [w5zzEDPfTDSE30HWYkUNcw], cluster name [Easysearch], roles [master, remote_cluster_client, data, ingest]</span><br><span class="line">[2025-02-11T13:50:30,915][WARN ][c.i.s.c.Salt             ] [xudeMacBook-Pro.<span class="built_in">local</span>] If you plan to use field masking pls configure compliance salt e1ukloTxxxOgPquJ to be a random string of 16 chars length identical on all nodes</span><br><span class="line">[2025-02-11T13:50:30,921][INFO ][c.i.s.a.i.AuditLogging   ] [xudeMacBook-Pro.<span class="built_in">local</span>] Message routing enabled: <span class="literal">true</span></span><br><span class="line">[2025-02-11T13:50:30,945][INFO ][c.i.s.f.SecurityFilter   ] [xudeMacBook-Pro.<span class="built_in">local</span>] &lt;NONE&gt; indices are made immutable.</span><br><span class="line">[2025-02-11T13:50:31,104][INFO ][o.e.t.NettyAllocator     ] [xudeMacBook-Pro.<span class="built_in">local</span>] creating NettyAllocator with the following configs: [name=unpooled, suggested_max_allocation_size=256kb, factors=&#123;es.unsafe.use_unpooled_allocator=null, g1gc_enabled=<span class="literal">true</span>, g1gc_region_size=1mb, heap_size=1gb&#125;]</span><br><span class="line">[2025-02-11T13:50:31,132][INFO ][o.e.d.DiscoveryModule    ] [xudeMacBook-Pro.<span class="built_in">local</span>] using discovery <span class="built_in">type</span> [zen] and seed hosts providers [settings]</span><br><span class="line">[2025-02-11T13:50:31,250][WARN ][o.e.g.DanglingIndicesState] [xudeMacBook-Pro.<span class="built_in">local</span>] gateway.auto_import_dangling_indices is disabled, dangling indices will not be automatically detected or imported and must be managed manually</span><br><span class="line">[2025-02-11T13:50:31,335][WARN ][o.e.r.MethodHandlers     ] [xudeMacBook-Pro.<span class="built_in">local</span>] replace existing handler <span class="keyword">for</span> [/&#123;index&#125;/_search] <span class="keyword">for</span> method: GET existing:com.infinilabs.security.filter.SecurityRestFilter<span class="variable">$1</span>@41fbe8c0 to com.infinilabs.security.filter.SecurityRestFilter<span class="variable">$1</span>@1e8fd198</span><br><span class="line">[2025-02-11T13:50:31,335][WARN ][o.e.r.MethodHandlers     ] [xudeMacBook-Pro.<span class="built_in">local</span>] replace existing handler <span class="keyword">for</span> [/&#123;index&#125;/_search] <span class="keyword">for</span> method: POST existing:com.infinilabs.security.filter.SecurityRestFilter<span class="variable">$1</span>@4f75c627 to com.infinilabs.security.filter.SecurityRestFilter<span class="variable">$1</span>@1631a614</span><br><span class="line">[2025-02-11T13:50:31,344][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] initialized</span><br><span class="line">[2025-02-11T13:50:31,344][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] starting ...</span><br><span class="line">[2025-02-11T13:50:31,345][INFO ][o.e.i.r.a.s.RollupListener] [xudeMacBook-Pro.<span class="built_in">local</span>] Rollup listener start</span><br><span class="line">[2025-02-11T13:50:31,391][INFO ][o.e.t.TransportService   ] [xudeMacBook-Pro.<span class="built_in">local</span>] publish_address &#123;127.0.0.1:9300&#125;, bound_addresses &#123;[::1]:9300&#125;, &#123;127.0.0.1:9300&#125;</span><br><span class="line">[2025-02-11T13:50:31,473][WARN ][o.e.b.BootstrapChecks    ] [xudeMacBook-Pro.<span class="built_in">local</span>] system call filters failed to install; check the logs and fix your configuration or <span class="built_in">disable</span> system call filters at your own risk</span><br><span class="line">[2025-02-11T13:50:31,473][WARN ][o.e.b.BootstrapChecks    ] [xudeMacBook-Pro.<span class="built_in">local</span>] the default discovery settings are unsuitable <span class="keyword">for</span> production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured</span><br><span class="line">[2025-02-11T13:50:31,477][INFO ][o.e.c.c.ClusterBootstrapService] [xudeMacBook-Pro.<span class="built_in">local</span>] no discovery configuration found, will perform best-effort cluster bootstrapping after [3s] unless existing master is discovered</span><br><span class="line">[2025-02-11T13:50:34,481][INFO ][o.e.c.c.Coordinator      ] [xudeMacBook-Pro.<span class="built_in">local</span>] setting initial configuration to VotingConfiguration&#123;w5zzEDPfTDSE30HWYkUNcw&#125;</span><br><span class="line">[2025-02-11T13:50:34,576][INFO ][o.e.c.s.MasterService    ] [xudeMacBook-Pro.<span class="built_in">local</span>] elected-as-master ([1] nodes joined)[&#123;xudeMacBook-Pro.<span class="built_in">local</span>&#125;&#123;w5zzEDPfTDSE30HWYkUNcw&#125;&#123;KMu1pbs-RB2vaWucEO8dmg&#125;&#123;127.0.0.1&#125;&#123;127.0.0.1:9300&#125;&#123;dimr&#125; elect leader, _BECOME_MASTER_TASK_, _FINISH_ELECTION_], term: 1, version: 1, delta: master node changed &#123;previous [], current [&#123;xudeMacBook-Pro.<span class="built_in">local</span>&#125;&#123;w5zzEDPfTDSE30HWYkUNcw&#125;&#123;KMu1pbs-RB2vaWucEO8dmg&#125;&#123;127.0.0.1&#125;&#123;127.0.0.1:9300&#125;&#123;dimr&#125;]&#125;</span><br><span class="line">[2025-02-11T13:50:34,601][INFO ][o.e.c.c.CoordinationState] [xudeMacBook-Pro.<span class="built_in">local</span>] cluster UUID <span class="built_in">set</span> to [kJHbUT_zT2ionNVL9f3raQ]</span><br><span class="line">[2025-02-11T13:50:34,623][INFO ][o.e.c.s.ClusterApplierService] [xudeMacBook-Pro.<span class="built_in">local</span>] master node changed &#123;previous [], current [&#123;xudeMacBook-Pro.<span class="built_in">local</span>&#125;&#123;w5zzEDPfTDSE30HWYkUNcw&#125;&#123;KMu1pbs-RB2vaWucEO8dmg&#125;&#123;127.0.0.1&#125;&#123;127.0.0.1:9300&#125;&#123;dimr&#125;]&#125;, term: 1, version: 1, reason: Publication&#123;term=1, version=1&#125;</span><br><span class="line">[2025-02-11T13:50:34,627][INFO ][o.e.i.i.ManagedIndexCoordinator] [xudeMacBook-Pro.<span class="built_in">local</span>] Cache cluster manager node onClusterManager <span class="keyword">time</span>: 1739253034627</span><br><span class="line">[2025-02-11T13:50:34,637][INFO ][o.e.h.AbstractHttpServerTransport] [xudeMacBook-Pro.<span class="built_in">local</span>] publish_address &#123;127.0.0.1:9200&#125;, bound_addresses &#123;[::1]:9200&#125;, &#123;127.0.0.1:9200&#125;</span><br><span class="line">[2025-02-11T13:50:34,640][INFO ][o.e.n.Node               ] [xudeMacBook-Pro.<span class="built_in">local</span>] started</span><br><span class="line">[2025-02-11T13:50:34,640][INFO ][c.i.s.SecurityPlugin     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Node started</span><br><span class="line">[2025-02-11T13:50:34,640][INFO ][c.i.s.c.ConfigurationRepository] [xudeMacBook-Pro.<span class="built_in">local</span>] Will attempt to create index .security and default configs <span class="keyword">if</span> they are absent</span><br><span class="line">[2025-02-11T13:50:34,641][INFO ][c.i.s.SecurityPlugin     ] [xudeMacBook-Pro.<span class="built_in">local</span>] 3 Security modules loaded so far: [Module [<span class="built_in">type</span>=REST_MANAGEMENT_API, implementing class=com.infinilabs.security.dlic.rest.api.SecurityRestApiActions], Module [<span class="built_in">type</span>=MULTITENANCY, implementing class=com.infinilabs.security.configuration.PrivilegesInterceptorImpl], Module [<span class="built_in">type</span>=AUDITLOG, implementing class=com.infinilabs.security.auditlog.impl.AuditLogging]]</span><br><span class="line">[2025-02-11T13:50:34,641][INFO ][c.i.s.c.ConfigurationRepository] [xudeMacBook-Pro.<span class="built_in">local</span>] Background init thread started. Install default config?: <span class="literal">true</span></span><br><span class="line">[2025-02-11T13:50:34,645][INFO ][c.i.s.c.ConfigurationRepository] [xudeMacBook-Pro.<span class="built_in">local</span>] Wait <span class="keyword">for</span> cluster to be available ...</span><br><span class="line">[2025-02-11T13:50:34,670][INFO ][o.e.g.GatewayService     ] [xudeMacBook-Pro.<span class="built_in">local</span>] recovered [0] indices into cluster_state</span><br><span class="line">[2025-02-11T13:50:36,676][INFO ][o.w.a.d.Dictionary       ] [xudeMacBook-Pro.<span class="built_in">local</span>] try load config from /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/analysis-ik/IKAnalyzer.cfg.xml</span><br><span class="line">[2025-02-11T13:50:37,465][INFO ][o.e.c.m.MetadataCreateIndexService] [xudeMacBook-Pro.<span class="built_in">local</span>] [.security] creating index, cause [api], templates [], shards [1]/[1]</span><br><span class="line">[2025-02-11T13:50:37,470][INFO ][o.e.c.r.a.AllocationService] [xudeMacBook-Pro.<span class="built_in">local</span>] updating number_of_replicas to [0] <span class="keyword">for</span> indices [.security]</span><br><span class="line">[2025-02-11T13:50:37,657][INFO ][o.e.c.r.a.AllocationService] [xudeMacBook-Pro.<span class="built_in">local</span>] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.security][0]]]).</span><br><span class="line">[2025-02-11T13:50:37,679][INFO ][c.i.s.c.ConfigurationRepository] [xudeMacBook-Pro.<span class="built_in">local</span>] Index .security created?: <span class="literal">true</span></span><br><span class="line">[2025-02-11T13:50:37,685][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Will update <span class="string">&#x27;config&#x27;</span> with /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/security/config.yml and populate it with empty doc <span class="keyword">if</span> file missing and populateEmptyIfFileMissing=<span class="literal">false</span></span><br><span class="line">[2025-02-11T13:50:37,720][INFO ][o.e.c.m.MetadataMappingService] [xudeMacBook-Pro.<span class="built_in">local</span>] [.security/ADtiL1q3TUiUghNCrlfrHQ] create_mapping [_doc]</span><br><span class="line">[2025-02-11T13:50:37,779][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Doc with <span class="built_in">id</span> <span class="string">&#x27;config&#x27;</span> and version 2 is updated <span class="keyword">in</span> .security index.</span><br><span class="line">[2025-02-11T13:50:37,780][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Will update <span class="string">&#x27;role&#x27;</span> with /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/security/role.yml and populate it with empty doc <span class="keyword">if</span> file missing and populateEmptyIfFileMissing=<span class="literal">false</span></span><br><span class="line">[2025-02-11T13:50:37,786][INFO ][o.e.c.m.MetadataMappingService] [xudeMacBook-Pro.<span class="built_in">local</span>] [.security/ADtiL1q3TUiUghNCrlfrHQ] update_mapping [_doc]</span><br><span class="line">[2025-02-11T13:50:37,830][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Doc with <span class="built_in">id</span> <span class="string">&#x27;role&#x27;</span> and version 2 is updated <span class="keyword">in</span> .security index.</span><br><span class="line">[2025-02-11T13:50:37,831][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Will update <span class="string">&#x27;role_mapping&#x27;</span> with /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/security/role_mapping.yml and populate it with empty doc <span class="keyword">if</span> file missing and populateEmptyIfFileMissing=<span class="literal">false</span></span><br><span class="line">[2025-02-11T13:50:37,838][INFO ][o.e.c.m.MetadataMappingService] [xudeMacBook-Pro.<span class="built_in">local</span>] [.security/ADtiL1q3TUiUghNCrlfrHQ] update_mapping [_doc]</span><br><span class="line">[2025-02-11T13:50:37,885][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Doc with <span class="built_in">id</span> <span class="string">&#x27;role_mapping&#x27;</span> and version 2 is updated <span class="keyword">in</span> .security index.</span><br><span class="line">[2025-02-11T13:50:37,886][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Will update <span class="string">&#x27;user&#x27;</span> with /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/security/user.yml and populate it with empty doc <span class="keyword">if</span> file missing and populateEmptyIfFileMissing=<span class="literal">false</span></span><br><span class="line">[2025-02-11T13:50:37,898][INFO ][o.e.c.m.MetadataMappingService] [xudeMacBook-Pro.<span class="built_in">local</span>] [.security/ADtiL1q3TUiUghNCrlfrHQ] update_mapping [_doc]</span><br><span class="line">[2025-02-11T13:50:37,937][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Doc with <span class="built_in">id</span> <span class="string">&#x27;user&#x27;</span> and version 2 is updated <span class="keyword">in</span> .security index.</span><br><span class="line">[2025-02-11T13:50:37,937][INFO ][c.i.s.s.ConfigHelper     ] [xudeMacBook-Pro.<span class="built_in">local</span>] Will update <span class="string">&#x27;privilege&#x27;</span> with /Users/xu/Desktop/es/Easysearch-1.10.1-1978-mac-arm64-bundle/config/security/privilege.yml and populate it with empty doc <span class="keyword">if</span> file missing and populateEmptyIfFileMissing=<span class="literal">false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用 Postman 尝试连接，可以正常返回 HTTP 的响应：</p><p><img src="https://i-blog.csdnimg.cn/direct/59507653bfb74e90bab4f21b2b0adc73.png" alt="在这里插入图片描述"></p><p>这里就需要用到上次的那个密码，默认存放在 log&#x2F;initialize.log 下。</p><p><img src="https://i-blog.csdnimg.cn/direct/32a54c5760464860858e5ada2c08c98b.png" alt="在这里插入图片描述"></p><h3 id="Console"><a href="#Console" class="headerlink" title="Console"></a>Console</h3><p>下载二进制文件并且运行，服务监听在 9000 端口，通过这个页面连接 Easysearch，用来替代 kibana，个人觉得比 kibana 和 OpenSearch Dashboard 要好用，除了 Easysearch 之外，还可以连接 ES 和 OpenSearch。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">./console-mac-arm64             (base) 14:25:43</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">HOME: https://github.com/infinilabs/console/</span><br><span class="line"></span><br><span class="line">[CONSOLE] The easiest way to operate your own search platform, open-sourced under the GNU AGPLv3.</span><br><span class="line">[CONSOLE] 1.28.1#1978, 2025-01-25 11:40:24, 2025-12-31 10:10:10, b189b72c11a9, 8ecefd18d9eb, c511f8f4cab9</span><br><span class="line">[02-11 14:25:47] [INF] [env.go:216] configuration auto reload enabled</span><br><span class="line">[02-11 14:25:47] [INF] [env.go:222] watching config: /Users/xu/Desktop/es/console-1.28.1-1978-mac-arm64/config</span><br><span class="line">[02-11 14:25:47] [INF] [app.go:311] initializing console, pid: 20341</span><br><span class="line">[02-11 14:25:47] [INF] [app.go:312] using config: /Users/xu/Desktop/es/console-1.28.1-1978-mac-arm64/console.yml</span><br><span class="line">[02-11 14:25:47] [INF] [instance.go:100] workspace: /Users/xu/Desktop/es/console-1.28.1-1978-mac-arm64/data/console/nodes/culeqqr55o14utfde2a0</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:159] started module: badger</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:159] started module: kafka_queue</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:159] started module: setup</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:159] started module: setup</span><br><span class="line">[02-11 14:25:47] [INF] [web.go:226] web server listen at: http://0.0.0.0:9000</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:159] started module: web</span><br><span class="line">[02-11 14:25:47] [INF] [module.go:184] all modules are started</span><br><span class="line">[02-11 14:25:47] [INF] [app.go:537] console is up and running now.</span><br><span class="line">[02-11 14:28:03] [WRN] [schema.go:101] duplicated schema rbac-role, infini.sh/console/core/security-role</span><br><span class="line">[02-11 14:28:03] [WRN] [schema.go:101] duplicated schema rbac-user, infini.sh/console/core/security-user</span><br><span class="line">[02-11 14:28:03] [WRN] [schema.go:101] duplicated schema credential, infini.sh/framework/core/credential-credential</span><br><span class="line">[02-11 14:28:04] [INF] [module.go:187] loading [0] remote configs</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: merge_metrics</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: metadata_ingest</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: activity_ingest</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: merge_logging</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: merge_audit_log</span><br><span class="line">[02-11 14:28:04] [INF] [pipeline.go:418] creating pipeline: ingest_merged_requests</span><br><span class="line">[02-11 14:28:04] [INF] [load.go:69] loading permission file from /Users/xu/Desktop/es/console-1.28.1-1978-mac-arm64/config/permission.json</span><br><span class="line">[02-11 14:28:06] [INF] [pipeline.go:227] config changed, checking <span class="keyword">for</span> new pipeline configs, CREATE, config/system_config.yml</span><br><span class="line">[02-11 14:54:38] [ERR] [v0.go:521] invalid response: https://localhost:9200/.infini_metrics%2A/_search,&#123;<span class="string">&quot;aggs&quot;</span>:&#123;<span class="string">&quot;culf8bj55o14utfdf1u0&quot;</span>:&#123;<span class="string">&quot;aggs&quot;</span>:&#123;<span class="string">&quot;culf8bj55o14utfdf1ug&quot;</span>:&#123;<span class="string">&quot;aggs&quot;</span>:&#123;<span class="string">&quot;culf8bj55o14utfdf1v0&quot;</span>:&#123;<span class="string">&quot;aggs&quot;</span>:&#123;&#125;,<span class="string">&quot;terms&quot;</span>:&#123;<span class="string">&quot;field&quot;</span>:<span class="string">&quot;payload.elasticsearch.cluster_health.status&quot;</span>,<span class="string">&quot;missing&quot;</span>:<span class="string">&quot;&quot;</span>,<span class="string">&quot;size&quot;</span>:2&#125;&#125;&#125;,<span class="string">&quot;date_range&quot;</span>:&#123;<span class="string">&quot;field&quot;</span>:<span class="string">&quot;timestamp&quot;</span>,<span class="string">&quot;format&quot;</span>:<span class="string">&quot;yyyy-MM-dd&quot;</span>,<span class="string">&quot;ranges&quot;</span>:[&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-13d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-12d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-12d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-11d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-11d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-10d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-10d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-9d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-9d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-8d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-8d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-7d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-7d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-6d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-6d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-5d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-5d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-4d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-4d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-3d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-3d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-2d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-2d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now-1d/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now-1d/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now/d&quot;</span>&#125;,&#123;<span class="string">&quot;from&quot;</span>:<span class="string">&quot;now/d&quot;</span>,<span class="string">&quot;to&quot;</span>:<span class="string">&quot;now&quot;</span>&#125;],<span class="string">&quot;time_zone&quot;</span>:<span class="string">&quot;+08:00&quot;</span>&#125;&#125;&#125;,<span class="string">&quot;terms&quot;</span>:&#123;<span class="string">&quot;field&quot;</span>:<span class="string">&quot;metadata.labels.cluster_id&quot;</span>,<span class="string">&quot;size&quot;</span>:2&#125;&#125;&#125;,<span class="string">&quot;query&quot;</span>:&#123;<span class="string">&quot;bool&quot;</span>:&#123;<span class="string">&quot;filter&quot;</span>:[&#123;<span class="string">&quot;range&quot;</span>:&#123;<span class="string">&quot;timestamp&quot;</span>:&#123;<span class="string">&quot;gte&quot;</span>:<span class="string">&quot;now-15d&quot;</span>,<span class="string">&quot;lte&quot;</span>:<span class="string">&quot;now&quot;</span>&#125;&#125;&#125;],<span class="string">&quot;must&quot;</span>:[&#123;<span class="string">&quot;terms&quot;</span>:&#123;<span class="string">&quot;metadata.labels.cluster_id&quot;</span>:[<span class="string">&quot;infini_default_system_cluster&quot;</span>]&#125;&#125;,&#123;<span class="string">&quot;term&quot;</span>:&#123;<span class="string">&quot;metadata.category&quot;</span>:&#123;<span class="string">&quot;value&quot;</span>:<span class="string">&quot;elasticsearch&quot;</span>&#125;&#125;&#125;,&#123;<span class="string">&quot;term&quot;</span>:&#123;<span class="string">&quot;metadata.name&quot;</span>:&#123;<span class="string">&quot;value&quot;</span>:<span class="string">&quot;cluster_health&quot;</span>&#125;&#125;&#125;]&#125;&#125;,<span class="string">&quot;size&quot;</span>:0&#125;,&#123;<span class="string">&quot;error&quot;</span>:&#123;<span class="string">&quot;root_cause&quot;</span>:[&#123;<span class="string">&quot;type&quot;</span>:<span class="string">&quot;runtime_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Unable to unroll aggregation tree.  Aggregation [culf8bj55o14utfdf1u0] is of type [UnmappedTerms] which is currently unsupported.&quot;</span>&#125;],<span class="string">&quot;type&quot;</span>:<span class="string">&quot;runtime_exception&quot;</span>,<span class="string">&quot;reason&quot;</span>:<span class="string">&quot;Unable to unroll aggregation tree.  Aggregation [culf8bj55o14utfdf1u0] is of type [UnmappedTerms] which is currently unsupported.&quot;</span>&#125;,<span class="string">&quot;status&quot;</span>:500&#125;</span><br><span class="line">[02-11 14:59:18] [ERR] [config.go:46] agent config not found: missing field accessing <span class="string">&#x27;agent&#x27;</span></span><br><span class="line">[02-11 14:59:18] [INF] [certs.go:49] auto generating cert files</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/f5c1ef6500b24949b22c7aaff8c317b6.png"></p><p>查看集群信息，可以看指标，也可以看索引，kibana 有的功能这边基本都有：</p><p><img src="https://i-blog.csdnimg.cn/direct/31e40aee35944b6fa7e6920ae065cd39.png" alt="在这里插入图片描述"></p><p>同时提供 DSL 查询：<br><img src="https://i-blog.csdnimg.cn/direct/a8cf7751f52b4ea0a9ef59c769a27f58.png" alt="在这里插入图片描述"></p><h3 id="Gateway"><a href="#Gateway" class="headerlink" title="Gateway"></a>Gateway</h3><p>INFINI Gateway 是一个面向搜索场景的高性能数据网关，所有请求都经过网关处理后再转发到后端的搜索业务集群。基于 INFINI Gateway，可以实现索引级别的限速限流、常见查询的缓存加速、查询请求的审计、查询结果的动态修改等等。</p><p>Macbook 下有奇怪的安全规则，除了二进制文件之外，yml 文件经验也报错不安全。打开 “系统偏好设置”，进入 “安全性与隐私”，点击 “通用”，有 “仍然允许” 按钮，点击它即可。<br><img src="https://i-blog.csdnimg.cn/direct/24aacd91ee854915acae12662b75f853.png" alt="在这里插入图片描述"></p><p>然后按照 Easysearch 的配置进行修改，否则会连不上集群。就是这个报错：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">./gateway-mac-arm64                 (base) 14:51:14</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">HOME: https://github.com/infinilabs/gateway/</span><br><span class="line"></span><br><span class="line">[GATEWAY] A light-weight, powerful and high-performance search gateway, open-sourced under the GNU AGPLv3.</span><br><span class="line">[GATEWAY] 1.28.1#1978, 2025-01-24 18:34:08, 2025-12-31 10:10:10, 93fd9e32500a, 8ecefd18d9eb, c511f8f4cab9</span><br><span class="line">[02-11 14:51:17] [INF] [app.go:311] initializing gateway, pid: 22456</span><br><span class="line">[02-11 14:51:17] [INF] [app.go:312] using config: /Users/xu/Desktop/es/gateway-1.28.1-1978-mac-arm64/gateway.yml</span><br><span class="line">[02-11 14:51:17] [INF] [instance.go:100] workspace: /Users/xu/Desktop/es/gateway-1.28.1-1978-mac-arm64/data/gateway/nodes/culf6pb55o15fe01mtvg</span><br><span class="line">panic: Get <span class="string">&quot;http://localhost:9200&quot;</span>: EOF</span><br><span class="line"></span><br><span class="line">goroutine 433 [running]:</span><br><span class="line">infini.sh/framework/modules/elastic/common.InitClientWithConfig(&#123;&#123;&#123;0x14000810630, 0x4&#125;, 0x0, 0x0&#125;, &#123;0x103466e7c, 0x4&#125;, &#123;0x14000810630, 0x4&#125;, &#123;0x0, 0x0&#125;, ...&#125;)</span><br><span class="line">/root/go/src/infini.sh/framework/modules/elastic/common/config.go:89 +0x880</span><br><span class="line">infini.sh/framework/modules/elastic/common.InitElasticInstanceWithoutMetadata(&#123;&#123;&#123;0x14000810630, 0x4&#125;, 0x0, 0x0&#125;, &#123;0x103466e7c, 0x4&#125;, &#123;0x14000810630, 0x4&#125;, &#123;0x0, 0x0&#125;, ...&#125;)</span><br><span class="line">/root/go/src/infini.sh/framework/modules/elastic/common/config.go:199 +0x64</span><br><span class="line">infini.sh/framework/modules/elastic.initElasticInstances(&#123;0x14000826008?, 0x1e?, 0x103670600?&#125;, &#123;0x103466e7c, 0x4&#125;)</span><br><span class="line">/root/go/src/infini.sh/framework/modules/elastic/module.go:194 +0x7c</span><br><span class="line">infini.sh/framework/modules/elastic.(*ElasticModule).Setup(0x140006fa120?)</span><br><span class="line">/root/go/src/infini.sh/framework/modules/elastic/module.go:215 +0x124</span><br><span class="line">infini.sh/framework/core/module.Start()</span><br><span class="line">/root/go/src/infini.sh/framework/core/module/module.go:123 +0x238</span><br><span class="line">main.start()</span><br><span class="line">/root/go/src/infini.sh/gateway/main.go:72 +0x1c</span><br><span class="line">infini.sh/framework.(*App).run(0x14000332500)</span><br><span class="line">/root/go/src/infini.sh/framework/app.go:493 +0x160</span><br><span class="line">created by infini.sh/framework.(*App).Start <span class="keyword">in</span> goroutine 1</span><br><span class="line">/root/go/src/infini.sh/framework/app.go:429 +0xb4</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/ebcac27064884421860bbc6e60d2155b.png" alt="在这里插入图片描述"><br>修改配置文件之后，启动日志如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">./gateway-mac-arm64                 (base) 14:53:07</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">HOME: https://github.com/infinilabs/gateway/</span><br><span class="line"></span><br><span class="line">[GATEWAY] A light-weight, powerful and high-performance search gateway, open-sourced under the GNU AGPLv3.</span><br><span class="line">[GATEWAY] 1.28.1#1978, 2025-01-24 18:34:08, 2025-12-31 10:10:10, 93fd9e32500a, 8ecefd18d9eb, c511f8f4cab9</span><br><span class="line">[02-11 14:53:16] [INF] [app.go:311] initializing gateway, pid: 22599</span><br><span class="line">[02-11 14:53:16] [INF] [app.go:312] using config: /Users/xu/Desktop/es/gateway-1.28.1-1978-mac-arm64/gateway.yml</span><br><span class="line">[02-11 14:53:16] [INF] [instance.go:100] workspace: /Users/xu/Desktop/es/gateway-1.28.1-1978-mac-arm64/data/gateway/nodes/culf6pb55o15fe01mtvg</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: badger</span><br><span class="line">[02-11 14:53:16] [INF] [api.go:214] <span class="built_in">local</span> ips: 192.168.5.53</span><br><span class="line">[02-11 14:53:16] [INF] [api.go:312] api server listen at: http://0.0.0.0:2900</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: api</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: disk_queue</span><br><span class="line">[02-11 14:53:16] [INF] [actions.go:420] elasticsearch [logging-server] is available</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: elasticsearch</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: kafka_queue</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: queue</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: redis</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: s3</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: simple_stats</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: task</span><br><span class="line">[02-11 14:53:16] [INF] [actions.go:420] elasticsearch [prod] is available</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: pipeline_logging_merge</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: ingest_pipeline_logging</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: async_messages_merge</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: metrics_merge</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: request_logging_merge</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: ingest_merged_requests</span><br><span class="line">[02-11 14:53:16] [INF] [pipeline.go:418] creating pipeline: async_ingest_bulk_requests</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:159] started module: pipeline</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:178] started plugin: floating_ip</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:178] started plugin: force_merge</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:178] started plugin: metrics</span><br><span class="line">[02-11 14:53:16] [INF] [module.go:178] started plugin: statsd</span><br><span class="line">[02-11 14:53:17] [INF] [reverseproxy.go:299] elasticsearch [prod] hosts: [] =&gt; [127.0.0.1:9200]</span><br><span class="line">[02-11 14:53:17] [INF] [entry.go:420] entry [my_es_entry] listen at: http://0.0.0.0:8000</span><br><span class="line">[02-11 14:53:17] [INF] [module.go:178] started plugin: gateway</span><br><span class="line">[02-11 14:53:17] [INF] [module.go:184] all modules are started</span><br><span class="line">[02-11 14:53:17] [INF] [app.go:537] gateway is up and running now.</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>认证之后，可以从 Gateway 代理到 Easysearch：<br><img src="https://i-blog.csdnimg.cn/direct/b30d52d702d34054a51c0717e0c8ba8a.png" alt="在这里插入图片描述"></p><h4 id="Gateway-注册到-Console"><a href="#Gateway-注册到-Console" class="headerlink" title="Gateway 注册到 Console"></a>Gateway 注册到 Console</h4><p>注册时候用的是 2900 端口，如果注册到 8000 是无法连接的。<br><img src="https://i-blog.csdnimg.cn/direct/2811c9077e0c497f9d3717b6778a27f6.png" alt="在这里插入图片描述"></p><p>注册完成就会显示 Gateway 的信息：<br><img src="https://i-blog.csdnimg.cn/direct/6f80928712d7410b842013aaa825aa94.png" alt="在这里插入图片描述"></p><p>本文围绕在 Mac 系统上安装、配置和使用 Easysearch 展开，详细介绍了各组件的安装过程、遇到的问题及解决办法，以下是详细总结：</p><h3 id="1-安装背景与前期准备"><a href="#1-安装背景与前期准备" class="headerlink" title="1. 安装背景与前期准备"></a>1. 安装背景与前期准备</h3><p>在学习阶段缺少服务器时，可使用 Mac 进行 Easysearch 的安装学习，但官网未提供 Mac 版安装教程。Easysearch 运行依赖 Java，程序启动时会从当前目录的 JDK 查找 Java，即便环境变量已配置也可能无法找到，解决办法有：一是下载 JDK 二进制文件，重命名为“jdk”放在 Easysearch 根目录；二是下载自带 JDK 的 Easysearch bundle 包。</p><h3 id="2-Easysearch-安装与启动"><a href="#2-Easysearch-安装与启动" class="headerlink" title="2. Easysearch 安装与启动"></a>2. Easysearch 安装与启动</h3><ul><li><strong>初始化</strong>：执行<code>bin/initialize.sh</code>脚本设置 TLS 证书和集群密码，初始化过程会提示覆盖证书和密码的风险，需确认是否继续，并可选择是否将凭证记录到日志文件。初始化成功后会显示集群的密码和连接信息，如<code>curl -ku admin:db3e19a8c1c9f7755763 https://localhost:9200</code>。</li><li><strong>启动集群</strong>：启动命令为<code>bin/Easysearch</code>，默认监听 9200 端口。启动过程中可能出现无法加载 JNA 本地支持库的警告，但不影响后续启动和使用。</li></ul><h3 id="3-连接与验证"><a href="#3-连接与验证" class="headerlink" title="3. 连接与验证"></a>3. 连接与验证</h3><p>使用 Postman 尝试连接，需用到初始化时生成的密码（默认存放在<code>log/initialize.log</code>），可正常返回 HTTP 响应，验证了 Easysearch 集群的可访问性。</p><h3 id="4-Console-使用"><a href="#4-Console-使用" class="headerlink" title="4. Console 使用"></a>4. Console 使用</h3><p>下载并运行二进制文件<code>./console-mac-arm64</code>，服务监听在 9000 端口，可替代 Kibana 连接 Easysearch、ES 和 OpenSearch。运行过程中会有部分警告信息，如重复的架构定义，但不影响基本功能使用，还可查看集群信息和进行 DSL 查询。</p><h3 id="5-Gateway-使用"><a href="#5-Gateway-使用" class="headerlink" title="5. Gateway 使用"></a>5. Gateway 使用</h3><ul><li><strong>配置问题与解决</strong>：INFINI Gateway 是高性能数据网关，Mac 上运行时可能因安全规则报错，需在“系统偏好设置 - 安全性与隐私 - 通用”中点击“仍然允许”。若配置不正确会出现连接不上集群的错误，修改配置文件后可正常启动。</li><li><strong>启动与功能验证</strong>：启动命令为<code>./gateway-mac-arm64</code>，启动后各模块和插件依次启动，可实现索引级别的限速限流、缓存加速、查询审计等功能。认证后可从 Gateway 代理到 Easysearch，还可将 Gateway 注册到 Console。</li></ul>]]></content>
    
    
    <summary type="html">在 MacBook Pro 上快速搭建 Easysearch 本地学习开发环境</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>群晖套娃飞牛OS（FnOS）</title>
    <link href="https://blog.no-claw.com/posts/1eb7a4c5/"/>
    <id>https://blog.no-claw.com/posts/1eb7a4c5/</id>
    <published>2025-02-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>头段实在飞牛实在炒的有有点多，也安装尝试了下，同时也做了虚拟机的测评。</p><p>不过飞牛使用下来还是影音刮削的功能还是不错的，所以觉得把飞牛装在虚拟机里只使用影音的功能。</p><p>于是这个是一个使用群晖虚拟机安装飞牛 OS 的的过程。</p><p>在某个时间点的更新之后，群晖安装 debian 类虚拟机遇到问题，尽管看报错是正在加载，但是一直都没有响应。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328205942850.png" alt="image-20250328205942850"></p><p>在多次尝试之后，发现如下组合可以不卡代码，视频卡使用 vga，机器类型使用 Q35</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210137030.png" alt="image-20250328210137030"></p><p>查了下 GPT 的说法如下：</p><blockquote><table><thead><tr><th>选项</th><th>说明</th></tr></thead><tbody><tr><td><code>cirrus</code></td><td>非常基础的显卡，兼容性强，但仅支持 800x600 或 1024x768 的低分辨率，性能差。</td></tr><tr><td><code>vga</code></td><td>模拟标准 VGA，支持更多分辨率，但也比较基础。</td></tr><tr><td><code>vmvga</code></td><td>模拟 VMware 的显卡，适用于装有 VMware Tools 的系统，和 Chromium&#x2F;FnOS 相对兼容较好。</td></tr></tbody></table><table><thead><tr><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>PC</code></td><td>传统的 Intel i440FX 芯片组，兼容性强但比较老旧（类似 90 年代主板结构）</td></tr><tr><td><code>Q35</code></td><td>模拟 Intel Q35 芯片组，支持 PCIe、AHCI、现代硬件，推荐给新系统（如 Win10+、FnOS、Linux）</td></tr></tbody></table></blockquote><p>飞牛要直通两个盘，一个是系统盘，一个是数据盘。当然如果你只使用 SMB 做数据盘那也没问题，只是一直会提示你要加一个盘。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210704523.png" alt="image-20250328210704523"></p><p>我主要使用影视，就只能安装在数据盘了，</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210956758.png" alt="image-20250328210956758"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210956758.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210843980.png" alt="image-20250328210843980"></p><p>创建媒体库，选择 SMB 文件夹：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328210908955.png" alt="image-20250328210908955"></p><p>缺点是这套不能直通核显给 FnOS，所以用浏览器看的时候 CPU 是满的，但是不耽误客户端解码。</p><p>当贝桌面下载飞牛 TV，然后使用手机飞牛账户绑定。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328211025340.png" alt="image-20250328211025340"></p><p>放了一个 4K 版本的哪吒，不卡顿～</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250328211151213.png" alt="image-20250328211151213"></p>]]></content>
    
    
    <summary type="html">在群晖虚拟机中套娃安装飞牛 NAS 系统的实践记录。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="NAS" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/NAS/"/>
    
    
    <category term="家庭网络" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C/"/>
    
    <category term="群晖" scheme="https://blog.no-claw.com/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>老式打印机改 Airprint 之 cups</title>
    <link href="https://blog.no-claw.com/posts/6fcedbb0/"/>
    <id>https://blog.no-claw.com/posts/6fcedbb0/</id>
    <published>2025-01-19T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多年以前就想把家里的老式打印机改成无线，前前后后摸索了这些方案：</p><ol><li>用小白盒连接路由器：其实这个思路了网络打印机很类似，就是打印机 over IP，企业里几乎也都是这样的做法。缺点就是需要客户端安装驱动，所以相比之下就牺牲了移动端。</li><li>windows&#x2F;MacOS 共享：由于缺少 airprint，所以 Apple 设备无法使用隔空打印。其实 Windows 的兼容性是最好的。</li><li>在 OpenWrt 上安装 Cups 驱动，然后打印机接路由器当做无线使用。<span id="more"></span>感谢这篇文章，给了我很大的帮助：<a href="https://www.bilibili.com/opus/720655857020305463">https://www.bilibili.com/opus/720655857020305463</a></li></ol><p>我的方案是在打印机接群晖，然后使用 Docker 运行 Cups，来支持 Airprint。</p><p>虽然群晖自己支持了 cups，但是驱动不全，联想的打印机基本没有驱动，换几个其他的打印机型号也无法正确驱动起来，反而因为指令集冲突打印机一直在出空白页。</p><p>于是，打上了 docker 的主意。。。。</p><p>因为懒，也觉得没必要做数据映射：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> docker run -d --name=airprint --net=host --privileged=<span class="literal">true</span> -e TZ=<span class="string">&quot;Asia/Shanghai&quot;</span> -e CUPSADMIN=<span class="string">&quot;admin&quot;</span> -e CUPSPASSWORD=<span class="string">&quot;pass&quot;</span> -e HOST_OS=<span class="string">&quot;Synology&quot;</span> -e TCP_PORT_631=<span class="string">&quot;631&quot;</span> chuckcharlie/cups-avahi-airprint:latest</span><br></pre></td></tr></table></figure><p>从 631 端口进去 web 页，</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224165925487.png" alt="image-20250224165925487"></p><p>选择识别的打印机：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164153529.png" alt="image-20250224164153529"></p><p>填写信息，选择共享这个打印机。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164118890.png" alt="image-20250224164118890"></p><p>没有打印机的驱动，所以我选了兄弟的。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164059690.png" alt="image-20250224164059690"></p><p>打印机信息一览：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164032734.png" alt="image-20250224164032734"></p><p>一个小插曲：</p><p>Mac 升级之后把高级选项弄丢了，需要在这里邮件，选择自定义工具栏</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164235899.png" alt="image-20250224164235899"></p><p>要把 logo 拖放到 2 处而不是 1 处，这个设计很反人类。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164221298.png" alt="image-20250224164221298"></p><p>主要原因是一开始使用其他的 docker 镜像无法识别打印机，所以在这里使用 http 和 ipp 添加</p><p>http 的这么添加：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164439409.png" alt="image-20250224164439409"></p><p>ipp 的把这串输入到浏览器,MacOS 可以，手机和 Ipad 不行：</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">ipp://192.168.5.171:631/printers/Lenovo_M7400_Pro</span><br></pre></td></tr></table></figure><p>这俩 docker 怎么都搜不到打印机（iPhone 不行，Window 可以，Mac 可以用上述办法添加），踩了几个小时的坑：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164625022.png" alt="image-20250224164625022"></p><p>换了最上边那个容器之后全平台都可以了：</p><p>第二个就是我的打印机，第一个是群晖自己 cups 映射出来的，有 bug systemctl stop cupsd 也关不掉，不过也没啥影响。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164735702.png" alt="image-20250224164735702"></p><p>MacOS 结果：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224164851437.png"></p><p>Iphone 默认无法选择打印机，只能点击分享，然后下拉菜单选择打印：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224165243296.png" alt="image-20250224165243296"></p><p>Iphone 默认只支持隔空打印，但是使用 Cups 之后我们的打印机不在列表中，但是也能正常的使用了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224165251869.png" alt="image-20250224165251869"></p><p>整了这么多测试页，主打一个折腾开心：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224165620697.png" alt="image-20250224165620697"></p><p>最后还是有一个小问题，就是打印机由于关机或者拔掉 USB 的再重启的话，这个 docker 服务没有轮训机制，所以如果不常用的话，就需要每次打开打印机之后再手动运行重启下容器。</p><p>虽然现在的打印机都支持了 Airpint，cups 虽然已经成为了历史了，这么做算是圆了一个以前折腾的梦吧。</p><p>写了一个重启 CUPS docker 的 web：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224175113482.png" alt="image-20250224175113482"></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Restart Container<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: Arial, sans-serif;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: <span class="number">#f4f4f9</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-class">.container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: white;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">2rem</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">border-radius</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">8px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.1</span>);</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">h1</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">color</span>: <span class="number">#333</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin-bottom</span>: <span class="number">1.5rem</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-id">#restart-btn</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: <span class="number">#007bff</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">border</span>: none;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">0.75rem</span> <span class="number">1.5rem</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-size</span>: <span class="number">1rem</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">cursor</span>: pointer;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">transition</span>: background-color <span class="number">0.3s</span> ease;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-id">#restart-btn</span><span class="selector-pseudo">:hover</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: <span class="number">#0056b3</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">      <span class="selector-id">#status</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin-top</span>: <span class="number">1.5rem</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-size</span>: <span class="number">1.1rem</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">color</span>: <span class="number">#555</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Restart Docker Container<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">&quot;restart-btn&quot;</span>&gt;</span>重启打印机服务<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;status&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">document</span></span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">getElementById</span>(<span class="string">&quot;restart-btn&quot;</span>)</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">addEventListener</span>(<span class="string">&quot;click&quot;</span>, <span class="title function_">async</span> () =&gt; &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="keyword">const</span> statusElement = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;status&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">          statusElement.<span class="property">textContent</span> = <span class="string">&quot;Restarting...&quot;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">          <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;/restart&quot;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">            &#125;);</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">const</span> result = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span></span><br><span class="line"><span class="language-javascript">            statusElement.<span class="property">textContent</span> = result.<span class="property">message</span>;</span></span><br><span class="line"><span class="language-javascript">          &#125; <span class="keyword">catch</span> (error) &#123;</span></span><br><span class="line"><span class="language-javascript">            statusElement.<span class="property">textContent</span> = <span class="string">&quot;Failed to restart container.&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">          &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;);</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>后端使用 Flask：</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></pre></td><td class="code"><pre><span class="line">from flask import Flask, render_template, request, jsonify</span><br><span class="line">import docker</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">client = docker.from_env()</span><br><span class="line"></span><br><span class="line">@app.route(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">def index():</span><br><span class="line">    <span class="built_in">return</span> render_template(<span class="string">&#x27;index.html&#x27;</span>)</span><br><span class="line"></span><br><span class="line">@app.route(<span class="string">&#x27;/restart&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>])</span><br><span class="line">def restart_container():</span><br><span class="line">    container_name = <span class="string">&#x27;airprint&#x27;</span>  <span class="comment"># 你要重启的容器名字</span></span><br><span class="line">    try:</span><br><span class="line">        container = client.containers.get(container_name)</span><br><span class="line">        container.restart()</span><br><span class="line">        <span class="built_in">return</span> jsonify(&#123;<span class="string">&#x27;status&#x27;</span>: <span class="string">&#x27;success&#x27;</span>, <span class="string">&#x27;message&#x27;</span>: f<span class="string">&#x27;Container &#123;container_name&#125; restarted successfully!&#x27;</span>&#125;)</span><br><span class="line">    except Exception as e:</span><br><span class="line">        <span class="built_in">return</span> jsonify(&#123;<span class="string">&#x27;status&#x27;</span>: <span class="string">&#x27;error&#x27;</span>, <span class="string">&#x27;message&#x27;</span>: str(e)&#125;), 500</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">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=8000)</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">docker buildx build --platform linux/amd64 -t printer:latest --load .</span><br><span class="line">docker save printer:latest &gt; 1.tar</span><br><span class="line">docker run -d --name printer_container -p 8888:8000 -v /var/run/docker.sock:/var/run/docker.sock --restart unless-stopped printer:latest</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250224190546784.png" alt="image-20250224190546784"></p>]]></content>
    
    
    <summary type="html">用 CUPS 将老式打印机改造为支持 AirPrint 的无线打印机</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="打印机" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E6%89%93%E5%8D%B0%E6%9C%BA/"/>
    
    
    <category term="打印机" scheme="https://blog.no-claw.com/tags/%E6%89%93%E5%8D%B0%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>酒店机场免费WIFI不弹认证？用这个方法</title>
    <link href="https://blog.no-claw.com/posts/c39ca3b5/"/>
    <id>https://blog.no-claw.com/posts/c39ca3b5/</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>现在免费的 Wi-FI 还可以放心用,只是需要填写认证信息,一般来说连接 WI-FI 时候会自动弹窗提示输入手机号,不过笔者遇到几次不弹窗又无法上网的尴尬情况:</p><p>解决方式如下,在浏览器输入如下网址:</p><p>t.cn: 实测桔子酒店和重庆机场都能用</p><span id="more"></span><p>1.1.1.1:iphone 在重庆机场也能用</p><p>captive.apple.com:适用于 Apple 设备</p><p>上次在武汉机场也有提示, <a href="http://www.whairport.com/">www.whairport.com</a><br><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/09ca9403-c558-4b05-b008-aa0d416b29a6.png" alt="09ca9403-c558-4b05-b008-aa0d416b29a6"></p>]]></content>
    
    
    <summary type="html">解决酒店机场免费 WiFi 连接后不弹出认证页面的方法</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>记一次消防演练</title>
    <link href="https://blog.no-claw.com/posts/cd8d8984/"/>
    <id>https://blog.no-claw.com/posts/cd8d8984/</id>
    <published>2024-11-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>写字楼物业组织了消防演练，也是第一次使用真实的灭火器，当然还是希望永远都不要再用到的好。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/f889bea8-3447-4d63-9cb9-6bf09c9523ed.png" alt="图片"><br>这次使用的是干粉灭火器，也只能覆盖火苗降低温度，然后还需要使用水来彻底灭火，使用起来很简单。需要提醒的是，使用的时候不要距离太近。</p><p>消防人员交了四字口诀“提拔握压”</p><p>提：单手提起灭火器，手提提把，保持水平垂直</p><span id="more"></span><p>拔：拔出保险销，注意另外一只手不要按到压把，否则无法拔出</p><p>握：握住喷管的最前端，控制好方向</p><p>压：压住灭火器的开关，喷出干粉灭火</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/8d45a2ae-5f74-41bc-ae91-b3f2bb78c32b.png" alt="8d45a2ae-5f74-41bc-ae91-b3f2bb78c32b"><br>体验了一次火灾模拟，每人分发了湿毛巾捂住口鼻（还是很呛），出来的时候感觉空气都是甜的。只能说烟熏比较火情更为恐怖。</p><p>除此之外，还有消防车的喷水演示，从图片中可以看到，这个射程还是很远的。</p><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/22e0471b-9857-4905-a748-587fba7c7dff.png" alt="22e0471b-9857-4905-a748-587fba7c7dff"><br>其实大多数人对火灾没啥概念，无非是偶尔看到哪里哪里又着火了之类的，还是防患于未然吧。</p>]]></content>
    
    
    <summary type="html">记录写字楼消防演练经历，第一次使用真实灭火器</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>Markdown 完全指南</title>
    <link href="https://blog.no-claw.com/posts/84c20813/"/>
    <id>https://blog.no-claw.com/posts/84c20813/</id>
    <published>2024-09-15T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>Markdown 是一种轻量级的标记语言，广泛应用于编写文档、博客、README 文件等。由于其简单的语法和良好的可读性，Markdown 已成为技术人员和写作者常用的文档撰写工具之一。本教程将介绍 Markdown 的常用语法、进阶用法及各种扩展功能，帮助你快速掌握这项技能。</p><h2 id="1-标题"><a href="#1-标题" class="headerlink" title="1. 标题"></a>1. 标题</h2><p>Markdown 使用 <code>#</code> 符号表示标题，<code>#</code> 的数量决定标题的级别，从一级标题到六级标题不等。</p> <span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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><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">###### 六级标题</span><br></pre></td></tr></table></figure><h2 id="2-段落与换行"><a href="#2-段落与换行" class="headerlink" title="2. 段落与换行"></a>2. 段落与换行</h2><p>直接输入文字即为段落，段落之间需要空行隔开。行内换行需要在行尾加两个空格。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><br><span class="line">这是同一段中的新行。</span><br></pre></td></tr></table></figure><h2 id="3-强调"><a href="#3-强调" class="headerlink" title="3. 强调"></a>3. 强调</h2><p>Markdown 支持文本加粗、斜体及同时加粗斜体。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">_斜体_</span><br><span class="line">**加粗**</span><br><span class="line">**_加粗并斜体_**</span><br></pre></td></tr></table></figure><p>效果：</p><p><em>斜体</em><br><strong>加粗</strong><br><strong>*加粗并斜体*</strong></p><h2 id="4-引用"><a href="#4-引用" class="headerlink" title="4. 引用"></a>4. 引用</h2><p>使用 <code>&gt;</code> 符号可以创建引用块，并且可以嵌套。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt; 这是一个引用。</span><br><span class="line">&gt;</span><br><span class="line">&gt; &gt; 这是嵌套引用。</span><br></pre></td></tr></table></figure><h2 id="5-列表"><a href="#5-列表" class="headerlink" title="5. 列表"></a>5. 列表</h2><h4 id="无序列表"><a href="#无序列表" class="headerlink" title="无序列表"></a>无序列表</h4><p>无序列表使用 <code>-</code>、<code>+</code> 或 <code>*</code> 作为列表标记。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- 项目一</span><br><span class="line">- 项目二</span><br><span class="line">  - 子项目</span><br></pre></td></tr></table></figure><p>效果：</p><ul><li>项目一</li><li>项目二<ul><li>子项目</li></ul></li></ul><h4 id="有序列表"><a href="#有序列表" class="headerlink" title="有序列表"></a>有序列表</h4><p>有序列表使用数字加 <code>.</code> 来表示。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 项目一</span><br><span class="line">2. 项目二</span><br><span class="line">   1. 子项目</span><br></pre></td></tr></table></figure><p>效果：</p><ol><li>项目一</li><li>项目二<ol><li>子项目</li></ol></li></ol><h2 id="6-链接与图片"><a href="#6-链接与图片" class="headerlink" title="6. 链接与图片"></a>6. 链接与图片</h2><p>使用 <code>[文本](URL &quot;文本&quot;)</code> 语法创建超链接，使用 <code>![图片描述](图片地址)</code> 插入图片。</p><figure class="highlight plaintext"><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">[Google](https://www.google.com &quot;Google&quot;)</span><br><span class="line">![Markdown Logo](https://markdown-here.com/img/icon256.png)</span><br></pre></td></tr></table></figure><h2 id="7-代码块"><a href="#7-代码块" class="headerlink" title="7. 代码块"></a>7. 代码块</h2><p>行内代码使用反引号 &#96;&#96;&#96;&#96;，多行代码使用三个反引号包围，并且可以指定代码语言以启用语法高亮。</p><figure class="highlight plaintext"><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">def hello_world():</span><br><span class="line">    print(&quot;Hello, World!&quot;)</span><br></pre></td></tr></table></figure><h2 id="8-表格"><a href="#8-表格" class="headerlink" title="8. 表格"></a>8. 表格</h2><p>使用 <code>|</code> 和 <code>-</code> 创建表格，并通过 <code>:</code> 控制对齐方式。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">| 名字 | 年龄 | 性别 |</span><br><span class="line">| :--- | :--: | ---: |</span><br><span class="line">| 张三 |  25  |   男 |</span><br><span class="line">| 李四 |  30  |   女 |</span><br></pre></td></tr></table></figure><p>效果：</p><table><thead><tr><th align="left">名字</th><th align="center">年龄</th><th align="right">性别</th></tr></thead><tbody><tr><td align="left">张三</td><td align="center">25</td><td align="right">男</td></tr><tr><td align="left">李四</td><td align="center">30</td><td align="right">女</td></tr></tbody></table><h2 id="9-水平分割线"><a href="#9-水平分割线" class="headerlink" title="9. 水平分割线"></a>9. 水平分割线</h2><p>使用 <code>---</code>、<code>***</code> 或 <code>___</code> 创建分割线。</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">---</span><br></pre></td></tr></table></figure><p>效果：</p><hr><h2 id="10-转义字符"><a href="#10-转义字符" class="headerlink" title="10. 转义字符"></a>10. 转义字符</h2><p>使用反斜杠 <code>\</code> 来转义 Markdown 特殊符号。</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">\*这是不被解析的星号\*</span><br></pre></td></tr></table></figure><p>效果：</p><p><em>这是不被解析的星号</em></p><h2 id="11-注脚"><a href="#11-注脚" class="headerlink" title="11. 注脚"></a>11. 注脚</h2><p>Markdown 支持注脚功能，通过 <code>[^注脚]</code> 来实现，注脚内容放在文档底部。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">这是一个带注脚的例子[^1]。</span><br><span class="line"></span><br><span class="line">[^1]: 这是注脚的内容。</span><br></pre></td></tr></table></figure><p>效果：</p><p>这是一个带注脚的例子^1。</p><h2 id="12-任务列表"><a href="#12-任务列表" class="headerlink" title="12. 任务列表"></a>12. 任务列表</h2><p>Markdown 中可以创建任务列表，常用于跟踪任务状态。</p><figure class="highlight plaintext"><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><br><span class="line">- [x] 已完成任务</span><br></pre></td></tr></table></figure><h2 id="13-内部链接（锚点链接）"><a href="#13-内部链接（锚点链接）" class="headerlink" title="13. 内部链接（锚点链接）"></a>13. 内部链接（锚点链接）</h2><p>可以通过 <code>#</code> 创建文档内部的跳转链接，常用于创建目录或快速导航。</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">[跳转到高级用法](#高阶用法 &quot;跳转到高级用法&quot;)</span><br></pre></td></tr></table></figure><h2 id="14-LaTeX-公式"><a href="#14-LaTeX-公式" class="headerlink" title="14. LaTeX 公式"></a>14. LaTeX 公式</h2><p>Markdown 支持 LaTeX 数学公式，可以通过 <code>$</code> 或 <code>$$</code> 来包围公式内容。</p><figure class="highlight latex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">$</span>E=mc<span class="built_in">^</span>2<span class="built_in">$</span></span><br><span class="line"></span><br><span class="line">这是块级公式：</span><br><span class="line"></span><br><span class="line"><span class="built_in">$</span><span class="built_in">$</span></span><br><span class="line"><span class="keyword">\sum</span><span class="built_in">_</span>&#123;i=1&#125;<span class="built_in">^</span>n a<span class="built_in">_</span>i<span class="built_in">^</span>2</span><br><span class="line"><span class="built_in">$</span><span class="built_in">$</span></span><br></pre></td></tr></table></figure><p>效果：</p><p>这是行内公式：</p><p>这是块级公式：</p><p>$$<br>\sum_{i&#x3D;1}^n a_i^2<br>$$</p><h2 id="15-Mermaid-流程图"><a href="#15-Mermaid-流程图" class="headerlink" title="15. Mermaid 流程图"></a>15. Mermaid 流程图</h2><p>借助 Mermaid，可以在 Markdown 中创建流程图或时序图等可视化内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/Users/xu/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_neghhff72jo721_cb5c/temp/InputTemp/b699a258-8d44-4dbb-8c7c-ea70f37fa743.png```mermaid</span><br><span class="line">graph TD;</span><br><span class="line">    A--&gt;B;</span><br><span class="line">    A--&gt;C;</span><br><span class="line">    B--&gt;D;</span><br><span class="line">    C--&gt;D;</span><br><span class="line">```</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/cloudsmithy/picgo-imh/master/b699a258-8d44-4dbb-8c7c-ea70f37fa743.png" alt="b699a258-8d44-4dbb-8c7c-ea70f37fa743"></p><h2 id="16-折叠内容"><a href="#16-折叠内容" class="headerlink" title="16. 折叠内容"></a>16. 折叠内容</h2><p>使用 <code>&lt;details&gt;</code> 和 <code>&lt;summary&gt;</code> 标签来实现可折叠的内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;details&gt;</span><br><span class="line">  &lt;summary&gt;点击展开&lt;/summary&gt;</span><br><span class="line">  这是隐藏的内容。</span><br><span class="line">&lt;/details&gt;</span><br></pre></td></tr></table></figure><p>效果：</p><p>点击展开</p><p>这是隐藏的内容。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>Markdown 的简洁、灵活和易读性，使其成为撰写文档的首选工具。通过本教程，你不仅能掌握 Markdown 的基本语法，还能利用其高级功能扩展文档的可读性和功能性。无论是编写博客、项目文档，还是生成结构复杂的技术手册，Markdown 都能够帮助你轻松完成任务。希望本教程对你有所帮助，助你在文档编写中更高效地应用 Markdown。</p>]]></content>
    
    
    <summary type="html">Markdown 语法完全指南，从基础到进阶用法全面覆盖</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="NAS" scheme="https://blog.no-claw.com/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>关于速食我想说的话</title>
    <link href="https://blog.no-claw.com/posts/404bc04b/"/>
    <id>https://blog.no-claw.com/posts/404bc04b/</id>
    <published>2024-08-26T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="Soylent"><a href="#Soylent" class="headerlink" title="Soylent"></a>Soylent</h3><p>最早接触的速食是 Soylent，准确的说应该是代餐，据说是一个程序员为了节约时间发明创造的产品，他那个时候没有 AIGC 时间都浪费在 debug 上了，所以从吃上来节省时间。无论是学生时代还是在帝都上班的时光里，这款产品的定位始终透露着吃不起的风格，而且国内一直没正规渠道卖过，只能海淘遂放弃。 <span id="more"></span></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122185934506.png" alt="image-20240122185934506"></p><h3 id="若饭"><a href="#若饭" class="headerlink" title="若饭"></a>若饭</h3><p>海外的东西国内总有平替，这个叫做“若饭”，也曾疯狂追捧过，除了是为了腾出时间让老板过上更好的生活，要么就是外边的饭太难吃了。这个花样很多，固体的粉末的，还有其他口味以及能量棒什么的。总结下来就是真的吃不饱，起码两瓶才能保证没有饥饿感（好像也没有饱腹感），号称科学的添加了各种人体所需的营养，可以满足人体的需求。尽管这样，官方对他的定义仍然是建议每周保持几顿正常餐饮。不过在帝都，不吃几条街的馆子那就可惜了，遂放弃。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122190339605.png" alt="image-20240122190339605"></p><p>接下来是几款速食产品，单品不超过一顿饭的成本，按照时间顺序有蔡林记，阿宽，拉面说，牛肉河粉。至于速热米饭和自热锅，本来印象就不好，就不在这里再踩一遍了。</p><h3 id="蔡林记"><a href="#蔡林记" class="headerlink" title="蔡林记"></a>蔡林记</h3><p>京东购入，打折的时候不到 10 元一包，当初也是被公司附近的小馆子逼到只能网上买吃的，公司的饮水机的热水永远也泡不完两碗热干面，个人也不是很喜欢酸豆角，即使每顿两碗，这个能量还是供应不上学习时候的用脑过度。连着吃几天还是会感觉身体营养不良。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122192009706.png" alt="image-20240122192009706"></p><h3 id="阿宽"><a href="#阿宽" class="headerlink" title="阿宽"></a>阿宽</h3><p>小面味道不错，单价实惠，面皮吃了很上瘾，但是真的没啥能量，肚子饱了但是脑袋反应不过来，不能常吃，后来还是去同学家一口气吃了两三大碗米饭（排骨玉米汤）。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122191645690.png" alt="image-20240122191645690"></p><h3 id="拉面说"><a href="#拉面说" class="headerlink" title="拉面说"></a>拉面说</h3><p>拉面说似乎是这里最高档的面了，这个是半干半鲜面，一半都需要煮上 6-8 分钟，再用开水冲开里面的料包，所以这个不适合在办公室吃，汤面多一些，拌面少一些。配料应该是速食界的天花板了，但是也抵不住疫情期间每天吃过的厌倦，真的不会再买了。另外狠狠的吐槽下和府捞面，除了宣传的书房里的拉面，其他都是严重掉粉，量上价格贵又难吃系列，貌似现在店里也是这种预知的餐盘，但是卫生问题仍然不能保证，工作人员素质问题有待提高。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122192609007.png" alt="image-20240122192609007"></p><h3 id="五谷道场"><a href="#五谷道场" class="headerlink" title="五谷道场"></a>五谷道场</h3><p>本来方便面是不应该放在这里的，毕竟非油炸国产之光</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5355.jpg"></p><p>个人心中的国产方便面排名： 五谷道场 &gt; 汤达人 &gt; 康师傅 &gt; 其他</p><p>五谷道场兰州牛肉面和线下兰州牛肉面味道一模一样，辣子的味道能品尝出大西北的粗犷</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202405131558988.png"></p><h3 id="牛肉河粉"><a href="#牛肉河粉" class="headerlink" title="牛肉河粉"></a>牛肉河粉</h3><p>线下 58 一碗的河粉尽管可以免费续面，但是单价还是很贵，于是超市购入一箱火车头河粉，还是面饼+汤料的搭配，只是这个价格赶超拉面说还是让人觉得没有下次了。味道嘛，以后还是去吃线下吧，大不了再多续几碗嘛～</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240122193319038.png" alt="image-20240122193319038"></p><h3 id="羊肉泡馍"><a href="#羊肉泡馍" class="headerlink" title="羊肉泡馍"></a>羊肉泡馍</h3><p>女同事推荐的，山姆超市高档货，味道很浓郁，料包需要开水煮，我这砂锅煮了五六分钟打开看羊肉还有部分红血丝。不过味道不错，比上边同价位越南河粉好上不少。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5354.JPG"></p><h3 id="日清拉面"><a href="#日清拉面" class="headerlink" title="日清拉面"></a>日清拉面</h3><p>Youtube 刷到了日清拉面创始人的故事，所以直接上网买了一堆，不过都没有青菜包，图片的肉片也是没有的，感觉还是拉王比较好吃，泡开了之后看起来很像日式拉面而是方便面，其他的泡开的和方便面没差。鸡汤拉面也很惊艳，用开水冲泡开就能直接收获一碗鸡汤面，生的面渣还能当作干脆面吃。出前一丁是日清的子品牌，其他都是杯面（基本上都是合味道），因为吃不饱就没有买。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/88126071bfa5f3d0bf14c03fb37f71e0.JPG"></p><p>这个就是日清拉王泡开的效果了，基本上看不出是方便面，再加上空气炸锅烤的鸡蛋简直完美～</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/c7db3664b23b58459e10e6a49ad5dde2.JPG"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>价格上真的不值得，营养也不见得跟得上，还是好好吃饭吧～</p>]]></content>
    
    
    <summary type="html">从 Soylent 到各类代餐速食的体验与思考</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
  </entry>
  
  <entry>
    <title>INFINI Console 使用介绍</title>
    <link href="https://blog.no-claw.com/posts/bde1966f/"/>
    <id>https://blog.no-claw.com/posts/bde1966f/</id>
    <published>2024-07-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>上次在《INFINI Easysearch 尝鲜 Hands on》中我们部署了两个节点的 Easysearch，并且也设置了 Console 对集群进行监控。那么今天我们再来介绍下 INFINI Console 的使用。</p><h2 id="INFINI-Console-仪表盘功能介绍"><a href="#INFINI-Console-仪表盘功能介绍" class="headerlink" title="INFINI Console 仪表盘功能介绍"></a>INFINI Console 仪表盘功能介绍</h2><p>INFINI Console 是一个功能强大的数据管理和分析平台，其仪表盘页面提供了直观、简洁的界面，使用户可以快速了解系统状态并进行各种管理操作。本文将详细介绍仪表盘页面的各项功能。</p><h3 id="警告和通知"><a href="#警告和通知" class="headerlink" title="警告和通知"></a>警告和通知</h3><p>仪表盘顶部显示了系统的实时告警、通知和待办事项的数量，当前数据显示：</p><ul><li>告警：0 条</li><li>通知：0 条</li><li>待办：0 条</li></ul><p>这些指标有助于及时了解系统的异常情况和待处理的任务。</p><h3 id="概览信息"><a href="#概览信息" class="headerlink" title="概览信息"></a>概览信息</h3><p>在仪表盘的中心区域，用户可以看到几项关键的系统概览信息：</p><span id="more"></span><ul><li><strong>集群数量</strong>：当前有 3 个集群正在运行。</li><li><strong>节点数量</strong>：系统中有 16 个节点。</li><li><strong>主机数量</strong>：共有 3 台主机。</li><li><strong>已用存储</strong>：系统已使用存储空间为 2.0GB。</li></ul><p>仪表盘页面还提供了几个常用操作的快速入口，方便用户迅速访问常用功能：</p><ul><li><strong>集群注册</strong>：用户可以通过此入口快速注册新的集群。</li><li><strong>数据探索</strong>：用户可以访问数据探索工具，对系统中的数据进行分析和查询。</li><li><strong>告警管理</strong>：提供对告警信息的管理功能，用户可以查看和处理告警。</li><li><strong>安全管理</strong>：安全管理入口帮助用户维护系统的安全设置和策略。</li></ul><p>仪表盘右侧显示了集群的动态信息，包括最近的操作日志。例如：</p><ul><li>2024-07-03 22:43:43，index medcl 在 cluster infiniLabs 中的状态更新。</li><li>2024-07-03 22:06:43，index medcl 在 cluster infiniLabs 中被创建。</li></ul><p><img src="https://i-blog.csdnimg.cn/blog_migrate/69cfa7cc765112f313193d72c876efd0.png" alt="在这里插入图片描述"></p><h1 id="INFINI-Console-集群管理页面功能介绍"><a href="#INFINI-Console-集群管理页面功能介绍" class="headerlink" title="INFINI Console 集群管理页面功能介绍"></a>INFINI Console 集群管理页面功能介绍</h1><p>INFINI Console 是一个用于数据管理和分析的综合平台，其集群管理页面提供了对系统集群、节点、索引和主机的全面监控和管理功能。本文将详细介绍该页面的各项功能和特点。</p><p>集群管理页面主要分为几个部分：顶部的功能选项卡、中部的集群列表、以及右侧的筛选和排序选项。</p><p>页面顶部的功能选项卡包括以下几项：</p><ul><li>**Clusters (集群)**：显示当前系统中的所有集群。</li><li>**Nodes (节点)**：显示集群中的节点详细信息。</li><li>**Indices (索引)**：显示集群中的索引信息。</li><li>**Hosts (主机)**：显示系统中的主机信息。</li></ul><p>集群列表展示了每个集群的详细信息，包括：</p><ul><li><strong>集群名称</strong>：每个集群的名称，如 “infinilabs”、”mycluster”、”INFINI_SYSTEM (JeanGrey)”。</li><li><strong>集群健康状态</strong>：以颜色条的形式显示最近 14 天的集群健康状态（绿色表示健康，黄色表示有警告）。</li><li><strong>节点数量</strong>：集群中包含的节点数量。</li><li><strong>索引数量</strong>：集群中的索引数量。</li><li><strong>分片数量</strong>：集群中的分片数量。</li><li><strong>文档数量</strong>：集群中存储的文档数量。</li><li><strong>磁盘使用率</strong>：集群的磁盘使用情况。</li><li><strong>JVM 堆内存使用率</strong>：集群的 JVM 堆内存使用情况。</li><li><strong>索引速率</strong>：当前集群的索引速率（每秒索引数）。</li><li><strong>搜索速率</strong>：当前集群的搜索速率（每秒搜索数）。</li></ul><p>页面右侧提供了丰富的筛选和排序选项，可以根据以下条件筛选和排序集群：</p><ul><li>**健康状态 (Health Status)**：根据集群的健康状态筛选，如绿色（健康）和黄色（警告）。</li><li>**分布 (Distribution)**：根据集群的分布类型筛选，如 “easysearch” 和 “elasticsearch”。</li><li>**版本 (Version)**：根据集群使用的软件版本筛选，我这里是 easysearch 1.8.2”和 ElasticSearch 7.10.2。</li><li>**区域 (Region)**：根据集群所在的区域筛选，如 “china” 和 “default”。</li><li>**标签 (Tags)**：根据自定义标签进行筛选。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/8d4f68e833e90c99aa583e5b587359a9.png" alt="在这里插入图片描述"><br>下面分别是节点层面，索引层面，以及主机层面的信息，监控的指标和集群层面大同小异。</li></ul><p>节点监控：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/ecd388e5e2083eeef7c4ea6b7bd21e2d.png" alt="在这里插入图片描述"><br>索引监控：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/c12be3370ae00fa3cc46c891e0ee64d8.png" alt="在这里插入图片描述"><br>主机的监控：</p><p>这里包括了常规的 CPU，内存，磁盘，网络的监控。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/74b56ae141bbf5d0f8b33028640e4e88.png" alt="在这里插入图片描述"></p><p>监控报表页面提供了对集群运行状况的详细监控和分析功能。本文将详细介绍该页面的各项功能和特点，帮助用户更好地理解和使用该工具。用户可以选择最近 15 分钟、1 小时、24 小时等不同时间范围查看数据。同时，用户可以手动点击刷新按钮更新数据，以获取最新的监控信息。</p><p>概览信息部分显示了当前集群的基本状态，包括：</p><ul><li><strong>集群名称</strong>：如 “infinilabs”。</li><li><strong>在线时长</strong>：集群已经运行的时间，如 “3 天”。</li><li><strong>集群版本</strong>：当前集群使用的软件版本，如 “1.8.2”。</li><li><strong>健康状态</strong>：集群的健康状况，如 “green” 表示健康。</li><li><strong>节点数</strong>：集群中的节点数量，如 “2”。</li><li><strong>索引数</strong>：集群中的索引数量，如 “38”。</li><li><strong>主&#x2F;总分片</strong>：集群中已分配的主分片和总分片数量，如 “38&#x2F;76”。</li><li><strong>未分配分片</strong>：集群中未分配的分片数量，如 “0”。</li><li><strong>文档数</strong>：集群中存储的文档数量，如 “656,803”。</li><li><strong>存储空间</strong>：集群使用的存储空间和总可用存储空间，如 “1007.2MB&#x2F;385.4GB”。</li><li><strong>JVM 内存</strong>：JVM 堆内存的使用情况，如 “1023.0MB&#x2F;2.0GB”。</li></ul><p>监控报表页面还提供了多个性能指标的图表，包括：</p><h4 id="索引吞吐-doc-s"><a href="#索引吞吐-doc-s" class="headerlink" title="索引吞吐 (doc&#x2F;s)"></a>索引吞吐 (doc&#x2F;s)</h4><ul><li><strong>Total Indexing</strong>：总索引吞吐量。</li><li><strong>Primary Indexing</strong>：主分片的索引吞吐量。</li></ul><h4 id="查询吞吐-query-s"><a href="#查询吞吐-query-s" class="headerlink" title="查询吞吐 (query&#x2F;s)"></a>查询吞吐 (query&#x2F;s)</h4><ul><li><strong>Total Query</strong>：总查询吞吐量。</li></ul><h4 id="索引延迟-ms"><a href="#索引延迟-ms" class="headerlink" title="索引延迟 (ms)"></a>索引延迟 (ms)</h4><ul><li><strong>Indexing Latency</strong>：索引延迟时间。</li><li><strong>Delete Latency</strong>：删除操作的延迟时间。</li></ul><h4 id="查询延迟-ms"><a href="#查询延迟-ms" class="headerlink" title="查询延迟 (ms)"></a>查询延迟 (ms)</h4><ul><li><strong>Query Latency</strong>：查询延迟时间。</li><li><strong>Fetch Latency</strong>：获取操作的延迟时间。</li><li><strong>Scroll Latency</strong>：滚动操作的延迟时间。</li></ul><p><img src="https://i-blog.csdnimg.cn/blog_migrate/0a3bfc62bc6a8c9b0818b4733c839427.png" alt="在这里插入图片描述"></p><p>当然也可以点击 Advance 查看更多的监控指标：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6ad64acc24959c6db740ff5578501944.png" alt="在这里插入图片描述"></p><p>这个是是节点级别的性能监控，包括 CPU，负载，JVM 内存，剩余使用空间以及磁盘空间，集群启动时间和索引一些读写的情况。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/12b2d9eafe3661d0ccdfc3e29aa2c313.png" alt="在这里插入图片描述"></p><p>这个是索引级别的监控，包括集群里有几个索引，索引状态是 open 还是 close，每个索引有几个主分片和副本分片，每个索引里文档的条数和占用空间，<br><img src="https://i-blog.csdnimg.cn/blog_migrate/2a9608edbed3a9550a7a0c7690ea15ef.png" alt="在这里插入图片描述"></p><p>集群动态页面提供了对集群中各类事件和活动的详细记录和监控功能。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/c59f44da6e9de578cd127d5df5499825.png" alt="在这里插入图片描述"><br>别名管理页面提供了对索引别名的管理功能，使用户可以方便地管理和配置 Elasticsearch 的索引别名。</p><p>先来介绍下什么是别名以及基于别名的索引轮换：</p><p>别名（Alias）是 Elasticsearch 提供的一种机制，使用户可以为一个或多个索引创建一个或多个别名。由于 EasySearch 基于 Elasticsearch 开发，所以同样适用此功能。</p><h3 id="创建别名"><a href="#创建别名" class="headerlink" title="创建别名"></a>创建别名</h3><p>可以通过 DSL 创建别名。例如，创建一个名为 <code>my_index_alias</code> 的别名指向 <code>my_index</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><span class="line">6</span><br><span class="line">7</span><br><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">POST /_aliases</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;actions&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;add&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index_alias&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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><h3 id="删除别名"><a href="#删除别名" class="headerlink" title="删除别名"></a>删除别名</h3><p>删除一个别名同样可以通过 REST API 实现：</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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;actions&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;remove&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index_alias&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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>索引轮换是一种常用的索引管理策略，特别适用于日志和时间序列数据的场景。通过索引轮换，用户可以在索引达到一定条件（如大小或文档数量）时，创建一个新的索引来继续存储数据，而旧的索引可以继续用于查询。</p><ol><li><strong>设置写别名</strong>：创建一个指向当前写入索引的别名，例如 <code>current_write_index</code>。</li><li><strong>定义索引轮换条件</strong>：可以基于索引的大小、文档数量或时间来定义轮换条件。</li><li><strong>执行轮换操作</strong>：当索引满足轮换条件时，创建一个新的索引并更新写别名指向这个新索引。</li></ol><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></pre></td><td class="code"><pre><span class="line">PUT /my_index<span class="number">-000001</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;aliases&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;current_write_index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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><p>使用 <code>/_rollover</code> API 定义轮换条件并执行轮换：</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">POST /current_write_index/_rollover</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;conditions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;max_age&quot;</span><span class="punctuation">:</span> <span class="string">&quot;7d&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;max_docs&quot;</span><span class="punctuation">:</span> <span class="number">1000000</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;number_of_shards&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;aliases&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;current_write_index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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><p>此命令会检查 <code>current_write_index</code> 别名指向的索引是否满足轮换条件，如果满足，则创建一个新的索引（如 <code>my_index-000002</code>），并更新 <code>current_write_index</code> 别名指向新索引。</p><p>在轮换之后，所有的写入操作会自动指向新的索引 <code>my_index-000002</code>，而旧的索引 <code>my_index-000001</code> 可以继续用于查询操作。</p><p>可以通过一个独立的读别名来处理查询操作，例如 <code>all_indices</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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;actions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span> <span class="attr">&quot;add&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index-000001&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;all_indices&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span> <span class="attr">&quot;add&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index-000002&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;all_indices&quot;</span> <span class="punctuation">&#125;</span><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>通过这种方式，查询操作可以透明地访问所有的历史数据，而写操作总是指向最新的索引。在 INFINI Console 中提供了可视化创建索引以及别名的方式，页面右上角提供了新建按钮，用户可以通过点击该按钮创建新的索引别名，填写别名名称、关联索引、索引路由、搜索路由和过滤查询等配置。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/81873cb012d1b1895a4094b500255990.png" alt="在这里插入图片描述"></p><p>平台概览页面中部展示了多个关键指标的监控图表，包括：</p><p>**健康状态 (Health)**：显示系统当前的健康状态。如果没有数据，则显示“暂无数据”。</p><p>**引擎分布 (Engines)**：展示系统中不同搜索引擎的分布情况，例如 EasySearch 和 Elasticsearch 的比例。图表显示当前 EasySearch 占 67%，Elasticsearch 占 33%。</p><p>**提供商 (Providers)**：显示系统中使用的云服务提供商信息。在示例中，所有资源都托管在 AWS 上。</p><p>**JDK 版本 (JDK)**：显示系统中使用的 JDK 版本信息。在示例中，所有节点都使用 JDK 版本 11.0.20。</p><p><strong>磁盘使用情况 (Disk Utilization) - Top 10</strong>：显示磁盘使用率最高的前 10 个节点。在示例中，easysearch-node1 和 easysearch-node2 的磁盘使用率均为 4%。</p><p><strong>JVM 使用情况 (JVM Utilization) - Top 10</strong>：展示 JVM 使用率最高的前 10 个节点。在示例中，infinilabs 集群的 easysearch-node1 和 easysearch-node2 节点的 JVM 使用情况有详细的时间序列数据，显示了不同时间点的使用率变化。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/b5c424dd48ea0b5f9a2c30903cc06685.png" alt="在这里插入图片描述"></p><p>结下来是更加详细的页面，我们能够看到更多指标：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/35e44900d68426dcfa0cfea72cf908af.png" alt="在这里插入图片描述"></p><p>这个是 Discovery 的页面：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/b9cd9cfb536cad4d4ac5431621cdf3ef.png" alt="在这里插入图片描述"><br>这里可以看到集群的警报，目前集群运行良好，没有任何警报。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/76c30ac5cc8f50f7e29486a4b6f86c88.png" alt="在这里插入图片描述"><br>内部会预设一些警报规则，如下：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/e5727ff282cfe6b64a6867da541f4c88.png" alt="在这里插入图片描述"><br>点进去一个请求，比如磁盘的警告，可以到针对不同的使用量设置了不同的警告级别和警告通知。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/ccd8c6e9e41be3272e32d14d87a1571d.png" alt="在这里插入图片描述"><br>这里针对警报设置警报，可以看到现在支持很多平台，Discord，飞书，邮件，微信，Slack 以及钉钉。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6e8ae2443ad49345a0a8f8cd54cd1422.png" alt="在这里插入图片描述"><br>点击进去可以查看到，对于社交软件而言，其实是使用 Webhook 的方式进行通知，除此之外也支持配置邮件服务器和自定义的 webhook 进行通知。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/2fd848757499fcd44641dfde735da363.png" alt="在这里插入图片描述"><br>接下来是开发工具，其实就是 kibana 的 dev tool，中文直译过来，使用上没有任何区别，除了支持 DSL 之外，还支持 SQL 查询。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/e85c555b6053626b7350d12ee1bb478f.png" alt="在这里插入图片描述"></p><p>这里可以看到连接这三个集群的凭证管理，目前都是有效的。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/a596503b2d17df6571a58096883edd40.png" alt="在这里插入图片描述"></p><p>后台的用户授权，我这里目前设置了 admin，也可以在这里添加用户以及修改 console 管理界面的密码。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/c3f605d3cfae751de9d3433e4321b4f5.png" alt="在这里插入图片描述"><br>最后审计日子，会追踪对于集群做了什么操作，发出了哪些的 API 请求，可以看到我这里能看到捕获了查看集群监控信息以及查看集群索引的操作。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/b3cc68031c2e2ab8d158e57ec20c6dfb.png" alt="在这里插入图片描述"></p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>INFINI Console 的仪表盘页面集成了系统的关键信息和快捷操作入口，使用户可以高效地管理和监控系统。通过详细的概览信息、实时的告警通知、快速的功能入口和动态日志，用户能够对系统的运行状态一目了然，并快速响应各种管理需求。这个设计不仅提升了用户的工作效率，还确保了系统的安全和稳定运行。</p><p>INFINI Console 的集群管理页面提供了对系统集群的全面监控和管理功能。通过详细的集群信息展示、便捷的功能选项卡切换以及丰富的筛选和排序功能，用户可以高效地管理和监控系统中的集群状态。这不仅提升了运维效率，还确保了系统的稳定运行和高效管理。</p><p>INFINI Console 的节点管理页面提供了对集群节点的全面监控和管理功能。通过详细的节点信息展示、便捷的功能选项卡切换以及丰富的筛选和搜索功能，用户可以高效地管理和监控系统中的节点状态，从而提升运维效率，确保系统的稳定运行和高效管理。</p><p>INFINI Console 的监控报表页面提供了对集群运行状况的全面监控和分析功能。通过详细的概览信息和多个性能指标图表，用户可以高效地监控和管理集群的运行状态。这不仅提升了系统运维效率，还确保了集群的稳定运行和高效管理。</p><p>通过这些功能，INFINI Console 为用户提供了全面的系统管理工具，帮助他们高效地应对各种运维挑战，确保系统的高效、安全、稳定运行。</p>]]></content>
    
    
    <summary type="html">INFINI Console 的功能介绍与基本使用教程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch、Elasticsearch、Amazon OpenSearch 快照兼容对比</title>
    <link href="https://blog.no-claw.com/posts/80e06f4b/"/>
    <id>https://blog.no-claw.com/posts/80e06f4b/</id>
    <published>2024-07-18T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在当今的数据驱动时代，搜索引擎的快照功能在数据保护和灾难恢复中至关重要。本文将对 Easysearch、Elasticsearch 和 Amazon OpenSearch 的快照兼容性进行比较，分析它们在快照创建、恢复、存储格式和跨平台兼容性等方面的特点，帮助大家更好地理解这些搜索引擎的差异，从而选择最适合自己需求的解决方案。</p><h2 id="启动集群"><a href="#启动集群" class="headerlink" title="启动集群"></a>启动集群</h2><h3 id="Easysearch"><a href="#Easysearch" class="headerlink" title="Easysearch"></a>Easysearch</h3><p>服务器一般情况下默认参数都是很低的，而 Easysearch&#x2F;Elasticsearch 是内存大户，所以就需要进行系统调优。</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">sysctl -w vm.max_map_count=262144</span><br></pre></td></tr></table></figure><p><code>vm.max_map_count</code> 是一个 Linux 内核参数，用于控制单个进程可以拥有的最大内存映射区域（VMA，Virtual Memory Areas）的数量。内存映射区域是指通过内存映射文件或匿名内存映射创建的虚拟内存区域。</p><span id="more"></span><p>这个参数在一些应用程序中非常重要，尤其是那些需要大量内存映射的应用程序，比如 Elasticsearch。Elasticsearch 使用内存映射文件来索引和搜索数据，这可能需要大量的内存映射区域。如果 <code>vm.max_map_count</code> 设置得太低，Elasticsearch 可能无法正常工作，并会出现错误信息。</p><p>调整 <code>vm.max_map_count</code> 参数的一些常见原因：</p><ol><li><p><strong>支持大型数据集</strong>：<br>应用程序（如 Elasticsearch）在处理大型数据集时可能需要大量内存映射区域。增加 <code>vm.max_map_count</code> 可以确保这些应用程序有足够的内存映射区域来处理数据。</p></li><li><p><strong>防止内存错误</strong>：<br>如果 <code>vm.max_map_count</code> 设置得太低，当应用程序尝试创建超过限制的内存映射时，会出现错误，导致应用程序崩溃或无法正常工作。</p></li><li><p><strong>优化性能</strong>：<br>适当地设置 <code>vm.max_map_count</code> 可以优化应用程序的性能，确保内存映射操作顺利进行。</p></li></ol><p>检查当前的 <code>vm.max_map_count</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">sysctl vm.max_map_count</span><br></pre></td></tr></table></figure><p>或者查看 <code>/proc/sys/vm/max_map_count</code> 文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /proc/sys/vm/max_map_count</span><br></pre></td></tr></table></figure><p>Elasticsearch 官方建议将 <code>vm.max_map_count</code> 设置为至少 262144。对于其他应用程序。</p><p>Easysearch 具体安装步骤见<a href="https://blog.csdn.net/weixin_38781498/article/details/140077785">INFINI Easysearch 尝鲜 Hands on</a></p><h3 id="Amazon-OpenSearch"><a href="#Amazon-OpenSearch" class="headerlink" title="Amazon OpenSearch"></a>Amazon OpenSearch</h3><p>使用 Amazon Web Services 控制台进行创建。</p><h3 id="Elasticsearch"><a href="#Elasticsearch" class="headerlink" title="Elasticsearch"></a>Elasticsearch</h3><p>使用如下 docker compose 部署一个三节点的 ES 集群：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;2.2&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">es01:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.elastic.co/elasticsearch/elasticsearch:7.10.2</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es01</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es01</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">bootstrap.memory_lock=true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data01:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9200</span><span class="string">:9200</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line">  <span class="attr">es02:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.elastic.co/elasticsearch/elasticsearch:7.10.2</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es02</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es02</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es01,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">bootstrap.memory_lock=true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data02:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line">  <span class="attr">es03:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.elastic.co/elasticsearch/elasticsearch:7.10.2</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es03</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es01,es02</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">bootstrap.memory_lock=true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">ulimits:</span></span><br><span class="line">      <span class="attr">memlock:</span></span><br><span class="line">        <span class="attr">soft:</span> <span class="number">-1</span></span><br><span class="line">        <span class="attr">hard:</span> <span class="number">-1</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data03:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data01:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">  <span class="attr">data02:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">  <span class="attr">data03:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">elastic:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br></pre></td></tr></table></figure><p>由于这个 docker compose 没有关于 kibana 的配置，所以我们还是用 Console 添加原生的 Elasticsearch 集群<img src="https://i-blog.csdnimg.cn/direct/edc37c2feb794c4d8d06ba9a7d8bd190.png" alt="请添加图片描述"></p><p>集群信息</p><p><img src="https://i-blog.csdnimg.cn/direct/b814ba386e294a9f81c3375c0669f59c.png" alt="请添加图片描述"></p><h2 id="快照还原的步骤"><a href="#快照还原的步骤" class="headerlink" title="快照还原的步骤"></a>快照还原的步骤</h2><h3 id="快照前的准备"><a href="#快照前的准备" class="headerlink" title="快照前的准备"></a>快照前的准备</h3><h3 id="插件安装"><a href="#插件安装" class="headerlink" title="插件安装"></a>插件安装</h3><p>本次测试选择把索引快照备份到 Amazon S3，所以需要使用 S3 repository plugin，这个插件添加了对使用 Amazon S3 作为快照&#x2F;恢复存储库的支持。</p><p>Easysearch 和 OpenSearch 集群自带了这个插件，所以无需额外安装。</p><p>对于自己部署的三节点 Elasticsearch 则需要进入每一个节点运行安装命令然后再重启集群，建议使用自动化运维工具来做这步，安装命令如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> bin/elasticsearch-plugin install repository-s3</span><br></pre></td></tr></table></figure><p>如果不再需要这个插件，可以这样删除。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> bin/elasticsearch-plugin remove repository-s3</span><br></pre></td></tr></table></figure><p>由于需要和 Amazon Web Services 打交道，所以我们需要设置 IAM 凭证，这个插件可以从 EC2 IAM instance profile，ECS task role 以及 EKS 的 Service account 读取相应的凭证。</p><p>对于托管的 Amazon OpenSearch 来说，我们无法在托管的 EC2 上绑定我们的凭证，所以需要新建一个 OpenSearchSnapshotRole，然后通过当前的用户把这个角色传递给服务，也就是我们说的 IAM:PassRole。</p><p>创建 OpenSearchSnapshotRole，策略如下：</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [&#123;</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;s3:ListBucket&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::bucket-name&quot;</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;s3:GetObject&quot;</span>,</span><br><span class="line">        <span class="string">&quot;s3:PutObject&quot;</span>,</span><br><span class="line">        <span class="string">&quot;s3:DeleteObject&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::bucket-name/*&quot;</span></span><br><span class="line">      ]</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 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;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&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;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Principal&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;Service&quot;</span><span class="punctuation">:</span> <span class="string">&quot;es.amazonaws.com&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;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sts:AssumeRole&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>然后在我们的 IAM user 上加上 PassRole 的权限，这样我们就可以把 OpenSearchSnapshotRole 传递给 OpenSearch 集群。</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&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;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;iam:PassRole&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iam::123456789012:role/OpenSearchSnapshotRole&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><h3 id="注册存储库"><a href="#注册存储库" class="headerlink" title="注册存储库"></a>注册存储库</h3><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></pre></td><td class="code"><pre><span class="line">PUT /_snapshot/snapshot-repo-name</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-name&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;base_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-prefix&gt;&quot;</span><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><br></pre></td></tr></table></figure><p>在目标集群同样执行这个语句，为了防止覆盖源集群存储库的数据，将 “readonly”: true 添加到”settings” PUT 请求中，这样就只有一个集群具有对存储库的写入权限。</p><p>如果 Bucket 在中国区，那么还需要加上<strong>endpoint: <a href="https://s3/">https://s3</a>.&lt; region &gt;.amazonaws.com.cn</strong>这样的参数。</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></pre></td><td class="code"><pre><span class="line">PUT /_snapshot/snapshot-repo-name</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-name&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;base_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-prefix&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;readonly&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="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>对于 OpenSearch 来说，还需要执行 passrole，所以还需要添加<strong>role_arn</strong>这个字段，由于 IAM:PassRole 需要对 HTTP 请求做 signV4 日签名，所以这部常常使用 Postman 来完成。把角色传递过去之后，接下来的快照还原操作就可以在 OpenSearch Dashboard 中进行操作了。</p><p><img src="https://i-blog.csdnimg.cn/direct/0db4d578214d47d483d1f15cde3f848e.png" alt="在这里插入图片描述"></p><p>需要注意的是，需要在 auth 这里输入 AccessKey，SecretKey，AWS Region，Service Name（es）来做 SignV4 的签名。<br><img src="https://i-blog.csdnimg.cn/direct/26d8ee62236a4c97a498419328746bfc.png" alt="在这里插入图片描述"></p><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-name&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;base_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-prefix&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;readonly&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;role_arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iam::123456789012:role/OpenSearchSnapshotRole&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><h3 id=""><a href="#" class="headerlink" title=""></a></h3><ul><li><strong>查看所有注册的存储库</strong>：<ul><li><code>GET _snapshot</code>：这个命令返回所有已注册的快照存储库列表及其基本信息。</li></ul></li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;es_repository&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-s3-bucket-name&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-s3-bucket-region&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ul><li><strong>查看特定存储库的详细信息</strong>：<br><code>GET _snapshot/es_repository</code>：这个命令返回名为<code>es_repository</code>的存储库的详细配置信息，包括存储桶名称、区域和其他设置。</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _snapshot/es_repository</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;es_repository&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-s3-bucket-name&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-s3-bucket-region&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;access_key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-access-key&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;secret_key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-secret-key&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ul><li><strong>查看特定存储库中的快照</strong>：<br><code>GET _cat/snapshots/es_repository?v</code>：这个命令返回<code>es_repository</code>存储库中的所有快照及其详细信息，包括快照 ID、状态、开始时间、结束时间、持续时间、包含的索引数量、成功和失败的分片数量等。</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET _cat/snapshots/es_repository?v</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">id</span>                     status start_epoch start_time end_epoch end_time duration indices successful_shards failed_shards total_shards</span><br><span class="line">snapshot_1             SUCCESS 1628884800 08:00:00   1628888400 09:00:00 1h       3       10                0             10</span><br><span class="line">snapshot_2             SUCCESS 1628971200 08:00:00   1628974800 09:00:00 1h       3       10                0             10</span><br></pre></td></tr></table></figure><h3 id="创建索引快照"><a href="#创建索引快照" class="headerlink" title="创建索引快照"></a>创建索引快照</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># PUT _snapshot/my_repository/&lt;my_snapshot_&#123;now/d&#125;&gt;</span></span><br><span class="line">PUT _snapshot/my_repository/my_snapshot</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;indices&quot;</span>: <span class="string">&quot;my-index,logs-my_app-default&quot;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据快照的大小不同，完成快照可能需要一些时间。默认情况下，create snapshot API 只会异步启动快照过程，该过程在后台运行。要更改为同步调用，可以将 <code>wait_for_completion</code> 查询参数设置为 <code>true</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">PUT _snapshot/my_repository/my_snapshot?wait_for_completion=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>另外还可以使用 clone snapshot API 克隆现有的快照。要监控当前正在运行的快照，可以使用带有 <code>_current</code> 请求路径参数的 get snapshot API。</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 _snapshot/my_repository/_current</span><br></pre></td></tr></table></figure><p>如果要获取参与当前运行快照的每个分片的完整详细信息，可以使用 get snapshot status API。</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 _snapshot/_status</span><br></pre></td></tr></table></figure><p>成功创建快照之后，就可以在 S3 上看到备份的数据块文件，这个是正确的快照层级结构：<br><img src="https://i-blog.csdnimg.cn/direct/5f14fe8a4724434bb821fa649e1ce1c1.png" alt="在这里插入图片描述"></p><p>需要注意的是， “base_path”: “<bucket-prefix>“这里最好不要加&#x2F;，虽然不影响同集群迁移，这个会为我们在不同厂商的搜索引擎中迁移遇到问题，可能是这样的，所以需要注意。<img src="https://i-blog.csdnimg.cn/direct/f07f8927c0724ddc8bd363c1dc13fd64.png" alt="请添加图片描述">所以在 Open Search 中还原 Elasticsearch 就遇到了这个问题：</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_missing_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[easy_repository:2/-jOQ0oucQDGF3hJMNz-vKQ] is missing&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_missing_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[easy_repository:2/-jOQ0oucQDGF3hJMNz-vKQ] is missing&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;no_such_file_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Blob object [11111/indices/7fv2zAi4Rt203JfsczUrBg/meta-YGnzxZABRBxW-2vqcmci.dat] not found: The specified key does not exist. (Service: S3, Status Code: 404, Request ID: R71DDHX4XXM0434T, Extended Request ID: d9M/HWvPvMFdPhB6KX+wYCW3ZFqeFo9EoscWPkulOXWa+TnovAE5PlemtuVzKXjlC+rrgskXAus=)&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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">404</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="恢复索引快照"><a href="#恢复索引快照" class="headerlink" title="恢复索引快照"></a>恢复索引快照</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">POST _snapshot/my_repository/my_snapshot_2099.05.06/_restore</span><br><span class="line">&#123;</span><br><span class="line">  &quot;indices&quot;: &quot;my-index,logs-my_app-default&quot;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="各个集群的还原"><a href="#各个集群的还原" class="headerlink" title="各个集群的还原"></a>各个集群的还原</h2><ol><li><p>Elasticsearch 7.10.2 的快照可以还原到 Easysearch 和 Amazon OpenSearch</p></li><li><p>从 Easysearch 1.8.2 还原到 Elasticsearch 7.10.2 报错如下：</p></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[s3_repository:1/a2qV4NYIReqvgW6BX_nxxw] cannot restore index [my_indexs] because it cannot be upgraded&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[s3_repository:1/a2qV4NYIReqvgW6BX_nxxw] cannot restore index [my_indexs] because it cannot be upgraded&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_state_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The index [[my_indexs/ALlTCIr0RJqtP06ouQmf0g]] was created with version [1.8.2] but the minimum compatible version is [6.0.0-beta1]. It should be re-indexed in Elasticsearch 6.x before upgrading to 7.10.2.&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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="3"><li>从 Amazon OpenSearch 2.1.3 还原到 Elasticsearch 7.10.2 报错如下（无论是否开启兼容模式）：</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[aos:2/D-oyYSscSdCbZFcmPZa_yg] the snapshot was created with Elasticsearch version [36.34.78-beta2] which is higher than the version of this node [7.10.2]&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[aos:2/D-oyYSscSdCbZFcmPZa_yg] the snapshot was created with Elasticsearch version [36.34.78-beta2] which is higher than the version of this node [7.10.2]&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;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="4"><li>从 Easysearch 1.8.2 还原到 Amazon OpenSearch2.13 报错如下（无论是否开启兼容模式）：</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[easy_repository:2/LE18AWHlRJu9rpz9BJatUQ] cannot restore index [my_indexs] because it cannot be upgraded&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[easy_repository:2/LE18AWHlRJu9rpz9BJatUQ] cannot restore index [my_indexs] because it cannot be upgraded&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_state_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The index [[my_indexs/VHOo7yfDTRa48uhQvquFzQ]] was created with version [1.8.2] but the minimum compatible version is OpenSearch 1.0.0 (or Elasticsearch 7.0.0). It should be re-indexed in OpenSearch 1.x (or Elasticsearch 7.x) before upgrading to 2.13.0.&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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="5"><li>Amazon OpenSearch 还原到 Easysearch 同样失败</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[aoss:2/D-oyYSscSdCbZFcmPZa_yg] cannot restore index [aos] because it cannot be upgraded&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;snapshot_restore_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[aoss:2/D-oyYSscSdCbZFcmPZa_yg] cannot restore index [aos] because it cannot be upgraded&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;illegal_state_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The index [[aos/864WjTAXQCaxJ829V5ktaw]] was created with version [36.34.78-beta2] but the minimum compatible version is [6.0.0]. It should be re-indexed in Easysearch 6.x before upgrading to 1.8.2.&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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="6"><li>Elasticsearch8.14.3 迁移到 Amazon OpenSearch 或者 Elasticsearch 都是有这个报错：</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;parsing_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Failed to parse object: unknown field [uuid] found&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;line&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;col&quot;</span><span class="punctuation">:</span> <span class="number">25</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;repository_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[snap] Unexpected exception when loading repository data&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;caused_by&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;parsing_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Failed to parse object: unknown field [uuid] found&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;line&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;col&quot;</span><span class="punctuation">:</span> <span class="number">25</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="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这是由于 Elasticsearch8 在创建快照的时候会默认加上一个 UUID 的字段，所以我们低版本的 Easysearch、Amazon OpenSearch 中会找不到这个字段，在执行<code>GET _cat/snapshots/snap?v</code>的时候就报错，及时在注册存储库的时候显示加上 UUID 的字段也无事无补。</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;snapshot-repo-name&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;qlJ0uqErRmW6aww2Fyt4Fg&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-name&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;base_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;bucket-prefix&gt;&quot;</span><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></pre></td></tr></table></figure><p>以下是兼容性对比，每行第一列代表源集群，第一行代表目标集群：</p><table><thead><tr><th>快照兼容对比</th><th>Easysearch 1.8.2</th><th>Elasticsearch 7.10.2</th><th>OpenSearch 2.13</th></tr></thead><tbody><tr><td>Easysearch 1.8.2</td><td>兼容</td><td><strong>不兼容</strong></td><td><strong>不兼容</strong></td></tr><tr><td>Elasticsearch 7.10.2</td><td>兼容</td><td>兼容</td><td>兼容</td></tr><tr><td>OpenSearch 2.13</td><td><strong>不兼容</strong></td><td><strong>不兼容</strong></td><td>兼容</td></tr></tbody></table><p>Elasticsearch 的兼容列表官方的列表如下：<br><img src="https://i-blog.csdnimg.cn/direct/42760eba986a4605a15256c0b9264b1c.png" alt="在这里插入图片描述"></p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>开始使用 Elastic Stack 和 Docker Compose：第 1 部分<br><a href="https://www.elastic.co/cn/blog/getting-started-with-the-elastic-stack-and-docker-compose">https://www.elastic.co/cn/blog/getting-started-with-the-elastic-stack-and-docker-compose</a></p><p>Docker Compose 部署多节点 Elasticsearch</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.10/docker.html#docker-compose-file">https://www.elastic.co/guide/en/elasticsearch/reference/7.10/docker.html#docker-compose-file</a></p><p>repository-s3 教程</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.14/repository-s3.html">https://www.elastic.co/guide/en/elasticsearch/reference/8.14/repository-s3.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3.html">https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3.html</a></p><p>snapshot-restore</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.10/snapshot-restore.html">https://www.elastic.co/guide/en/elasticsearch/reference/7.10/snapshot-restore.html</a></p><p>在亚马逊 OpenSearch 服务中创建索引快照</p><p><a href="https://docs.amazonaws.cn/zh_cn/opensearch-service/latest/developerguide/managedomains-snapshots.html#managedomains-snapshot-restore">https://docs.amazonaws.cn/zh_cn/opensearch-service/latest/developerguide/managedomains-snapshots.html#managedomains-snapshot-restore</a></p><p>教程：迁移至 Amazon OpenSearch Service</p><p><a href="https://docs.amazonaws.cn/zh_cn/opensearch-service/latest/developerguide/migration.html">https://docs.amazonaws.cn/zh_cn/opensearch-service/latest/developerguide/migration.html</a></p>]]></content>
    
    
    <summary type="html">对比 Easysearch、Elasticsearch 和 Amazon OpenSearch 的快照兼容性</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 数据可视化和管理平台：INFINI Console 使用介绍</title>
    <link href="https://blog.no-claw.com/posts/ebd08f3f/"/>
    <id>https://blog.no-claw.com/posts/ebd08f3f/</id>
    <published>2024-07-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着数据分析需求的不断增长，高效查询和分析大数据集变得越来越重要。Easysearch 作为一种强大的国产化搜索和分析引擎，同时作为 Elasticsearch 国产替代方案，支持原生 DSL 查询语法和 SQL 查询，确保原业务代码无需调整即可无缝迁移。Easysearch 兼容 ES 7.x 现有的 SDK 和索引存储格式，支持冷热架构和索引生命周期管理，为用户提供了全面的数据处理解决方案。本文将详细介绍如何使用 ES 7.x Python SDK 与 Easysearch 进行交互，包括安装、连接、数据操作和查询等方面。</p><h2 id="1-安装-Elasticsearch-Python-客户端"><a href="#1-安装-Elasticsearch-Python-客户端" class="headerlink" title="1. 安装 Elasticsearch Python 客户端"></a>1. 安装 Elasticsearch Python 客户端</h2><p>要使用 Elasticsearch Python 客户端，首先需要通过<code>pip</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">pip install elasticsearch==7.13.1</span><br></pre></td></tr></table></figure><p>如果使用默认版本安装，会安装 8.x 的依赖，可能会报错 <code>elasticsearch.UnsupportedProductError: The client noticed that the server is not Elasticsearch and we do not support this unknown product.</code></p><p>由于 Elasticsearch 7.10.2 以后变更了许可模式，引入了 Server Side Public License (SSPL) 和 Elastic License，很多基于 Elasticsearch 7.10.2 分支出来的搜索引擎需要使用 7.x 版本的 SDK 和 agent，比如 Beats 全家桶。</p><p><img src="https://i-blog.csdnimg.cn/direct/c6a4f8799be34a8498aa6b11c43b7788.png" alt="在这里插入图片描述"><br>这是一个获取集群信息的 demo，使用<code>es.cluster.health()</code> 调用 Elasticsearch 集群的健康检查 API，返回集群的健康状态。</p><span id="more"></span><p>由于使用了自签名证书，所以在初始化时加上 <code>verify_certs=False</code> 参数，同时使用 <code>warnings.filterwarnings(&quot;ignore&quot;)</code> 设置 Python 的警告系统，忽略所有发出的警告。这在生产代码中通常不推荐，因为它会隐藏潜在的问题，但在开发或测试环境中，如果警告信息太多干扰调试，可能会暂时使用。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> urllib3</span><br><span class="line"><span class="keyword">import</span> elasticsearch</span><br><span class="line"><span class="keyword">from</span> elasticsearch <span class="keyword">import</span> Elasticsearch</span><br><span class="line"><span class="keyword">import</span> warnings</span><br><span class="line"><span class="keyword">from</span> pprint <span class="keyword">import</span> pprint</span><br><span class="line"></span><br><span class="line"><span class="comment"># 禁用所有警告</span></span><br><span class="line">warnings.filterwarnings(<span class="string">&quot;ignore&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(elasticsearch.VERSION)</span><br><span class="line"><span class="comment"># 禁用警告</span></span><br><span class="line">urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;https://ip:9200/&quot;</span></span><br><span class="line">user_passwd = (<span class="string">&#x27;user&#x27;</span>, <span class="string">&#x27;passwd&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立连接</span></span><br><span class="line">es = Elasticsearch(</span><br><span class="line">    [url],</span><br><span class="line">    http_auth=user_passwd,</span><br><span class="line">    verify_certs=<span class="literal">False</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">health = es.cluster.health()</span><br><span class="line">pprint(health)</span><br></pre></td></tr></table></figure><h2 id="2-准备示例数据"><a href="#2-准备示例数据" class="headerlink" title="2. 准备示例数据"></a>2. 准备示例数据</h2><p>在进行查询之前，我们需要在 Easysearch 中创建一些示例数据。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义示例数据</span></span><br><span class="line">sample_data = [</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;value1&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">10</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;value2&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">20</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;value3&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">30</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value1&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">100</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value2&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">200</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value3&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">300</span>&#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">response = es.bulk(body=sample_data)</span><br><span class="line"><span class="built_in">print</span>(response)</span><br></pre></td></tr></table></figure><h2 id="3-使用-REST-API-进行查询"><a href="#3-使用-REST-API-进行查询" class="headerlink" title="3. 使用 REST API 进行查询"></a>3. 使用 REST API 进行查询</h2><p>REST API 是与 Easysearch 进行通信的常用方式。通过 REST API，开发者可以发送 HTTP 请求来执行各种操作，包括索引文档、搜索数据等。以下示例展示了如何在 Python 中执行 REST 查询。</p><p>由于是 REST API，我们可以先使用 Postman 进行测试。</p><p><img src="https://i-blog.csdnimg.cn/direct/1379de6b582147e6862b04574d6fc58b.png" alt="在这里插入图片描述"></p><p>我们可以看到 HTTP 端点可以正常返回，然后就可以使用编程方式进行访问了：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> requests.auth <span class="keyword">import</span> HTTPBasicAuth</span><br><span class="line"><span class="keyword">from</span> pprint <span class="keyword">import</span> pprint</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;https://ip:9200/&quot;</span></span><br><span class="line">user_passwd = (<span class="string">&#x27;user&#x27;</span>, <span class="string">&#x27;passwd&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建查询参数</span></span><br><span class="line">query = &#123;</span><br><span class="line">    <span class="string">&quot;query&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;match&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;field&quot;</span>: <span class="string">&quot;value1&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">dsl = <span class="string">f&quot;<span class="subst">&#123;url&#125;</span>/my_index/_search&quot;</span></span><br><span class="line"></span><br><span class="line">response = requests.get(dsl, json=query, auth=HTTPBasicAuth(*user_passwd), verify=<span class="literal">False</span>)</span><br><span class="line">pprint(response.json())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理查询结果</span></span><br><span class="line"><span class="keyword">if</span> response.status_code == <span class="number">200</span>:</span><br><span class="line">    results = response.json()</span><br><span class="line">    <span class="keyword">for</span> hit <span class="keyword">in</span> results[<span class="string">&#x27;hits&#x27;</span>][<span class="string">&#x27;hits&#x27;</span>]:</span><br><span class="line">        <span class="built_in">print</span>(hit)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Error: <span class="subst">&#123;response.status_code&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="4-通过-DSL-对索引数据进行增删改查"><a href="#4-通过-DSL-对索引数据进行增删改查" class="headerlink" title="4. 通过 DSL 对索引数据进行增删改查"></a>4. 通过 DSL 对索引数据进行增删改查</h2><p>DSL（Domain-Specific Language）是 Easysearch 的原生查询语言，允许用户构建复杂的查询。以下是一些示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 构建 DSL 查询</span></span><br><span class="line">dsl_query = &#123;</span><br><span class="line">    <span class="string">&quot;query&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;match&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;field&quot;</span>: <span class="string">&quot;value1&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"># 执行 DSL 查询</span></span><br><span class="line">response = es.search(index=<span class="string">&quot;my_index&quot;</span>, body=dsl_query)</span><br><span class="line"></span><br><span class="line">results = response.get(<span class="string">&quot;hits&quot;</span>)</span><br><span class="line"><span class="comment"># 处理查询结果</span></span><br><span class="line"><span class="keyword">if</span> results:</span><br><span class="line">    <span class="keyword">for</span> hit <span class="keyword">in</span> results[<span class="string">&#x27;hits&#x27;</span>]:</span><br><span class="line">        <span class="built_in">print</span>(hit)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Error: <span class="subst">&#123;response.status_code&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="插入数据"><a href="#插入数据" class="headerlink" title="插入数据"></a>插入数据</h3><p>如果不指定 document ID，那么随机生成一个 ID 并写入。</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></pre></td><td class="code"><pre><span class="line">doc = &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;value4&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">9999</span>&#125;</span><br><span class="line">response = es.index(index=<span class="string">&quot;my_index&quot;</span>, body=doc)</span><br><span class="line"><span class="built_in">print</span>(response)</span><br></pre></td></tr></table></figure><h3 id="更新数据"><a href="#更新数据" class="headerlink" title="更新数据"></a>更新数据</h3><p>指定 ID 为 1 来手动更新索引：</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></pre></td><td class="code"><pre><span class="line">doc = &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;value4&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">9999</span>&#125;</span><br><span class="line">response = es.index(index=<span class="string">&quot;my_index&quot;</span>, body=doc, <span class="built_in">id</span>=<span class="number">1</span>)</span><br><span class="line"><span class="built_in">print</span>(response)</span><br></pre></td></tr></table></figure><h3 id="更新单条数据"><a href="#更新单条数据" class="headerlink" title="更新单条数据"></a>更新单条数据</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 更新单条数据</span></span><br><span class="line">update_body = &#123;<span class="string">&quot;doc&quot;</span>: &#123;<span class="string">&quot;another_field&quot;</span>: <span class="number">50</span>&#125;&#125;</span><br><span class="line">response = es.update(index=<span class="string">&quot;my_index&quot;</span>, <span class="built_in">id</span>=<span class="string">&quot;1&quot;</span>, body=update_body)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h3 id="删除数据"><a href="#删除数据" class="headerlink" title="删除数据"></a>删除数据</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除单条数据</span></span><br><span class="line">response = es.delete(index=<span class="string">&quot;my_index&quot;</span>, <span class="built_in">id</span>=<span class="string">&quot;1&quot;</span>)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h2 id="5-索引数据-SQL-查询"><a href="#5-索引数据-SQL-查询" class="headerlink" title="5. 索引数据 SQL 查询"></a>5. 索引数据 SQL 查询</h2><p>创建客户端实例后，我们可以使用 <code>sql</code> 方法执行 SQL 查询。以下示例展示了如何执行一个简单的 SELECT 查询。</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="comment"># 执行 SQL 查询</span></span><br><span class="line">query_sql = &#123;</span><br><span class="line">    <span class="string">&quot;query&quot;</span>: <span class="string">&quot;SELECT * FROM my_index&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">res = es.sql.query(body=query_sql)</span><br><span class="line">pprint(res)</span><br></pre></td></tr></table></figure><h2 id="6-索引数据批量操作"><a href="#6-索引数据批量操作" class="headerlink" title="6. 索引数据批量操作"></a>6. 索引数据批量操作</h2><p>Bulk API 允许用户一次性对多个文档进行创建、更新或删除操作，极大提高了操作效率。以下是一些示例：</p><h3 id="批量插入数据"><a href="#批量插入数据" class="headerlink" title="批量插入数据"></a>批量插入数据</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义批量插入数据</span></span><br><span class="line">bulk_data = [</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value1&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">100</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value2&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">200</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;index&quot;</span>: &#123;<span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;field&quot;</span>: <span class="string">&quot;bulk_value3&quot;</span>, <span class="string">&quot;another_field&quot;</span>: <span class="number">300</span>&#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">response = es.bulk(body=bulk_data)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h3 id="批量更新数据"><a href="#批量更新数据" class="headerlink" title="批量更新数据"></a>批量更新数据</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义批量更新数据</span></span><br><span class="line">bulk_update_data = [</span><br><span class="line">    &#123;<span class="string">&quot;update&quot;</span>: &#123;<span class="string">&quot;_id&quot;</span>: <span class="string">&quot;1&quot;</span>, <span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;doc&quot;</span>: &#123;<span class="string">&quot;another_field&quot;</span>: <span class="number">110</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;update&quot;</span>: &#123;<span class="string">&quot;_id&quot;</span>: <span class="string">&quot;2&quot;</span>, <span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;doc&quot;</span>: &#123;<span class="string">&quot;another_field&quot;</span>: <span class="number">220</span>&#125;&#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">response = es.bulk(body=bulk_update_data)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h3 id="批量删除数据"><a href="#批量删除数据" class="headerlink" title="批量删除数据"></a>批量删除数据</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义批量删除数据</span></span><br><span class="line">bulk_delete_data = [</span><br><span class="line">    &#123;<span class="string">&quot;delete&quot;</span>: &#123;<span class="string">&quot;_id&quot;</span>: <span class="string">&quot;1&quot;</span>, <span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;delete&quot;</span>: &#123;<span class="string">&quot;_id&quot;</span>: <span class="string">&quot;2&quot;</span>, <span class="string">&quot;_index&quot;</span>: <span class="string">&quot;my_index&quot;</span>&#125;&#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">response = es.bulk(body=bulk_delete_data)</span><br><span class="line"><span class="built_in">print</span>(response)</span><br></pre></td></tr></table></figure><h2 id="7-索引级别的操作"><a href="#7-索引级别的操作" class="headerlink" title="7. 索引级别的操作"></a>7. 索引级别的操作</h2><p>接下来，介绍索引创建、删除和检查索引是否存在操作。以下是一些示例：</p><h3 id="创建索引"><a href="#创建索引" class="headerlink" title="创建索引"></a>创建索引</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建索引</span></span><br><span class="line">index_body = &#123;</span><br><span class="line">    <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;number_of_shards&quot;</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="string">&quot;number_of_replicas&quot;</span>: <span class="number">0</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;mappings&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;field&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&quot;</span>&#125;,</span><br><span class="line">            <span class="string">&quot;another_field&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;integer&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">response = es.indices.create(index=<span class="string">&quot;new_index&quot;</span>, body=index_body)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h3 id="删除索引"><a href="#删除索引" class="headerlink" title="删除索引"></a>删除索引</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除索引</span></span><br><span class="line">response = es.indices.delete(index=<span class="string">&quot;new_index&quot;</span>)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h3 id="检查索引是否存在"><a href="#检查索引是否存在" class="headerlink" title="检查索引是否存在"></a>检查索引是否存在</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 检查索引是否存在</span></span><br><span class="line">response = es.indices.exists(index=<span class="string">&quot;new_index&quot;</span>)</span><br><span class="line">pprint(response)</span><br></pre></td></tr></table></figure><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h2><p>Easysearch 虽然没有专门的 Python SDK，但完全兼容 ES 7.x 的 Python SDK 客户端，这为开发者提供了极大的便利。通过使用 ES 7.x Python SDK，开发者可以轻松地使用 DSL 和 SQL 语法对 Easysearch 进行查询和数据操作。Easysearch 主要优势包括：</p><ol><li>兼容性强：无需修改现有代码，即可从 ES 迁移到 Easysearch。</li><li>功能全面：支持 DSL 查询、SQL 查询、批量操作等高级功能。</li><li>易于使用：提供简洁明了的 API，降低学习成本。</li><li>高效性能：批量操作 API 大幅提高数据处理效率。</li></ol><p>Easysearch 结合 ES 7.x Python SDK 的强大功能，为开发者提供了一个高效、灵活的大数据处理平台。无论是执行简单的 SQL 查询，还是构建复杂的 DSL 查询，都能满足各种数据分析需求。如果您正在寻找一个强大的搜索和分析解决方案，Easysearch 绝对值得一试。它不仅能帮助您更高效地处理和分析大数据集，还能为数据驱动的决策提供有力支持。</p>]]></content>
    
    
    <summary type="html">使用 Elasticsearch Python SDK 连接和查询 Easysearch 的实战教程</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 数据可视化和管理平台：INFINI Console 使用介绍</title>
    <link href="https://blog.no-claw.com/posts/ebd08f3f/"/>
    <id>https://blog.no-claw.com/posts/ebd08f3f/</id>
    <published>2024-07-03T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="什么是-Easysearch"><a href="#什么是-Easysearch" class="headerlink" title="什么是 Easysearch"></a>什么是 Easysearch</h1><p><strong>Elasticsearch</strong> 是一个基于 Apache Lucene 的开源分布式搜索和分析引擎，它被广泛应用于全文搜索、结构化搜索和分析等多种场景中。作为 Elasticsearch 的国产化替代方案，<strong>Easysearch</strong> 不仅保持了与原生 Elasticsearch 的高度兼容性，还在功能、性能、稳定性和扩展性方面进行了全面提升。对于开发团队来说，从 Elasticsearch 切换到 Easysearch 不需要做任何业务代码的调整，确保了无缝衔接和平滑迁移。</p><p>Easysearch 是基于 Elasticsearch 7.10.2 开源版本二次开发，所以支持 Elasticsearch 原始的 Query DSL 语法，基本的 SQL 语法，并且兼容现有 Elasticsearch 的 SDK，使得应用无需修改代码即可进行迁移。其平滑的迁移特性，如基于网关的无缝跨版本迁移与升级，提供了随时安全回退的能力。</p><p>在之前的文章中，我们已经介绍了 <a href="https://blog.csdn.net/weixin_38781498/article/details/140077785?spm=1001.2014.3001.5502">Easysearch 的搭建</a> 和 <a href="https://blog.csdn.net/weixin_38781498/article/details/140165173?spm=1001.2014.3001.5502">可视化工具的使用</a>，今天我们将探讨 Easysearch 集群的基本概念和常用的 API。</p><h4 id="Easysearch-集群的核心概念"><a href="#Easysearch-集群的核心概念" class="headerlink" title="Easysearch 集群的核心概念"></a>Easysearch 集群的核心概念</h4><p>Easysearch 集群由以下几个核心概念构成：</p><ol><li><strong>节点（Node）</strong>：集群中的单个服务器，负责存储数据并参与集群的索引和搜索功能。</li><li><strong>集群（Cluster）</strong>：由一个或多个节点组成，拥有唯一的集群名，协同完成数据索引和查询任务。</li><li><strong>索引（Index）</strong>：存储相关数据的容器，类似于关系数据库中的数据库，一个索引包含多个文档。</li><li><strong>文档（Document）</strong>：索引中的基本数据单位，相当于关系数据库中的行。</li><li><strong>字段（Field）</strong>：文档中的一个属性，相当于数据库中的列。</li><li><strong>分片（Shard）</strong>：为了提高性能和扩展性，索引可以被分割成多个分片，每个分片是索引的一个部分。</li><li><strong>副本（Replica）</strong>：分片的副本，用于提高数据的可靠性和在节点出现故障时的可用性。</li></ol><p>通过多个 API，例如 <code>_cluster/health</code> 和 <code>_cluster/stats</code>，用户可以轻松查看集群的健康状态和详细信息，这些信息对于维护和优化 Easysearch 集群至关重要。</p><span id="more"></span><p>无论是在性能的提升，还是在功能的兼容性方面，Easysearch 都为用户提供了一个强大的搜索引擎平台，让从 Elasticsearch 到 Easysearch 的迁移变得无缝且高效。掌握其核心概念和 API 的使用，将帮助开发者更好地利用这些工具来构建和优化他们的搜索解决方案。<br><img src="https://i-blog.csdnimg.cn/direct/ad929f527e9146b1bc4aa29d56687170.png" alt="在这里插入图片描述"></p><h2 id="查看集群信息"><a href="#查看集群信息" class="headerlink" title="查看集群信息"></a>查看集群信息</h2><p>在 Easysearch 中，可以通过多个 API 来查看集群的各种信息，包括集群的健康状况、节点信息和索引状态。以下是一些常用的查看集群信息的 API 和示例：</p><h3 id="查看集群健康状况"><a href="#查看集群健康状况" class="headerlink" title="查看集群健康状况"></a>查看集群健康状况</h3><p><code>_cluster/health</code> API 可以查看集群的健康状态，包括集群是否处于正常状态、节点数量、分片状态等。</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">GET /_cluster/health</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_cluster&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;green&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timed_out&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="attr">&quot;number_of_nodes&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;number_of_data_nodes&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_primary_shards&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_shards&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;relocating_shards&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;initializing_shards&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;unassigned_shards&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;delayed_unassigned_shards&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;number_of_pending_tasks&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;number_of_in_flight_fetch&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;task_max_waiting_in_queue_millis&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;active_shards_percent_as_number&quot;</span><span class="punctuation">:</span> <span class="number">100.0</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="查看集群状态"><a href="#查看集群状态" class="headerlink" title="查看集群状态"></a>查看集群状态</h3><p><code>_cluster/stats</code> API 可以查看集群的详细状态，包括索引、节点、分片等信息。</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">GET /_cluster/stats</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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_cluster&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;green&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;indices&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">20</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;primaries&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;replication&quot;</span><span class="punctuation">:</span> <span class="number">1.0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;min&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;max&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;avg&quot;</span><span class="punctuation">:</span> <span class="number">2.0</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</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="attr">&quot;nodes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;coordinating_only&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;master&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ingest&quot;</span><span class="punctuation">:</span> <span class="number">2</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;os&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;available_processors&quot;</span><span class="punctuation">:</span> <span class="number">12</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;allocated_processors&quot;</span><span class="punctuation">:</span> <span class="number">12</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;process&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;cpu&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;percent&quot;</span><span class="punctuation">:</span> <span class="number">10</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;open_file_descriptors&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;min&quot;</span><span class="punctuation">:</span> <span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max&quot;</span><span class="punctuation">:</span> <span class="number">300</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;avg&quot;</span><span class="punctuation">:</span> <span class="number">200</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><h3 id="查看节点信息"><a href="#查看节点信息" class="headerlink" title="查看节点信息"></a>查看节点信息</h3><p><code>_nodes</code> API 可以查看集群中节点的详细信息，包括节点角色、IP 地址、内存使用情况等。</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">GET /_nodes</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;cluster_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_cluster&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;nodes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;node_id_1&quot;</span><span class="punctuation">:</span> <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;node_1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;transport_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.1:9300&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;host&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;roles&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;master&quot;</span><span class="punctuation">,</span> <span class="string">&quot;data&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ingest&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;os&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;available_processors&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocated_processors&quot;</span><span class="punctuation">:</span> <span class="number">4</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;process&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;cpu&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;percent&quot;</span><span class="punctuation">:</span> <span class="number">10</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;open_file_descriptors&quot;</span><span class="punctuation">:</span> <span class="number">200</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="attr">&quot;node_id_2&quot;</span><span class="punctuation">:</span> <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;node_2&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;transport_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.2:9300&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;host&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;roles&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;data&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;os&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;available_processors&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;allocated_processors&quot;</span><span class="punctuation">:</span> <span class="number">4</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;process&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;cpu&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;percent&quot;</span><span class="punctuation">:</span> <span class="number">15</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;open_file_descriptors&quot;</span><span class="punctuation">:</span> <span class="number">150</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><h3 id="查看索引状态"><a href="#查看索引状态" class="headerlink" title="查看索引状态"></a>查看索引状态</h3><p><code>_cat/indices</code> API 可以查看集群中所有索引的状态，包括文档数、存储大小、分片数等信息。</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">GET /_cat/indices?v</span><br></pre></td></tr></table></figure><p>示例响应：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size</span><br><span class="line">green  open   index_1 SxNUd84vRl6QH5P7g0T4Vg   1   1          0            0       230b           230b</span><br><span class="line">green  open   index_2 NxEYib4yToCnA1PpQ8P4Xw   5   1        100            1      10mb           5mb</span><br></pre></td></tr></table></figure><p>这些 API 可以帮助你全面了解 Easysearch 集群的状态和健康状况，从而更好地管理和维护集群。<br><img src="https://i-blog.csdnimg.cn/direct/f6bca171289643e3a6c5700caa032f7e.png" alt="在这里插入图片描述"></p><h2 id="增删改查操作"><a href="#增删改查操作" class="headerlink" title="增删改查操作"></a>增删改查操作</h2><p>在 Easysearch 中，增删改查操作是管理数据和索引的基本功能。以下是如何使用这些操作的详细示例。</p><h3 id="创建索引"><a href="#创建索引" class="headerlink" title="创建索引"></a>创建索引</h3><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></pre></td><td class="code"><pre><span class="line">PUT /my_index</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;number_of_shards&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;number_of_replicas&quot;</span><span class="punctuation">:</span> <span class="number">2</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><h3 id="删除索引"><a href="#删除索引" class="headerlink" title="删除索引"></a>删除索引</h3><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">DELETE /my_index</span><br></pre></td></tr></table></figure><h3 id="添加文档"><a href="#添加文档" class="headerlink" title="添加文档"></a>添加文档</h3><p>通过 POST 或 PUT 请求向索引中添加文档：</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></pre></td><td class="code"><pre><span class="line">POST /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">PUT /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/a4ae6c3b1d9e4eb7bb6aa9734a291659.png" alt="插入图片描述"><br><code>POST</code> 和 <code>PUT</code> 方法用于不同的操作，尽管它们都可以用于添加或更新文档，但它们的行为有所不同。</p><p><code>POST /my_index/_doc/1</code> 方法用于创建或替换一个文档。如果指定的文档 ID 已经存在，<code>POST</code> 请求将更新整个文档（不会合并字段）。如果文档 ID 不存在，它将创建一个新的文档。</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></pre></td><td class="code"><pre><span class="line">POST /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><code>PUT /my_index/_doc/1</code> 方法通常用于创建一个新的文档，或者完全替换一个已存在的文档。与 <code>POST</code> 类似，如果指定的文档 ID 已经存在，<code>PUT</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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PUT /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol><li><p><strong>使用场景</strong>：</p><ul><li><code>POST</code>：更适合用于添加或部分更新文档，即使文档 ID 已经存在。</li><li><code>PUT</code>：更适合用于创建或完全替换文档。</li></ul></li><li><p><strong>ID 自动生成</strong>：</p><ul><li><code>POST</code> 请求可以不提供文档 ID，此时 Easysearch 会自动生成一个文档 ID。</li><li><code>PUT</code> 请求必须提供文档 ID，如果未提供，则会返回错误。</li></ul></li><li><p><strong>部分更新</strong>：</p><ul><li><code>POST</code> 请求可以用于部分更新（通过 <code>_update</code> API）。</li><li><code>PUT</code> 请求用于完全替换文档，不支持部分更新。</li></ul></li></ol><p>如果文档 ID 已经存在，<code>POST</code> 和 <code>PUT</code> 都会覆盖整个文档，并且效果是一样的。但是，通常 <code>POST</code> 用于提交数据，而 <code>PUT</code> 用于上传和替换资源。</p><ol><li><strong>使用 <code>POST</code> 方法添加或更新文档</strong>：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="2"><li><strong>使用 <code>PUT</code> 方法添加或更新文档</strong>：</li></ol><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></pre></td><td class="code"><pre><span class="line">PUT /my_index/_doc/<span class="number">1</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;John Doe&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">30</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>在这两个示例中，结果都是在索引 <code>my_index</code> 中创建或更新文档 ID 为 <code>1</code> 的文档。无论使用 <code>POST</code> 还是 <code>PUT</code>，如果文档 ID 已存在，都会覆盖原有的文档内容。</p><h3 id="新建文档"><a href="#新建文档" class="headerlink" title="新建文档"></a>新建文档</h3><p>使用 <code>_create</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></pre></td><td class="code"><pre><span class="line">PUT /my_index/_create/<span class="number">1</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;a&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line"><span class="punctuation">&#125;</span></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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;root_cause&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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;version_conflict_engine_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[1]: version conflict, document already exists (current version [1])&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index_uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1xWdHLTaTm6l6HbqACaIEA&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;version_conflict_engine_exception&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;reason&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[1]: version conflict, document already exists (current version [1])&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;index_uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1xWdHLTaTm6l6HbqACaIEA&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;shard&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&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;status&quot;</span><span class="punctuation">:</span> <span class="number">409</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="获取文档"><a href="#获取文档" class="headerlink" title="获取文档"></a>获取文档</h3><p>通过 ID 获取文档的详细信息：</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">GET /my_index/_doc/<span class="number">1</span></span><br></pre></td></tr></table></figure><h3 id="更新文档"><a href="#更新文档" class="headerlink" title="更新文档"></a>更新文档</h3><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></pre></td><td class="code"><pre><span class="line">POST /my_index/_update/<span class="number">1</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;doc&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">31</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><h3 id="删除文档"><a href="#删除文档" class="headerlink" title="删除文档"></a>删除文档</h3><p>通过 ID 删除指定的文档：</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">DELETE /my_index/_doc/<span class="number">1</span></span><br></pre></td></tr></table></figure><h3 id="查询所有文档"><a href="#查询所有文档" class="headerlink" title="查询所有文档"></a>查询所有文档</h3><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></pre></td><td class="code"><pre><span class="line">GET /my_index/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;match_all&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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><p>这个是《<a href="https://space.bilibili.com/1171585214/video">老杨玩搜索</a>》中总结的图，可以作为“小抄”来记忆：</p><p><img src="https://i-blog.csdnimg.cn/direct/20c8add0cbbf43cc94dc121c4152ef6c.png" alt="插入图片描述"></p><h3 id="批量操作-bulk-API"><a href="#批量操作-bulk-API" class="headerlink" title="批量操作 (_bulk API)"></a>批量操作 (_bulk API)</h3><p>_bulk API 用于在一次请求中执行多个索引、删除和更新操作，这对于批量处理大规模数据非常有用，可以显著提高性能和效率。以下是 _bulk API 的基本使用示例：</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></pre></td><td class="code"><pre><span class="line">POST /my_index/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;John Doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">30</span><span class="punctuation">,</span> <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Jane Doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">25</span><span class="punctuation">,</span> <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Designer&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;update&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;doc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">31</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/3e75c1a0b38c4677b6e4ab8ff5d71b8e.png" alt="插入图片描述"></p><p>_bulk API 的请求体由多个操作和文档组成。每个操作行包含一个动作描述行和一个可选的源文档行。动作描述行指明了操作的类型（例如，index、create、delete、update）以及操作的元数据。源文档行则包含了实际的数据。</p><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></pre></td><td class="code"><pre><span class="line">POST /_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;a&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;John Doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">30</span><span class="punctuation">,</span> <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Engineer&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;b&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Jane Doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">25</span><span class="punctuation">,</span> <span class="attr">&quot;occupation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Designer&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;update&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;a&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;doc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">31</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/fec27402104540aa832e126ddf089d23.png" alt="插入图片描述"></p><h2 id="分词器"><a href="#分词器" class="headerlink" title="分词器"></a>分词器</h2><p>在 Easysearch 中，分词器（Analyzer）用于将文本分解为词项（terms），是全文搜索和文本分析的基础。分词器通常由字符过滤器（Character Filters）、分词器（Tokenizer）和词项过滤器（Token Filters）组成。以下是关于分词器的详细介绍：</p><ol><li><strong>字符过滤器（Character Filters）</strong>：在分词之前对文本进行预处理。例如，去除 HTML 标签，替换字符等。</li><li><strong>分词器（Tokenizer）</strong>：将文本分解为词项（tokens）。这是分词过程的核心。</li><li><strong>词项过滤器（Token Filters）</strong>：对词项进行处理，如小写化、去除停用词、词干提取等。<br><img src="https://i-blog.csdnimg.cn/direct/657b071bfd314d39a68f0d55898885bf.png" alt="在这里插入图片描述"></li></ol><p>只有 text 字段支持全文检索，返回的结果根据相似度打分，我们一起看下</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></pre></td><td class="code"><pre><span class="line">POST /index/_mapping</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;analyzer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ik_max_word&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;search_analyzer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ik_smart&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol><li><p><strong>POST &#x2F;index&#x2F;_mapping</strong></p><ul><li>这个部分表示要向名为<code>index</code>的索引添加或更新映射设置。</li></ul></li><li><p><strong>“properties”</strong>:</p><ul><li><code>properties</code>定义了索引中文档的字段结构。在这个例子中，定义了一个名为<code>content</code>的字段。</li></ul></li><li><p><strong>“content”:</strong></p><ul><li>定义了名为<code>content</code>的字段。</li></ul></li><li><p><strong>“type”: “text”</strong></p><ul><li><code>type</code>字段指定<code>content</code>字段的数据类型为<code>text</code>。<code>text</code>类型适用于需要分词和全文搜索的字段。</li></ul></li><li><p><strong>“analyzer”: “ik_max_word”</strong></p><ul><li><code>analyzer</code>字段指定索引时使用的分词器为<code>ik_max_word</code>。<code>ik_max_word</code>是 IK 分词器中的一种，它会尽可能多地将文本分解为更多的词项。</li></ul></li><li><p><strong>“search_analyzer”: “ik_smart”</strong></p><ul><li><code>search_analyzer</code>字段指定搜索时使用的分词器为<code>ik_smart</code>。<code>ik_smart</code>是 IK 分词器中的另一种，它会更智能地进行分词，以提高搜索的准确性。</li></ul></li></ol><p>当然，在设置这个 mapping 的时候可以使用同样的分词器，也可以使用不同的分词器。这里介绍下 IK 分词器：</p><ul><li><strong>IK 分词器</strong>是一种中文分词器，适用于中文文本的分词。IK 分词器有两种分词模式：<code>ik_max_word</code>和<code>ik_smart</code>。<ul><li><code>ik_max_word</code>：将文本尽可能多地切分成词项，适用于需要更高召回率的场景。</li><li><code>ik_smart</code>：进行最智能的分词，适用于需要更高精度的搜索场景。</li></ul></li></ul><p>这个 DSL 的设置意味着，在向这个索引添加或更新文档时，<code>content</code>字段的文本会使用<code>ik_max_word</code>分词器进行分词处理，以确保文本被尽可能多地切分成词项。而在搜索时，<code>content</code>字段的文本会使用<code>ik_smart</code>分词器进行分词处理，以提高搜索的准确性和相关性。</p><p>以下是关于 standard，ik_smart，ik_max_word 这几个分词器的对比：</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><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">GET /_analyze</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokenizer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;standard&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;text&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><br><span class="line">GET /_analyze</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokenizer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ik_smart&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;text&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><br><span class="line">GET /_analyze</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokenizer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ik_max_word&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;text&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><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><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></pre></td><td class="code"><pre><span class="line"># GET /_analyze （standard）</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokens&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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;IDEOGRAPHIC&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">0</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;机&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;IDEOGRAPHIC&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">1</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;器&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;IDEOGRAPHIC&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">2</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;人&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;IDEOGRAPHIC&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">3</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><span class="line"># GET /_analyze（ik_smart）</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokens&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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_CHAR&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">0</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;机器人&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_WORD&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">1</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><span class="line"># GET /_analyze （ik_max_word）</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tokens&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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_CHAR&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">0</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;机器人&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_WORD&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">1</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;机器&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_WORD&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">2</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;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;人&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;start_offset&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;end_offset&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN_CHAR&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;position&quot;</span><span class="punctuation">:</span> <span class="number">3</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><img src="https://i-blog.csdnimg.cn/direct/f1b236c741714502ba76805f39f2e8c0.png" alt="在这里插入图片描述"></p><p>如果使用了不存在的分词器会出现这个错误。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">&#123;</span><br><span class="line">  &quot;error&quot;: &#123;</span><br><span class="line">    &quot;root_cause&quot;: [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;type&quot;: &quot;illegal_argument_exception&quot;,</span><br><span class="line">        &quot;reason&quot;: &quot;failed to find global tokenizer under [simple]&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    &quot;type&quot;: &quot;illegal_argument_exception&quot;,</span><br><span class="line">    &quot;reason&quot;: &quot;failed to find global tokenizer under [simple]&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;status&quot;: 400</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="精确搜索-正则表达式搜索-通配符"><a href="#精确搜索-正则表达式搜索-通配符" class="headerlink" title="精确搜索&#x2F;正则表达式搜索&#x2F;通配符"></a>精确搜索&#x2F;正则表达式搜索&#x2F;通配符</h2><p>在 Easysearch 中，精确搜索、正则表达式搜索和通配符搜索是三种不同的搜索方式，各有其应用场景和特点：</p><ol><li><p>**精确搜索 (Term Query)**：</p><ul><li>精确搜索用于查找与搜索词完全匹配的文档。</li><li>不进行分词处理，通常用于关键字、ID、标签等字段的精确匹配。</li><li>适用于结构化数据或不需要分词的字段（如数字、日期、布尔值等）。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;active&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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></li><li><p>**正则表达式搜索 (Regexp Query)**：</p><ul><li>正则表达式搜索用于基于正则表达式模式匹配的文档搜索。</li><li>支持复杂的字符串匹配模式，但性能可能较低，特别是当数据量较大时。</li><li>适用于需要灵活且复杂匹配条件的搜索。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;regexp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch .*powerful&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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></li><li><p>**通配符搜索 (Wildcard Query)**：</p><ul><li>通配符搜索用于通过通配符模式匹配文档。</li><li>支持 <code>?</code>（匹配单个字符）和 <code>*</code>（匹配零个或多个字符）。</li><li>性能相对较差，因为通配符搜索可能需要扫描大量数据。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;wildcard&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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></li></ol><ul><li><strong>精确搜索</strong>：用于需要绝对匹配特定词语或不需要分词的字段。例如，查找特定用户 ID 或状态。</li><li><strong>正则表达式搜索</strong>：用于需要复杂字符串模式匹配的场景，但要谨慎使用，避免性能问题。</li><li><strong>通配符搜索</strong>：用于简单模式匹配，但同样需要注意性能影响，尽量避免在大数据集上频繁使用。</li></ul><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></pre></td><td class="code"><pre><span class="line">POST /users/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john_doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;active&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john.doe@example.com&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;John loves Easysearch  and open-source technologies.&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jane_doe&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;inactive&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jane.doe@example.com&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Jane is a data scientist working with big data.&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john_smith&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;active&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john.smith@example.com&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;John enjoys hiking and nature.&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;alice_jones&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;active&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;alice.jones@example.com&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Alice is a software engineer specialized in JavaScript.&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">5</span> <span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bob_jones&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;inactive&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bob.jones@example.com&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Bob is an AI enthusiast and machine learning expert.&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol><li><strong>精确查询</strong>：查询状态为 “active” 的用户。</li></ol><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">GET /users/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;active&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="2"><li><strong>通配符查询</strong>：查询 bio 字段中包含 “John” 开头的词。</li></ol><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">GET /users/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;wildcard&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;bio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;John*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="3"><li><strong>正则表达式查询</strong>：查询用户名以 “john” 开头的用户。</li></ol><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">GET /users/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;regexp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;john.*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>通过这些例子，你可以看到如何在 Easysearch 中使用批量导入数据，然后使用各种查询方法来检索特定条件的数据。这些查询方法可以帮助你高效地搜索和分析数据，以满足不同的业务需求。</p><p>这里同样是《<a href="https://space.bilibili.com/1171585214/video">老杨玩搜索</a>》中总结的“小抄”来方便记忆：<br><img src="https://i-blog.csdnimg.cn/direct/0b65a57a846145099f4f025fec9b2235.png" alt="在这里插入图片描述"><img src="https://i-blog.csdnimg.cn/direct/97fcf2437f7b4024b5156d47537b824b.png" alt="在这里插入图片描述"></p><p><img src="https://i-blog.csdnimg.cn/direct/b35f8927e1094b9d9d0ef64db93936bd.png" alt="在这里插入图片描述"></p><h2 id="多字段查询"><a href="#多字段查询" class="headerlink" title="多字段查询"></a>多字段查询</h2><p>在 Easysearch 中，多字段查询允许您在多个字段上同时执行搜索，以获取更精确的结果。最常用的多字段查询类型是 <code>multi_match</code> 查询。<code>multi_match</code> 查询是 <code>match</code> 查询的扩展，能够在多个字段中搜索指定的关键词。</p><p><code>multi_match</code> 查询支持多种匹配模式，如 <code>best_fields</code>、<code>most_fields</code>、<code>cross_fields</code>、<code>phrase</code> 和 <code>phrase_prefix</code>。以下是各模式的简要介绍：</p><ul><li><strong>best_fields</strong>：默认模式，选择匹配度最高的字段。</li><li><strong>most_fields</strong>：计算每个字段的匹配度，然后将匹配度相加。</li><li><strong>cross_fields</strong>：将多个字段视为一个字段进行匹配，适用于分析文本被分散到多个字段的情况。</li><li><strong>phrase</strong>：短语匹配，确保词项顺序与查询相同。</li><li><strong>phrase_prefix</strong>：短语前缀匹配，允许词项的部分匹配。</li></ul><p><img src="https://i-blog.csdnimg.cn/direct/4e042ee10e5e47e0916fe1426032cd97.png" alt="在这里插入图片描述"></p><p>我们先导入一些示例数据到一个索引 <code>documents</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><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">POST /documents/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch  Guide&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This is an introductory guide to Easysearch .&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Advanced Easysearch &quot;</span><span class="punctuation">,</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This guide covers advanced topics in Easysearch .&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch  in Action&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Practical guide to Easysearch  usage.&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Learning Easysearch &quot;</span><span class="punctuation">,</span> <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Beginner&#x27;s guide to learning Easysearch .&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>我们将使用 <code>multi_match</code> 查询在 <code>title</code> 和 <code>content</code> 字段中同时搜索关键词。</p><ol><li><strong>基本 multi_match 查询</strong>：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;guide&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><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><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="2"><li>**指定匹配模式为 <code>best_fields</code>**：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;guide&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;best_fields&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="3"><li>**指定匹配模式为 <code>most_fields</code>**：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;guide&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;most_fields&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="4"><li><strong>使用 <code>cross_fields</code> 模式</strong>：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch  guide&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cross_fields&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="5"><li>**短语匹配 (phrase)**：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;introductory guide&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;phrase&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ol start="6"><li>**短语前缀匹配 (phrase_prefix)**：</li></ol><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></pre></td><td class="code"><pre><span class="line">POST /documents/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;introductory gui&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;title&quot;</span><span class="punctuation">,</span> <span class="string">&quot;content&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;phrase_prefix&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ul><li><strong>query</strong>：要搜索的关键词或短语。</li><li><strong>fields</strong>：要搜索的字段列表，可以包含一个或多个字段。</li><li><strong>type</strong>：指定匹配模式，默认为 <code>best_fields</code>。</li></ul><p>使用 <code>multi_match</code> 查询，您可以在多个字段上同时执行搜索，获得更精确和全面的结果。通过指定不同的匹配模式，您可以调整查询行为以满足特定的搜索需求。无论是基本关键词匹配、短语匹配还是跨字段匹配，<code>multi_match</code> 查询都提供了强大的功能来处理复杂的搜索场景。</p><p>除此之外，还可以使用 boost 参数用于调整特定字段的权重，从而影响搜索结果的相关性评分。multi_match 查询支持为不同字段设置不同的 boost 值，以便在搜索结果中优先显示某些字段的匹配项。<br><img src="https://i-blog.csdnimg.cn/direct/2660ad4a33aa414f9a9fd9e035dec4db.png" alt="在这里插入图片描述"></p><h2 id="布尔查询"><a href="#布尔查询" class="headerlink" title="布尔查询"></a>布尔查询</h2><p>布尔查询是 Easysearch 中非常强大且灵活的一种查询方式，它允许用户通过组合多个查询条件来实现复杂的搜索需求。布尔查询使用 <code>bool</code> 查询类型，可以包含以下几种子句：<code>must</code>、<code>filter</code>、<code>must_not</code> 和 <code>should</code>。每种子句都有其特定的用途和语义。</p><ol><li><strong>must</strong>：<ul><li>包含在 <code>must</code> 数组中的查询条件必须匹配，类似于逻辑上的 AND 操作。</li><li>如果有多个条件，所有条件都必须满足，文档才会被包含在结果集中。</li></ul></li><li><strong>filter</strong>：<ul><li>包含在 <code>filter</code> 数组中的查询条件必须匹配，但它不会影响评分。</li><li><code>filter</code> 子句通常用于对性能要求较高的过滤操作，因为它不计算相关性评分。</li></ul></li><li><strong>must_not</strong>：<ul><li>包含在 <code>must_not</code> 数组中的查询条件必须不匹配，类似于逻辑上的 NOT 操作。</li><li>如果有任何一个条件匹配，文档就会被排除在结果集之外。</li></ul></li><li><strong>should</strong>：<ul><li>包含在 <code>should</code> 数组中的查询条件至少匹配一个。</li><li>如果布尔查询中没有 <code>must</code> 子句，则至少要匹配一个 <code>should</code> 子句。</li><li><code>should</code> 子句在计算相关性评分时也有影响。</li></ul></li><li><strong>minimum_should_match</strong>：<ul><li>指定 <code>should</code> 子句中至少需要满足的条件数量。</li></ul></li></ol><p><img src="https://i-blog.csdnimg.cn/direct/e2c3614280d840729986444846e3aced.png" alt="在这里插入图片描述"></p><p>首先，我们需要创建一个名为<code>books</code>的索引，并定义它的映射（mappings）。映射用于指定每个字段的数据类型。在这个例子中，<code>类别</code>和<code>书名</code>字段都被定义为<code>keyword</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><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">PUT /books</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>接下来，我们使用批量操作（bulk API）将一些示例数据导入到<code>books</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><span class="line">6</span><br><span class="line">7</span><br><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">POST /books/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我的阿勒泰&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;平凡的世界&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;科学&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;时间简史&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;百年孤独&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">5</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;红楼梦&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>现在，我们使用布尔查询来搜索<code>类别</code>为“文学”并且<code>书名</code>为“我的阿勒泰”的文档。这里使用的是<code>must</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><span class="line">6</span><br><span class="line">7</span><br><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">POST /books/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我的阿勒泰&quot;</span> <span class="punctuation">&#125;</span> <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><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><p>我们还可以使用<code>filter</code>子句来执行相同的查询。<code>filter</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><span class="line">6</span><br><span class="line">7</span><br><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">POST /books/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;filter&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;书名&quot;</span><span class="punctuation">:</span> <span class="string">&quot;我的阿勒泰&quot;</span> <span class="punctuation">&#125;</span> <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><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><p>当我们执行上述查询时，期望返回的结果是<code>books</code>索引中类别为“文学”且书名为“我的阿勒泰”的文档。无论是使用<code>must</code>还是<code>filter</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;relation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eq&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;hits&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;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;books&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;类别&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文学&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;书名&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><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><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/fcf86d3fc74d4bc2be35541b2bcc8e3d.png" alt="在这里插入图片描述"></p><p><img src="https://i-blog.csdnimg.cn/direct/cccc263265fd4c25aabec0cca51298f7.png" alt="在这里插入图片描述"></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><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">POST /_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;filter&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;production&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;must_not&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;range&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="punctuation">&#123;</span> <span class="attr">&quot;gte&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span> <span class="attr">&quot;lte&quot;</span><span class="punctuation">:</span> <span class="number">20</span> <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;should&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;env1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deployed&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;minimum_should_match&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;boost&quot;</span><span class="punctuation">:</span> <span class="number">1.0</span></span><br><span class="line">    <span class="punctuation">&#125;</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><ul><li><strong><code>must</code> 子句</strong>：必须匹配的条件，文档必须包含<code>user.id</code>为<code>kimchy</code>。</li><li><strong><code>filter</code> 子句</strong>：过滤条件，文档必须包含<code>tags</code>为<code>production</code>，但不会影响评分。</li><li><strong><code>must_not</code> 子句</strong>：不匹配的条件，文档的<code>age</code>字段不能在 10 到 20 之间。</li><li><strong><code>should</code> 子句</strong>：可选匹配条件，至少需要匹配一个<code>should</code>子句中的条件。这里要求<code>tags</code>字段匹配<code>env1</code>或<code>deployed</code>。</li><li>**<code>minimum_should_match</code>**：至少需要匹配一个<code>should</code>子句中的条件。</li><li>**<code>boost</code>**：提升查询的整体评分。</li></ul><p>为了展示这个 DSL，我们需要创建一个索引并导入一些数据。假设我们要在 Easysearch 中创建一个索引<code>users</code>，并插入一些测试数据。</p><p><strong>创建索引</strong></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></pre></td><td class="code"><pre><span class="line">PUT /users</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integer&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p><strong>批量导入数据</strong></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></pre></td><td class="code"><pre><span class="line">POST /users/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;production&quot;</span><span class="punctuation">,</span> <span class="string">&quot;env1&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">25</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;production&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">15</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;deployed&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">30</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;test&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">35</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">5</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;other&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;production&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">25</span> <span class="punctuation">&#125;</span></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><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">POST /users/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;filter&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;production&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;must_not&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;range&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="punctuation">&#123;</span> <span class="attr">&quot;gte&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span> <span class="attr">&quot;lte&quot;</span><span class="punctuation">:</span> <span class="number">20</span> <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;should&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;env1&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span> <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deployed&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;minimum_should_match&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;boost&quot;</span><span class="punctuation">:</span> <span class="number">1.0</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>根据以上查询，预期返回的结果应该符合以下条件：</p><ol><li><code>user.id</code>必须是<code>kimchy</code>（由<code>must</code>子句决定）。</li><li><code>tags</code>必须包含<code>production</code>（由<code>filter</code>子句决定）。</li><li><code>age</code>字段不在 10 到 20 之间（由<code>must_not</code>子句决定）。</li><li><code>tags</code>字段中至少包含<code>env1</code>或<code>deployed</code>中的一个（由<code>should</code>子句和<code>minimum_should_match</code>参数决定）。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;took&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timed_out&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="attr">&quot;_shards&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;successful&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;skipped&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;failed&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hits&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;total&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;relation&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eq&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;max_score&quot;</span><span class="punctuation">:</span> <span class="number">1.3769134</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hits&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;_index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;users&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;_doc&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_score&quot;</span><span class="punctuation">:</span> <span class="number">1.3769134</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;_source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;user.id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kimchy&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;production&quot;</span><span class="punctuation">,</span> <span class="string">&quot;env1&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">25</span></span><br><span class="line">        <span class="punctuation">&#125;</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><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="SQL-搜索"><a href="#SQL-搜索" class="headerlink" title="SQL 搜索"></a>SQL 搜索</h2><p>Easysearch 直接支持 SQL 查询，无需额外安装插件，同时兼容 Elasticsearch 的 SQL 调用方式，还可以直接编写原生 SQL 查询。<br><img src="https://i-blog.csdnimg.cn/direct/5ae0b6a7476746b8ab2c26ce16044488.png" alt="SQL查询示例"></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><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">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> my_index;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> my_index LIMIT <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> my_index <span class="keyword">ORDER</span> <span class="keyword">BY</span> name;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> name <span class="keyword">AS</span> full_name, age <span class="keyword">FROM</span> my_index <span class="keyword">WHERE</span> age <span class="operator">&gt;</span> <span class="number">25</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> name <span class="keyword">AS</span> full_name, age <span class="keyword">FROM</span> my_index <span class="keyword">WHERE</span> age <span class="operator">=</span> <span class="number">25</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> my_index <span class="keyword">WHERE</span> age <span class="keyword">IS</span> <span class="keyword">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> age <span class="keyword">FROM</span> my_index;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">MIN</span>(age), <span class="built_in">MAX</span>(age), <span class="built_in">AVG</span>(age) <span class="keyword">FROM</span> my_index;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> age, <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">AS</span> CNT <span class="keyword">FROM</span> my_index <span class="keyword">GROUP</span> <span class="keyword">BY</span> age;</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/3382e540c1a145d2b138034cc30e43fd.png" alt="SQL查询结果示例"></p><h3 id="使用-Easysearch-执行-SQL-查询"><a href="#使用-Easysearch-执行-SQL-查询" class="headerlink" title="使用 Easysearch 执行 SQL 查询"></a>使用 Easysearch 执行 SQL 查询</h3><p>Easysearch 提供了对直接使用 SQL 查询的支持。以下是如何在 Easysearch 中通过 POST 请求使用 <code>_sql</code> 端点进行查询的示例：</p><p>执行 SQL 查询非常简单，只需通过 POST 请求发送 SQL 查询即可。以下是一个示例：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT name AS full_name, age FROM my_index WHERE age &gt; 25&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>你也可以规定返回 JSON 格式：</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">POST /_sql?format=json</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT name AS full_name, age FROM my_index WHERE age &gt; 25&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>和 Elasticsearch 一样，Easysearch 允许你通过 POST 请求直接在集群上运行 SQL 查询，并返回查询结果。以下是一些常见的 SQL 查询示例及其对应的 POST 请求：</p><ol><li><p><strong>查询所有文档</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM my_index&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>限制返回文档数</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM my_index LIMIT 2&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>按字段排序</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM my_index ORDER BY name&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>筛选条件查询</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT name AS full_name, age FROM my_index WHERE age &gt; 25&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>精确值查询</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT name AS full_name, age FROM my_index WHERE age = 25&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>查询空值</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM my_index WHERE age IS NULL&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>查询唯一值</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT DISTINCT age FROM my_index&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>聚合函数查询</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT MIN(age), MAX(age), AVG(age) FROM my_index&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>分组统计</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT age, COUNT(*) AS CNT FROM my_index GROUP BY age&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol><p><img src="https://i-blog.csdnimg.cn/direct/5998fa17e1a64a818d7fde7ab7e303c0.png" alt="SQL查询效果"></p><h3 id="多表操作的-SQL-语句"><a href="#多表操作的-SQL-语句" class="headerlink" title="多表操作的 SQL 语句"></a>多表操作的 SQL 语句</h3><p>以下是多表操作的 SQL 语句及其解释：</p><ol><li><p><strong>子查询</strong>：</p><figure class="highlight sql"><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> <span class="operator">*</span> <span class="keyword">FROM</span> table1 t1 <span class="keyword">WHERE</span> t1.id <span class="keyword">IN</span> (<span class="keyword">SELECT</span> id <span class="keyword">FROM</span> table2)</span><br></pre></td></tr></table></figure><p>这个查询从 <code>table1</code> 中选择所有字段的记录，其中这些记录的 <code>id</code> 在 <code>table2</code> 表中也存在。</p></li><li><p><strong>内连接</strong>：</p><figure class="highlight sql"><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> <span class="operator">*</span> <span class="keyword">FROM</span> table1 t1 <span class="keyword">JOIN</span> table2 t2 <span class="keyword">ON</span> t1.id <span class="operator">=</span> t2.id</span><br></pre></td></tr></table></figure><p>这个查询进行内连接，从 <code>table1</code> 和 <code>table2</code> 中选择所有字段的记录，前提是 <code>table1</code> 和 <code>table2</code> 中 <code>id</code> 相等。</p></li><li><p><strong>左连接</strong>：</p><figure class="highlight sql"><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> <span class="operator">*</span> <span class="keyword">FROM</span> table1 t1 <span class="keyword">LEFT</span> <span class="keyword">JOIN</span> table2 t2 <span class="keyword">ON</span> t1.id <span class="operator">=</span> t2.id</span><br></pre></td></tr></table></figure><p>这个查询进行左连接，从 <code>table1</code> 和 <code>table2</code> 中选择所有字段的记录，即使 <code>table2</code> 中没有匹配的记录，也会返回 <code>table1</code> 中的所有记录，未匹配到的部分会用 NULL 填充。</p></li><li><p><strong>右连接</strong>：</p><figure class="highlight sql"><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> <span class="operator">*</span> <span class="keyword">FROM</span> table1 t1 <span class="keyword">RIGHT</span> <span class="keyword">JOIN</span> table2 t2 <span class="keyword">ON</span> t1.id <span class="operator">=</span> t2.id</span><br></pre></td></tr></table></figure><p>这个查询进行右连接，从 <code>table1</code> 和 <code>table2</code> 中选择所有字段的记录，即使 <code>table1</code> 中没有匹配的记录，也会返回 <code>table2</code> 中的所有记录，未匹配到的部分会用 NULL 填充。</p></li></ol><p>假设我们有两个索引 <code>table1</code> 和 <code>table2</code>，对应于 SQL 中的两个表。</p><p>**创建索引 <code>table1</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><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">PUT /table1</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integer&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>**创建索引 <code>table2</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><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">PUT /table2</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integer&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</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><p>**导入数据到 <code>table1</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><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">POST /table1/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><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;Alice&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Bob&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Charlie&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;David&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>**导入数据到 <code>table2</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><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">POST /table2/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><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;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Value1&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span> <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Value2&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">5</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span> <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Value5&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">6</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">6</span><span class="punctuation">,</span> <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Value6&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>导入数据后，可以使用 SQL 来执行这些查询：</p><ol><li><p><strong>子查询</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM table1 WHERE id IN (SELECT id FROM table2)&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>内连接</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM table1 t1 JOIN table2 t2 ON t1.id = t2.id&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>左连接</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM table1 t1 LEFT JOIN table2 t2 ON t1.id = t2.id&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>右连接</strong>：</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">POST /_sql</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELECT * FROM table1 t1 RIGHT JOIN table2 t2 ON t1.id = t2.id&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol><p>效果如下：<br><img src="https://i-blog.csdnimg.cn/direct/759a21f230b54165a629f6052092ac8c.png" alt="多表查询效果"></p><h3 id="SQL-全文检索"><a href="#SQL-全文检索" class="headerlink" title="SQL 全文检索"></a>SQL 全文检索</h3><p><code>match</code> 和 <code>match_phrase</code> 是 Easysearch 中用于全文搜索的查询类型，它们在处理文本匹配方面有不同的用途：</p><ol><li><p><strong><code>match</code> 查询</strong>：</p><ul><li><code>match</code> 查询用于对文档进行全文搜索。</li><li>它将搜索关键词进行分词，并对这些分词后的词项进行搜索。</li><li>适用于查询单个或多个字段，可以进行布尔操作（如 “AND”, “OR”）。</li><li>例如，搜索 “Easysearch is powerful” 会被分词为 “Easysearch “, “is”, “powerful” 三个词，然后对这三个词进行搜索，文档中包含这些词的都会被认为是匹配的。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch  is powerful&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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></li><li><p><strong><code>match_phrase</code> 查询</strong>：</p><ul><li><code>match_phrase</code> 查询用于短语搜索。</li><li>它要求搜索的短语必须在文档中出现且词的顺序相同，词之间的间隔也必须与查询中的短语相同。</li><li>适用于需要精确匹配短语的场景。</li><li>例如，搜索 “Easysearch is powerful” 时，只有包含这个确切短语的文档才会被认为是匹配的。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;match_phrase&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Easysearch  is powerful&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</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></li></ol><p>总结来说，<code>match</code> 更灵活，用于一般的关键词搜索，而 <code>match_phrase</code> 则用于需要精确匹配短语的搜索。</p><h3 id="SQL-全文检索示例"><a href="#SQL-全文检索示例" class="headerlink" title="SQL 全文检索示例"></a>SQL 全文检索示例</h3><p>我们先造一些数据，然后使用 SQL 来进行全文检索。</p><p><strong>批量导入数据</strong>：</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">POST /table3/_bulk</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><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;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The quick brown fox jumps over the lazy dog&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">2</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Foxes are wild animals&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">3</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Jump high to catch the ball&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">4</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Some animals can jump very high&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">5</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The lazy dog sleeps all day&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span> <span class="number">6</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">6</span><span class="punctuation">,</span> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The foxes jump all day&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>执行全文检索的 SQL 查询</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table3 <span class="keyword">WHERE</span> <span class="keyword">match</span>(test, <span class="string">&#x27;jump&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table3 <span class="keyword">WHERE</span> match_phrase(test, <span class="string">&#x27;foxes jump&#x27;</span>);</span><br></pre></td></tr></table></figure><p><img src="https://i-blog.csdnimg.cn/direct/ef0384ec12fe454186fa9d51edd976cf.png" alt="SQL全文检索效果"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>随着数据量的不断增加，高效的数据搜索和分析变得尤为重要。Elasticsearch 以其强大的全文搜索能力和灵活的数据处理能力成为行业标准。Easysearch 作为 Elasticsearch 的优化版本，不仅继承了其强大的功能，还在性能和安全性上做了进一步的提升，为企业提供了一个高效、稳定且易于迁移的搜索引擎解决方案。通过深入了解这些技术和实践其应用，开发者和企业能够更好地利用这些工具来应对现代数据挑战，推动业务的持续发展和创新。</p>]]></content>
    
    
    <summary type="html">玩转 Easysearch 查询语法，掌握搜索引擎的核心检索能力</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Easysearch 数据可视化和管理平台：INFINI Console 使用介绍</title>
    <link href="https://blog.no-claw.com/posts/ebd08f3f/"/>
    <id>https://blog.no-claw.com/posts/ebd08f3f/</id>
    <published>2024-07-02T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>上次在《<a href="https://blog.csdn.net/weixin_38781498/article/details/140077785">INFINI Easysearch 尝鲜 Hands on</a>》中，我们部署了两个节点的 Easysearch，并设置了 Console 进行集群监控。今天，我们将介绍 <a href="https://infinilabs.cn/products/console">INFINI Console</a> 的使用。</p><h2 id="Dashboard"><a href="#Dashboard" class="headerlink" title="Dashboard"></a>Dashboard</h2><p>INFINI Console 是一个功能强大的数据管理和分析平台，其仪表盘页面提供了直观简洁的界面，使用户能够快速了解系统状态并进行管理操作。本文将详细介绍仪表盘页面的各项功能。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/69cfa7cc765112f313193d72c876efd0.png" alt="在这里插入图片描述"></p><span id="more"></span><p>仪表盘顶部显示系统的实时告警、通知和待办事项的数量，当前数据显示：</p><ul><li>告警：0 条</li><li>通知：0 条</li><li>待办：0 条</li></ul><p>在仪表盘的中心区域，用户可以看到几项关键的系统概览信息：</p><ul><li><strong>集群数量</strong>：当前有 3 个集群正在运行。</li><li><strong>节点数量</strong>：系统中有 16 个节点。</li><li><strong>主机数量</strong>：共有 3 台主机。</li><li><strong>已用存储</strong>：系统已使用存储空间为 2.0GB。</li></ul><p>仪表盘页面还提供了几个常用操作的快速入口，方便用户迅速访问常用功能：</p><ul><li><strong>集群注册</strong>：用户可以通过此入口快速注册新的集群。</li><li><strong>数据探索</strong>：用户可以访问数据探索工具，对系统中的数据进行分析和查询。</li><li><strong>告警管理</strong>：提供对告警信息的管理功能，用户可以查看和处理告警。</li><li><strong>安全管理</strong>：安全管理入口帮助用户维护系统的安全设置和策略。</li></ul><p>仪表盘右侧显示了集群的动态信息，包括最近的操作日志。例如：</p><ul><li>2024-07-03 22:43:43，index medcl 在 cluster infiniLabs 中的状态更新。</li><li>2024-07-03 22:06:43，index medcl 在 cluster infiniLabs 中被创建。</li></ul><h2 id="集群管理页面"><a href="#集群管理页面" class="headerlink" title="集群管理页面"></a>集群管理页面</h2><p>集群管理页面主要分为几个部分：顶部的功能选项卡、中部的集群列表、以及右侧的筛选和排序选项。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/8d4f68e833e90c99aa583e5b587359a9.png" alt="在这里插入图片描述"></p><p>页面顶部的功能选项卡包括以下几项：</p><ul><li>**Clusters (集群)**：显示当前系统中的所有集群。</li><li>**Nodes (节点)**：显示集群中的节点详细信息。</li><li>**Indices (索引)**：显示集群中的索引信息。</li><li>**Hosts (主机)**：显示系统中的主机信息。</li></ul><p>集群列表展示了每个集群的详细信息，包括：</p><ul><li><strong>集群名称</strong>：每个集群的名称，如 “infinilabs”、”mycluster”、”INFINI_SYSTEM (JeanGrey)”。</li><li><strong>集群健康状态</strong>：以颜色条的形式显示最近 14 天的集群健康状态（绿色表示健康，黄色表示有警告）。</li><li><strong>节点数量</strong>：集群中包含的节点数量。</li><li><strong>索引数量</strong>：集群中的索引数量。</li><li><strong>分片数量</strong>：集群中的分片数量。</li><li><strong>文档数量</strong>：集群中存储的文档数量。</li><li><strong>磁盘使用率</strong>：集群的磁盘使用情况。</li><li><strong>JVM 堆内存使用率</strong>：集群的 JVM 堆内存使用情况。</li><li><strong>索引速率</strong>：当前集群的索引速率（每秒索引数）。</li><li><strong>搜索速率</strong>：当前集群的搜索速率（每秒搜索数）。</li></ul><p>页面右侧提供了丰富的筛选和排序选项，可以根据以下条件筛选和排序集群：</p><ul><li>**健康状态 (Health Status)**：根据集群的健康状态筛选，如绿色（健康）和黄色（警告）。</li><li>**分布 (Distribution)**：根据集群的分布类型筛选，如 “easysearch” 和 “elasticsearch”。</li><li>**版本 (Version)**：根据集群使用的软件版本筛选，如 Easysearch 1.8.2 和 Elasticsearch 7.10.2。</li><li>**区域 (Region)**：根据集群所在的区域筛选，如 “china” 和 “default”。</li><li>**标签 (Tags)**：根据自定义标签进行筛选。</li></ul><p>接下来分别介绍节点、索引和主机层面的信息，这些监控指标与集群层面大同小异。</p><p><strong>节点监控</strong></p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/ecd388e5e2083eeef7c4ea6b7bd21e2d.png" alt="在这里插入图片描述"></p><p><strong>索引监控</strong><br><img src="https://i-blog.csdnimg.cn/blog_migrate/c12be3370ae00fa3cc46c891e0ee64d8.png" alt="在这里插入图片描述"></p><p><strong>主机监控</strong><br>包括了常规的 CPU、内存、磁盘、网络的监控。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/74b56ae141bbf5d0f8b33028640e4e88.png" alt="在这里插入图片描述"></p><h2 id="监控指标页面"><a href="#监控指标页面" class="headerlink" title="监控指标页面"></a>监控指标页面</h2><p>监控报表页面提供了对集群运行状况的详细监控和分析功能。用户可以选择最近 15 分钟、1 小时、24 小时等不同时间范围查看数据，并手动点击刷新按钮更新数据，以获取最新的监控信息。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/0a3bfc62bc6a8c9b0818b4733c839427.png" alt="在这里插入图片描述"></p><h3 id="概览信息"><a href="#概览信息" class="headerlink" title="概览信息"></a>概览信息</h3><p>显示当前集群的基本状态，包括：</p><ul><li><strong>集群名称</strong>：如 “infinilabs”。</li><li><strong>在线时长</strong>：如 “3 天”。</li><li><strong>集群版本</strong>：如 “1.8.2”。</li><li><strong>健康状态</strong>：如 “green”。</li><li><strong>节点数</strong>：如 “2”。</li><li><strong>索引数</strong>：如 “38”。</li><li><strong>主&#x2F;总分片</strong>：如 “38&#x2F;76”。</li><li><strong>未分配分片</strong>：如 “0”。</li><li><strong>文档数</strong>：如 “656,803”。</li><li><strong>存储空间</strong>：如 “1007.2MB&#x2F;385.4GB”。</li><li><strong>JVM 内存</strong>：如 “1023.0MB&#x2F;2.0GB”。</li></ul><p>监控报表页面还提供了多个性能指标的图表，包括：</p><h4 id="索引吞吐-doc-s"><a href="#索引吞吐-doc-s" class="headerlink" title="索引吞吐 (doc&#x2F;s)"></a>索引吞吐 (doc&#x2F;s)</h4><ul><li><strong>Total Indexing</strong>：总索引吞吐量。</li><li><strong>Primary Indexing</strong>：主分片的索引吞吐量。</li></ul><h4 id="查询吞吐-query-s"><a href="#查询吞吐-query-s" class="headerlink" title="查询吞吐 (query&#x2F;s)"></a>查询吞吐 (query&#x2F;s)</h4><ul><li><strong>Total Query</strong>：总查询吞吐量。</li></ul><h4 id="索引延迟-ms"><a href="#索引延迟-ms" class="headerlink" title="索引延迟 (ms)"></a>索引延迟 (ms)</h4><ul><li><strong>Indexing Latency</strong>：索引延迟时间。</li><li><strong>Delete Latency</strong>：删除操作的延迟时间。</li></ul><h4 id="查询延迟-ms"><a href="#查询延迟-ms" class="headerlink" title="查询延迟 (ms)"></a>查询延迟 (ms)</h4><ul><li><strong>Query Latency</strong>：查询延迟时间。</li><li><strong>Fetch Latency</strong>：获取操作的延迟时间。</li><li><strong>Scroll Latency</strong>：滚动操作的延迟时间。</li></ul><p>点击“Advance”可以查看更多监控指标：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6ad64acc24959c6db740ff5578501944.png" alt="在这里插入图片描述"></p><h3 id="节点级别性能监控"><a href="#节点级别性能监控" class="headerlink" title="节点级别性能监控"></a>节点级别性能监控</h3><p>包括 CPU、负载、JVM 内存、剩余使用空间及磁盘空间、集群启动时间和索引读写情况。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/12b2d9eafe3661d0ccdfc3e29aa2c313.png" alt="在这里插入图片描述"></p><h3 id="索引级别监控"><a href="#索引级别监控" class="headerlink" title="索引级别监控"></a>索引级别监控</h3><p>包括集群内索引的数量、状态、主分片和副本分片数量、文档条数和占用空间。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/2a9608edbed3a9550a7a0c7690ea15ef.png" alt="在这里插入图片描述"></p><h3 id="集群动态页面"><a href="#集群动态页面" class="headerlink" title="集群动态页面"></a>集群动态页面</h3><p>提供集群中各类事件和活动的详细记录和监控功能。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/c59f44da6e9de578cd127d5df5499825.png" alt="在这里插入图片描述"></p><h2 id="别名管理"><a href="#别名管理" class="headerlink" title="别名管理"></a>别名管理</h2><p>别名管理页面提供了对索引别名的管理功能，使用户可以方便地管理和配置 Elasticsearch&#x2F;EasySearch 的索引别名。</p><h3 id="创建别名"><a href="#创建别名" class="headerlink" title="创建别名"></a>创建别名</h3><p>可以通过 DSL 创建别名。例如，创建一个名为 <code>my_index_alias</code> 的别名指向 &#96;my_index</p><p>&#96;：</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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;actions&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;add&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index_alias&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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><h3 id="删除别名"><a href="#删除别名" class="headerlink" title="删除别名"></a>删除别名</h3><p>删除一个别名同样可以通过 REST API 实现：</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></pre></td><td class="code"><pre><span class="line">POST /_aliases</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;actions&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;remove&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;alias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my_index_alias&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</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><h3 id="索引轮换"><a href="#索引轮换" class="headerlink" title="索引轮换"></a>索引轮换</h3><p>索引轮换是一种常用的索引管理策略，特别适用于日志和时间序列数据的场景。通过索引轮换，用户可以在索引达到一定条件（如大小或文档数量）时，创建一个新的索引来继续存储数据，而旧的索引可以继续用于查询。</p><ol><li><strong>设置写别名</strong>：创建一个指向当前写入索引的别名，例如 <code>current_write_index</code>。</li><li><strong>定义索引轮换条件</strong>：可以基于索引的大小、文档数量或时间来定义轮换条件。</li><li><strong>执行轮换操作</strong>：当索引满足轮换条件时，创建一个新的索引并更新写别名指向这个新索引。</li></ol><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></pre></td><td class="code"><pre><span class="line">PUT /my_index<span class="number">-000001</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;aliases&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;current_write_index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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><p>使用 <code>/_rollover</code> API 定义轮换条件并执行轮换：</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">POST /current_write_index/_rollover</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;conditions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;max_age&quot;</span><span class="punctuation">:</span> <span class="string">&quot;7d&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;max_docs&quot;</span><span class="punctuation">:</span> <span class="number">1000000</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;number_of_shards&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;aliases&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;current_write_index&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</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><p>通过这种方式，查询操作可以透明地访问所有历史数据，而写操作总是指向最新的索引。</p><p>在 INFINI Console 中提供了可视化创建索引及别名的方式。页面右上角提供了新建按钮，用户可以通过点击该按钮创建新的索引别名，填写别名名称、关联索引、索引路由、搜索路由和过滤查询等配置。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/81873cb012d1b1895a4094b500255990.png" alt="在这里插入图片描述"></p><h2 id="平台监控"><a href="#平台监控" class="headerlink" title="平台监控"></a>平台监控</h2><p>展示了多个关键指标的监控图表，包括：</p><ul><li>**健康状态 (Health)**：显示系统当前的健康状态。如果没有数据，则显示“暂无数据”。</li><li>**引擎分布 (Engines)**：展示系统中不同搜索引擎的分布情况，例如 EasySearch 和 Elasticsearch 的比例。图表显示当前 EasySearch 占 67%，Elasticsearch 占 33%。</li><li>**提供商 (Providers)**：显示系统中使用的云服务提供商信息。在示例中，所有资源都托管在 AWS 上。</li><li>**JDK 版本 (JDK)**：显示系统中使用的 JDK 版本信息。在示例中，所有节点都使用 JDK 版本 11.0.20。</li><li><strong>磁盘使用情况 (Disk Utilization) - Top 10</strong>：显示磁盘使用率最高的前 10 个节点。在示例中，easysearch-node1 和 easysearch-node2 的磁盘使用率均为 4%。</li><li><strong>JVM 使用情况 (JVM Utilization) - Top 10</strong>：展示 JVM 使用率最高的前 10 个节点。在示例中，infinilabs 集群的 easysearch-node1 和 easysearch-node2 节点的 JVM 使用情况有详细的时间序列数据，显示了不同时间点的使用率变化。</li></ul><p><img src="https://i-blog.csdnimg.cn/blog_migrate/b5c424dd48ea0b5f9a2c30903cc06685.png" alt="在这里插入图片描述"></p><p>我们还能够看到更多指标：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/35e44900d68426dcfa0cfea72cf908af.png" alt="在这里插入图片描述"></p><h3 id="数据探索"><a href="#数据探索" class="headerlink" title="数据探索"></a>数据探索</h3><p>在数据探索里，可以根据时间、字段等条件对索引或者视图下的数据进行搜索查询和分析，类似 Kibana 的 Discover。<img src="https://i-blog.csdnimg.cn/blog_migrate/b9cd9cfb536cad4d4ac5431621cdf3ef.png" alt="在这里插入图片描述"><br>这里可以看到集群的警报，目前集群运行良好，没有任何警报。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/76c30ac5cc8f50f7e29486a4b6f86c88.png" alt="在这里插入图片描述"><br>内部会预设一些警报规则，如下：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/e5727ff282cfe6b64a6867da541f4c88.png" alt="在这里插入图片描述"><br>点进去一个请求，比如磁盘的警告，可以针对不同的使用量设置不同的警告级别和通知。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/ccd8c6e9e41be3272e32d14d87a1571d.png" alt="在这里插入图片描述"><br>这里针对警报设置警报，可以看到现在支持很多平台，Discord、飞书、邮件、微信、Slack 以及钉钉。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6e8ae2443ad49345a0a8f8cd54cd1422.png" alt="在这里插入图片描述"><br>点击进去可以查看，对于社交软件而言，其实是使用 Webhook 进行通知，除此之外也支持配置邮件服务器和自定义的 Webhook 进行通知。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/2fd848757499fcd44641dfde735da363.png" alt="在这里插入图片描述"></p><h3 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h3><p>Console 的开发工具相当于 Kibana DevTool 的升级版，使用上基本没有大的区别，除了支持 DSL 之外，还支持多集群 Tab 切换、常用命令快速 Load、SQL 查询等。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/e85c555b6053626b7350d12ee1bb478f.png" alt="在这里插入图片描述"></p><h3 id="集群连接凭证管理"><a href="#集群连接凭证管理" class="headerlink" title="集群连接凭证管理"></a>集群连接凭证管理</h3><p>可以看到连接这三个集群的凭证管理，目前都是有效的。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/a596503b2d17df6571a58096883edd40.png" alt="在这里插入图片描述"></p><h3 id="后台用户授权"><a href="#后台用户授权" class="headerlink" title="后台用户授权"></a>后台用户授权</h3><p>可以添加用户以及修改 console 管理界面的密码。目前设置了 admin 账号。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/c3f605d3cfae751de9d3433e4321b4f5.png" alt="在这里插入图片描述"></p><h3 id="审计日志"><a href="#审计日志" class="headerlink" title="审计日志"></a>审计日志</h3><p>追踪对集群的操作，捕获查看集群监控信息以及集群索引的操作。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/b3cc68031c2e2ab8d158e57ec20c6dfb.png" alt="在这里插入图片描述"></p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>INFINI Console 的仪表盘页面集成了系统的关键信息和快捷操作入口，使用户可以高效地管理和监控系统。通过详细的概览信息、实时的告警通知、快速的功能入口和动态日志，用户能够对系统的运行状态一目了然，并快速响应各种管理需求。这个设计不仅提升了用户的工作效率，还确保了系统的安全和稳定运行。</p><p>INFINI Console 的集群管理页面提供了对系统集群的全面监控和管理功能。通过详细的集群信息展示、便捷的功能选项卡切换以及丰富的筛选和排序功能，用户可以高效地管理和监控系统中的集群状态。这不仅提升了运维效率，还确保了系统的稳定运行和高效管理。</p><p>INFINI Console 的节点管理页面提供了对集群节点的全面监控和管理功能。通过详细的节点信息展示、便捷的功能选项卡切换以及丰富的筛选和搜索功能，用户可以高效地管理和监控系统中的节点状态，从而提升运维效率，确保系统的稳定运行和高效管理。</p><p>INFINI Console 的监控报表页面提供了对集群运行状况的全面监控和分析功能。通过详细的概览信息和多个性能指标图表，用户可以高效地监控和管理集群的运行状态。这不仅提升了系统运维效率，还确保了集群的稳定运行和高效管理。</p><p>通过这些功能，INFINI Console 为用户提供了全面的系统管理工具，帮助他们高效地应对各种运维挑战，确保系统的高效、安全、稳定运行。</p>]]></content>
    
    
    <summary type="html">介绍 INFINI Console 作为 Easysearch 数据可视化和集群管理平台的使用方法</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>Linux常用命令</title>
    <link href="https://blog.no-claw.com/posts/d0edc1ed/"/>
    <id>https://blog.no-claw.com/posts/d0edc1ed/</id>
    <published>2024-07-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>设置别名</p><figure class="highlight zsh"><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="built_in">echo</span> <span class="string">&quot;alias aa=&#x27;your-cmd&#x27;&quot;</span> &gt;&gt; ~/.zshrc</span><br><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Linux 常用命令速查，涵盖别名设置、文件操作等日常运维实用命令。</summary>
    
    
    
    <category term="软件" scheme="https://blog.no-claw.com/categories/%E8%BD%AF%E4%BB%B6/"/>
    
    
    <category term="Linux" scheme="https://blog.no-claw.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>INFINI Easysearch尝鲜Hands on</title>
    <link href="https://blog.no-claw.com/posts/8c820eb6/"/>
    <id>https://blog.no-claw.com/posts/8c820eb6/</id>
    <published>2024-06-29T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>INFINI Easysearch 是一个分布式的近实时搜索与分析引擎，核心引擎基于开源的 Apache Lucene。Easysearch 的目标是提供一个自主可控的轻量级的 Elasticsearch 可替代版本，并继续完善和支持更多的企业级功能。 与 Elasticsearch 相比，Easysearch 更关注在搜索业务场景的优化和继续保持其产品的简洁与易用性。</p><p>Easysearch 支持原生 Elasticsearch 的 DSL 查询语法，确保原业务代码无需调整即可无缝迁移。同时，极限科技还支持 SQL 查询，为熟悉 SQL 的开发人员提供更加便捷的数据分析方式。此外，Easysearch 兼容 Elasticsearch 的 SDK 和现有索引存储格式，支持冷热架构和索引生命周期管理，确保用户能够轻松实现数据的无缝衔接。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><h3 id="安装脚本"><a href="#安装脚本" class="headerlink" title="安装脚本"></a>安装脚本</h3><p>无论是 Linux 还是 mac 都是这个一键脚本</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 -sSL http://get.infini.cloud | bash -s -- -p easysearch</span><br></pre></td></tr></table></figure><p>同时也提供了二进制的安装包：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/0b16aaa6dd56bd94e28586f31a86294d.png" alt="在这里插入图片描述"></p><span id="more"></span><p>如果不想整理 JAVA 环境问题，还可以使用这个<br><a href="https://release.infinilabs.com/easysearch/stable/bundle/">https://release.infinilabs.com/easysearch/stable/bundle/</a></p><h3 id="docker-部署"><a href="#docker-部署" class="headerlink" title="docker 部署"></a>docker 部署</h3><p>官方提供了 Docker Compose 样例，包括三个服务：<code>easysearch-node1</code>、<code>easysearch-node2</code> 和 <code>console</code>。以下是详细说明：</p><ol><li><p><strong>版本控制</strong>：</p><ul><li><code>version: &#39;3&#39;</code> 表示使用 Docker Compose 文件的第 3 版格式。</li></ul></li><li><p><strong>服务定义</strong>：</p><ul><li><p><strong>easysearch-node1 和 easysearch-node2</strong>：</p><ul><li>这两个服务使用相同的 Docker 镜像 <code>infinilabs/easysearch:latest</code>来组成双节点的集群。</li><li>容器运行时使用用户和组 ID <code>602:602</code>。</li><li>设置了 <code>ES_JAVA_OPTS</code> 环境变量以配置 Java 虚拟机的内存。</li><li><code>ulimits</code> 选项配置了内存锁定和文件描述符的限制，以提升性能。</li><li>容器内的配置、数据和日志目录通过卷映射到主机目录中，以便于数据持久化。</li><li>服务暴露特定端口，使外部能够访问容器中的服务。</li><li>两个节点均加入名为 <code>esnet</code> 的自定义网络中。</li></ul></li><li><p><strong>console</strong>：</p><ul><li>该服务使用镜像 <code>infinilabs/console:1.26.0-1552</code>（该镜像没有 latest，需要手动把 latest 更改位特定的版本号）。</li><li>同样通过卷将数据和日志目录映射到主机。</li><li>暴露 9000 端口用于 Web 界面访问。</li><li>使用 <code>links</code> 功能链接到 <code>easysearch-node1</code> 和 <code>easysearch-node2</code>，简化容器之间的通信。</li><li>设置了时区环境变量 <code>TZ</code> 为 <code>Asia/Shanghai</code>。</li></ul></li></ul></li><li><p><strong>网络配置</strong>：</p><ul><li><code>esnet</code> 网络使用 <code>bridge</code> 驱动，提供一个隔离的网络环境，配置了特定的子网 <code>172.24.0.0/16</code>，以确保服务之间的网络通信。</li></ul></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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></pre></td><td class="code"><pre><span class="line">version: &#x27;3&#x27;</span><br><span class="line">services:</span><br><span class="line">  easysearch-node1:</span><br><span class="line">    user: &quot;602:602&quot;</span><br><span class="line">    image: infinilabs/easysearch:latest</span><br><span class="line">    container_name: easysearch-node1</span><br><span class="line">    hostname: easysearch-node1</span><br><span class="line">    environment:</span><br><span class="line">      - &quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span><br><span class="line">    ulimits:</span><br><span class="line">      memlock:</span><br><span class="line">        soft: -1</span><br><span class="line">        hard: -1</span><br><span class="line">      nofile:</span><br><span class="line">        soft: 65536</span><br><span class="line">        hard: 65536</span><br><span class="line">    volumes:</span><br><span class="line">      - $PWD/ezs1/config:/app/easysearch/config</span><br><span class="line">      - $PWD/ezs1/data:/app/easysearch/data</span><br><span class="line">      - $PWD/ezs1/logs:/app/easysearch/logs</span><br><span class="line">    ports:</span><br><span class="line">      - 9201:9200</span><br><span class="line">      - 9301:9300</span><br><span class="line">    networks:</span><br><span class="line">      - esnet</span><br><span class="line">  easysearch-node2:</span><br><span class="line">    user: &quot;602:602&quot;</span><br><span class="line">    image: infinilabs/easysearch:latest</span><br><span class="line">    container_name: easysearch-node2</span><br><span class="line">    hostname: easysearch-node2</span><br><span class="line">    environment:</span><br><span class="line">      - &quot;ES_JAVA_OPTS=-Xms1g -Xmx1g&quot;</span><br><span class="line">    ulimits:</span><br><span class="line">      memlock:</span><br><span class="line">        soft: -1</span><br><span class="line">        hard: -1</span><br><span class="line">      nofile:</span><br><span class="line">        soft: 65536</span><br><span class="line">        hard: 65536</span><br><span class="line">    volumes:</span><br><span class="line">      - $PWD/ezs2/config:/app/easysearch/config</span><br><span class="line">      - $PWD/ezs2/data:/app/easysearch/data</span><br><span class="line">      - $PWD/ezs2/logs:/app/easysearch/logs</span><br><span class="line">    ports:</span><br><span class="line">      - 9202:9200</span><br><span class="line">      - 9302:9300</span><br><span class="line">    networks:</span><br><span class="line">      - esnet</span><br><span class="line">  console:</span><br><span class="line">    image: infinilabs/console:1.26.0-1552</span><br><span class="line">    container_name: console</span><br><span class="line">    hostname: console</span><br><span class="line">    volumes:</span><br><span class="line">      - $PWD/console/data:/data</span><br><span class="line">      - $PWD/console/log:/log</span><br><span class="line">    networks:</span><br><span class="line">      - esnet</span><br><span class="line">    ports:</span><br><span class="line">      - 9000:9000</span><br><span class="line">    links:</span><br><span class="line">      - easysearch-node1:es1</span><br><span class="line">      - easysearch-node2:es2</span><br><span class="line">    environment:</span><br><span class="line">      - TZ=Asia/Shanghai</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  esnet:</span><br><span class="line">    driver: bridge</span><br><span class="line">    ipam:</span><br><span class="line">      config:</span><br><span class="line">        - subnet: 172.24.0.0/16</span><br></pre></td></tr></table></figure><p>尽管在这里官方提供了详细的命令，完全可以使用这个 docker-compose up 来进行替代。其他的脚本解释如下：</p><p><strong>init.sh</strong></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"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前脚本所在目录的绝对路径</span></span><br><span class="line">CUR_DIR=$(<span class="built_in">cd</span> $(<span class="built_in">dirname</span> <span class="variable">$0</span>); <span class="built_in">pwd</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建必要的目录结构</span></span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$CUR_DIR</span>/console/&#123;data,<span class="built_in">log</span>&#125;</span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$CUR_DIR</span>/&#123;ezs1,ezs2&#125;/&#123;data,logs&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置目录的拥有者和权限</span></span><br><span class="line"><span class="built_in">chown</span> -R 1000:1000 <span class="variable">$CUR_DIR</span>/console</span><br><span class="line"><span class="built_in">chown</span> -R 602:602 <span class="variable">$CUR_DIR</span>/&#123;ezs1,ezs2&#125;</span><br><span class="line"><span class="built_in">chmod</span> -R 0600 <span class="variable">$CUR_DIR</span>/&#123;ezs1,ezs2&#125;/config</span><br><span class="line"><span class="comment"># 设置 config 目录的子目录权限</span></span><br><span class="line">find <span class="variable">$CUR_DIR</span>/&#123;ezs1,ezs2&#125;/config -<span class="built_in">type</span> d -print0 | xargs -0 <span class="built_in">chmod</span> 750</span><br></pre></td></tr></table></figure><p><strong>reset.sh</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前脚本所在目录的绝对路径</span></span><br><span class="line">CUR_DIR=$(<span class="built_in">cd</span> $(<span class="built_in">dirname</span> <span class="variable">$0</span>); <span class="built_in">pwd</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义确认函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="function"><span class="title">confirm</span></span>() &#123;</span><br><span class="line">  display_str=<span class="variable">$1</span></span><br><span class="line">  default_ans=<span class="variable">$2</span></span><br><span class="line">  <span class="keyword">if</span> [[ <span class="variable">$default_ans</span> == <span class="string">&#x27;y/N&#x27;</span> ]]; <span class="keyword">then</span></span><br><span class="line">     must_match=<span class="string">&#x27;[yY]&#x27;</span></span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">     must_match=<span class="string">&#x27;[nN]&#x27;</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  <span class="built_in">read</span> -p<span class="string">&quot;<span class="variable">$&#123;display_str&#125;</span> [<span class="variable">$&#123;default_ans&#125;</span>]:&quot;</span> ans</span><br><span class="line">  [[ <span class="variable">$ans</span> == <span class="variable">$must_match</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">confirm <span class="string">&quot;RISK WARN: Delete all data!!!&quot;</span> <span class="string">&#x27;y/N&#x27;</span> &amp;&amp; <span class="built_in">echo</span> || <span class="built_in">exit</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除 console、ezs1 和 ezs2 的数据和日志文件</span></span><br><span class="line"><span class="built_in">rm</span> -rvf <span class="variable">$CUR_DIR</span>/console/&#123;data,<span class="built_in">log</span>&#125;/*</span><br><span class="line"><span class="built_in">rm</span> -rvf <span class="variable">$CUR_DIR</span>/&#123;ezs1,ezs2&#125;/&#123;data,logs&#125;/*</span><br></pre></td></tr></table></figure><p><strong>start.sh</strong></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="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Docker Compose 启动 ezs2 项目中的服务</span></span><br><span class="line">docker-compose -p ezs2 up</span><br></pre></td></tr></table></figure><p><strong>stop.sh</strong></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="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Docker Compose 关闭并移除 ezs2 项目中的所有服务</span></span><br><span class="line">docker-compose -p ezs2 down</span><br></pre></td></tr></table></figure><p>在我的电脑中，可以看到成功启动的容器。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/9b5046920244e5879ad4ff52ad0707d7.png" alt="在这里插入图片描述"></p><p><a href="https://infinilabs.cn/docs/latest/easysearch/getting-started/install/docker-compose/">https://infinilabs.cn/docs/latest/easysearch/getting-started/install/docker-compose/</a></p><h2 id="Console-连接"><a href="#Console-连接" class="headerlink" title="Console 连接"></a>Console 连接</h2><p>设置集群连接参数，比如域名端口，用户名密码。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6a34ae0bc70e42d0041827b5dfc9c593.png" alt="在这里插入图片描述"></p><p>初始化，这里会新建索引，写一些 sample 数据。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/8318f08a25fc3fd39083422ea3c6cbca.png" alt="在这里插入图片描述"></p><p>设置后台管理的密码，后期使用这个登录控制台。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/39c174a20875fb8b2a1cbae365eb2e72.png" alt="在这里插入图片描述"></p><p>检查配置，完成集群关联。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/97406dd352dd917ac809c620da4a1907.png" alt="在这里插入图片描述"><br>这个是后台管理界面，除了用户名密码之外，也支持单点登录：<br><img src="https://i-blog.csdnimg.cn/blog_migrate/5b62694c6618170048f9d8d1f9908364.png" alt="在这里插入图片描述"></p><p>跨引擎、跨版本、跨集群 独一份！<img src="https://i-blog.csdnimg.cn/blog_migrate/a4bb67ee25bc5a4cd127bbf6c8ffef30.png" alt="在这里插入图片描述"></p><p>使用自带的面板进行查看节点数量：</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/32e1dbf2dc2b3e2c9f88a0309bb28c57.png" alt="在这里插入图片描述"></p><p>同时也支持 REST 风格的 API 来进行查询。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/27716e131e454d17eb205175fef697fd.png" alt="在这里插入图片描述"></p><p>接下来来使用 Console 连接 Amazon 的 OpenSearch：</p><p>同样是输入集群的 URL，用户名和密码。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/f8f42ef1f3cbe09efce57b5c35d83562.png" alt="在这里插入图片描述"></p><p>然后可以拿到集群的信息，比如地址，版本号，集群状态，节点数量。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/6fe395e5b1178f2c37deefc2d9e61243.png" alt="在这里插入图片描述"></p><p>最后看到连接成功的信息。<br><img src="https://i-blog.csdnimg.cn/blog_migrate/d232c87a3b99e0297709e4af8efe5ba6.png" alt="在这里插入图片描述"></p><p>我们可以在集群管理中看到 EasySearch 的集群和我们刚刚添加的 OpenSearch 集群。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/7ddfec09a795cc7aa77f9b139d53e9ba.png" alt="在这里插入图片描述"></p><p>是否开源？目前还没有开放源代码。</p>]]></content>
    
    
    <summary type="html">INFINI Easysearch 初体验，快速上手搜索引擎部署与基本操作</summary>
    
    
    
    <category term="极限科技" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
    <category term="Easysearch" scheme="https://blog.no-claw.com/categories/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/Easysearch/"/>
    
    
    <category term="搜索引擎（ES）" scheme="https://blog.no-claw.com/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%EF%BC%88ES%EF%BC%89/"/>
    
    <category term="极限科技" scheme="https://blog.no-claw.com/tags/%E6%9E%81%E9%99%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
  <entry>
    <title>外滩夜游</title>
    <link href="https://blog.no-claw.com/posts/b4d03554/"/>
    <id>https://blog.no-claw.com/posts/b4d03554/</id>
    <published>2024-05-27T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>黄浦江真的很出片，无论是白天在世博中心，夜晚的外滩，都会给你一种。身处大都市的感觉，下午的黄浦江风和日丽，偶有渡轮从江面驶过，夜晚的外滩，人流拥挤，放松的人群和和东方明珠隔江相望。</p><p>同事说，没有到过外滩就等于没有到过上海，所以我特地在外滩周围订了酒店，然后在黄埔的大都市，我尽情的游玩。计划里有外滩，豫园，城隍庙，还有人民广场，大世界，然后再这一圈 xxx。</p><p>就能让我骑着小黄车悠哉悠哉的去看夜晚慢慢变黑，虽然地铁也很近，但是不喜欢那种，但是喜欢那种夜幕悄悄降临的感觉，嗯，对于上班，对上班族最。最残忍的事就是早上踏着踏着日光，或者日光没出来的时候就要出发，那晚上迎接你的就是一片黑暗，你偶尔有。街上的霓虹灯，但就不足以照亮街边的路。</p><span id="more"></span><p>要骑车，这次实实在在栽了一个跟头，在望京的时候，我经常行行在一条笔直的马路上放开了双手去骑行，靠着自然的平衡去穿过一条又一条的街道。为什么说上海栽了一个跟头呢？这是一个实实在在的跟头。平地摔了，嗯，人行斑马线的油漆太重，而且但是在单手去查的导航，所以导致。直接重心不稳，整个人飞，整个人飞了出去，不敢想象当时后面的人是怎么样一个想法。</p><p>一般来讲，这种事情都会有一个预兆，比如说后面的事情不顺，但不幸中的万幸就是也就只有膝盖擦破了皮。在行驶到外滩的中途，刚好有一家药店，我可以去买一些消毒器械，简单简单处理过之后，我就到外滩来散步。</p><p>当然这两天总会遇到一些管制，比如你世博附近演唱会散场，人流拥挤导致这一带几乎封路，对于地铁站安安检也是无能为力，直接直接放弃，崩溃，直接崩溃，放弃我今天。外滩的周边是不是不允许驶入共享单车的，驶入或者是停放共享单车的，嗯。</p><p>这就和你知道长安街相似，长安街禁止在在进故宫一带，长安街只有自行车才能使用，包括带助力的。不带，不带助力的自行车才能驶入，包括电动车都是禁止进入的，所以。在去年的深秋，我才能从百子湾一路骑到石景山，这是我觉得长安街带给我的意义。那么对于外滩呢，嗯，简单的来讲就像是一条街。你沿着黄浦江上的一条街，对面是和陆家嘴这种都市中心不太相望，嗯，在这里我们就可以看到这座城市的地标。</p><p>我不知道对于在城市的地位，这两者是否等同，但是可以肯定的是，这里人潮拥挤，确实是一个不得不来的地方。</p><p>从上北京到上海，几乎没有什么不习惯的地方，严格上来说是在北京这些方面都被磨合的很好，但不同的地方就是。这里的外国人会多一些，随处可见老外在拍照，在等，等着等着这边的红绿灯。对不对也可以。</p><p>外滩当然也不例外，嗯，有来有的外国人也有，也有一对，也有有一些情侣。嗯，可能对于外滩相当于迪士尼来说，确实不是一个适合分手的圣地。</p><p>江上的渡轮来来回回的回顾着，似乎一些是为了品牌，另一方面就是为了让为了游客去拍出更加质量，更加有质量的照片。你自拍的时候还是也试着一个人旅游去拍照，但往往总不尽如人意，比如距离感。我没有去拍照，不少，有个有个孩子要，要我帮他去拍照，然后，然后。他还很很乖巧的帮我，帮我先拍了几张。</p><p>大概是出于长期被骗的一个新人的我下意识下意识去说，或者是会不会把我的手机带走这样的，但看着对方。看着像一个没有步入社会的大学生，而且是为了演唱会来到这里，后来也就加了微信，他给我传，还顺便赠送了我刚刚出片的照片。刚刚出炉的风景照，这得再次吐槽一下，苹果的像素太难了，安卓一张 16 兆，你苹果呢？</p><p>嗯，大概是旅游高峰，而且又是日暮，大家都会堆在入口的地方，但。等回来的时候，可能由于这一片，嗯，一个小时就逛完了，然后等等出来的时候就可以。这人是少了很多，这里看高手大山。</p><p>自从离了渤海江畔，到大城市打拼，还真的没有，今年还真没有吹过海，无论是海风还是江风，几乎都没有吹过这样。明天不会感冒，是吧？</p><p>转了一圈，又从外滩出来啊，一波又一波的人流，我一个个企图找到一个吃饭的地方，夜晚八点。嗯</p><p>来的时候开着导航，在小小巷子里左拐右拐，回去的时候逆着人流走一条最长的街道，然后。在这夜里的八点，你在上海最正宗的小龙虾。</p><p>意外的是连接就是和平饭店，嗯，前面有一堆的小吃，今晚只是过来逛逛和平饭店，改天去吃。</p>]]></content>
    
    
    <summary type="html">骑车夜游上海外滩，摔了一跤、看了夜景，记录黄浦江边的都市漫游。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="游记" scheme="https://blog.no-claw.com/tags/%E6%B8%B8%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>便携显示器</title>
    <link href="https://blog.no-claw.com/posts/3badab23/"/>
    <id>https://blog.no-claw.com/posts/3badab23/</id>
    <published>2024-05-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>宣传是这样的：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202405131934445.png"></p><p>到手是这样的</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5264.JPG"></p><p>画质确实很清晰</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202405132010065.png"></p>]]></content>
    
    
    <summary type="html">便携显示器的使用体验与实际效果测评</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
  </entry>
  
  <entry>
    <title>你的下一个电脑还得是电脑</title>
    <link href="https://blog.no-claw.com/posts/c40f7be/"/>
    <id>https://blog.no-claw.com/posts/c40f7be/</id>
    <published>2024-05-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>M4 芯片先给了 Ipad，这是没想到的，最近 Apple 的操作是越来越看不懂了。到同事打赌一年不买苹果产品的期限还没到（M2 Pro 买了半年就发 M3 Pro 被刺），关于 8G 起步内存的事也没怎么关注，倒是趁着这次发布会想起来被吃灰的 Ipad。</p><p>“买前生产力，买后爱奇艺”。吃灰了好久还是决定利用起来。现在的定位感觉还是视频剪辑，甚至还不能写代码，所以一些重量级别的事情只好远程连接 windows。</p><span id="more"></span><p>这个是外接显示器的效果，距离 M1 可以外接显示已经好几年了，但是 ipad 锁屏之后外接显示器也会黑屏的这个问题还是没有修复，最后只能把 ipad 的亮度调到最低。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5344%202.JPG"></p><p>这个是 RDP 连接 Windows 的效果，除了 RDP logo 之外也看不出来其他的东西，然后就可以使用妙控键盘来控制了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5343%202.JPG"></p><p>由于合上盖子会导致自动锁屏，所以需要在设置里关闭自动锁定。（本来为了省电设计的功能，到这里就变成了负优化）</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_E89E17A0B56F-1.jpeg"></p><p>这个是合上盖子的效果。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5345%202.JPG"></p><p>合上盖子 RDP 的效果：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_5343%202.JPG"></p><p>已经只用 MacOS 当主力系统已经很多年了，就让 Windows 活在虚拟机里吧。</p>]]></content>
    
    
    <summary type="html">iPad 能否替代电脑？实际体验告诉你，下一台电脑还得是电脑。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>《每个人的OKR》书摘</title>
    <link href="https://blog.no-claw.com/posts/54f71ff9/"/>
    <id>https://blog.no-claw.com/posts/54f71ff9/</id>
    <published>2024-05-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p> 物质激励会减弱内在动机，降低绩效；鼓励不道德行为，减少创造力；助长短视思维。</p></blockquote><p>[toc]</p><h2 id="时间管理的一些术语"><a href="#时间管理的一些术语" class="headerlink" title="时间管理的一些术语"></a>时间管理的一些术语</h2><h3 id="OKR"><a href="#OKR" class="headerlink" title="OKR"></a>OKR</h3><p>O：objectives 目标</p><p>KR：key result 关键结果</p><p>针对O有KR1，KR2，这个其实类似于里程碑，</p><table><thead><tr><th>O</th><th>KR</th><th>信心指数（1-10）</th><th>备注</th></tr></thead><tbody><tr><td></td><td>KR1</td><td></td><td></td></tr><tr><td></td><td>KR2</td><td></td><td></td></tr><tr><td></td><td>KR2</td><td></td><td></td></tr></tbody></table><p>每日站会 -&gt;  周会 -&gt; 月度会议 -&gt; 季度会议 -&gt; 年度 </p><h3 id="SMART"><a href="#SMART" class="headerlink" title="SMART"></a>SMART</h3><p>S：Specific 具体的</p><p>M：measurable 可衡量的</p><p>A：attainable 可实现的</p><p>R：Relevant 相关的</p><p>T：time-bound 有时限的</p><h3 id="帕雷托原则"><a href="#帕雷托原则" class="headerlink" title="帕雷托原则"></a>帕雷托原则</h3><blockquote><p>帕雷托原则，也可称为二八法则，由意大利经济学家帕雷托提出，意思是让20%的投入产生80%的效益</p></blockquote><p>我们在每天的工作中，总会有精力充沛的时候，也会有大脑疲劳不堪的时候。因此我们要把握一天中精力最充沛的时间去集中精力做重要的事情。在疲惫时，我们则可以停下重要的工作，去做一些琐碎的事情，比如处理邮件等。</p><h3 id="“吃青蛙”定律"><a href="#“吃青蛙”定律" class="headerlink" title="“吃青蛙”定律"></a>“吃青蛙”定律</h3><blockquote><p>  “吃青蛙”定律来自博恩·崔西的《吃掉那只青蛙》。</p></blockquote><p>“青蛙”是指最艰巨、最重要的任务。坚持三个原则：</p><ol><li><p>每天早上做最难的那件事，那么，一天之内就没有比这更糟糕的事情了；</p></li><li><p>面对两件重要的事，要优先做更重要的那一件；</p></li><li><p>对于重要的事，要立即行动，说做就做，否则考虑得再周全而不行动也无济于事。</p></li></ol><p><strong>时间管理小贴士</strong></p><table><thead><tr><th>1.拒绝拖延症</th><th>2.赢得时间</th></tr></thead><tbody><tr><td>把工作写在纸上思考</td><td>建立工作时段，集中处理重要任务或同类任务（批量处理，缩小切换的时间）</td></tr><tr><td>开工前准备好所有材料</td><td>设立拒绝打扰时间（保持专注）</td></tr><tr><td>从小事做起</td><td>重要的事情拥有优先权（要事第一）</td></tr><tr><td>将任务分成若干环节，分别进行处理</td><td>尽可能做那些确实关键的工作</td></tr><tr><td>抑制完美主义</td><td>学会授权，对于烦琐的服务性工作可以借助他人之手（授权外包）</td></tr><tr><td>保持快节奏</td><td>任务分解，由大到小，由复杂到简单（任务分解）</td></tr><tr><td></td><td>给每一项任务规定完成期限（DDL，没有期限的会一直拖着）</td></tr><tr><td></td><td>重点任务，尽早完成 (吃掉那只青蛙)</td></tr><tr><td></td><td>根据自己工作效率或者注意力的高低波动，规划做事顺序（能量高峰）</td></tr></tbody></table><h2 id="关于居家办公"><a href="#关于居家办公" class="headerlink" title="关于居家办公"></a>关于居家办公</h2><h3 id="营造办公环境"><a href="#营造办公环境" class="headerlink" title="营造办公环境"></a>营造办公环境</h3><h4 id="规划工作时间"><a href="#规划工作时间" class="headerlink" title="规划工作时间"></a>规划工作时间</h4><h4 id="专用空间"><a href="#专用空间" class="headerlink" title="专用空间"></a>专用空间</h4><h4 id="吃什么"><a href="#吃什么" class="headerlink" title="吃什么"></a>吃什么</h4><h3 id="明确目标"><a href="#明确目标" class="headerlink" title="明确目标"></a>明确目标</h3><h4 id="阶段性"><a href="#阶段性" class="headerlink" title="阶段性"></a>阶段性</h4><p>在家办公的时候，我们制定OKR要注意：</p><p>  （1）周期可以更短，1个月或者两个月为一个周期，甚至一周，加快节奏，提升产出；</p><p>  （2）要与上级及关联者沟通，达成一致。</p><p>遵循以下5W1H法则：</p><p>何人（Who）：</p><p>何时（When）：</p><p>何事（What）：</p><p>何地（Where）：</p><p>为何（Why）：</p><p>如何（How）：</p><h4 id="制定周计划"><a href="#制定周计划" class="headerlink" title="制定周计划"></a>制定周计划</h4><ol><li>任务清单，明确如下：</li></ol><p>● 具体的任务：要做什么？</p><p>● 任务的产出：如何判断完成了任务？</p><p>● 任务的完成时间：什么时候完成？</p><ol start="2"><li>在制定任务清单注意要点：</li></ol><p>● 每个任务，完成的时间不超过1天。</p><p>● 每个任务，都应有完成标志。</p><p>● 任务的完成信息，能够共享和同步。</p><ol start="3"><li>制作周计划任务可视化看板。可视化看板包含三要素：</li></ol><p>● To Do：本周计划要完成的任务项。</p><p>● Doing：今天将要做的任务项。</p><p>● Done：本周已经完成的任务项。</p><h4 id="深度工作"><a href="#深度工作" class="headerlink" title="深度工作"></a>深度工作</h4><blockquote><p> 每次深度工作可以设定在45分钟到1.5小时之间。</p></blockquote><blockquote><p>根据注意力恢复理论，我们的注意力是有限的，只能在一段有限的时间内专注于一项任务，直到它变得让人筋疲力尽。</p></blockquote><p>至于一项任务是否属于深度工作，可以参考以下问题：</p><p>● 该任务是否需要集中注意力？</p><p>● 任务需要有专门的知识技能吗？</p><p>● 任务很难重复吗？</p><p>● 该任务会创造新的价值吗？</p><p>保持不间断的专注，你需要创造一个无干扰的环境。这意味着要屏蔽无关的和破坏性的打扰——聊天信息、电子邮件、会议、电话、社交媒体及其他杂音。非常需要注意的一点是，要与你的团队和上级达成一致：允许你不及时回复消息。</p><p>由于这个原因，许多人强制执行严格的“隔离措施”：</p><p>● 将手机设置为“免打扰”模式；</p><p>● 将邮箱设置为自动回复消息；</p><p>● 将“深度工作”时间分享给同事。</p><p>每周回顾：花时间反思过去一周进展顺利和失败的原因，并计划下一周的工作。</p><blockquote><p> 这是检视目标，并确保自己每天所做的工作能够帮助自己实现目标的机会</p></blockquote><p>在回顾的时候，我们可以参考以下问题清单：</p><p>● 我本周总体感觉如何？</p><p>● 是什么让我在本周达到目标？</p><p>● 有什么事情阻止我本周实现目标？</p><p>● 我本周采取了哪些行动来推动我实现长期目标？</p><p>● 下周我该如何改善？</p><p>● 下周我该怎么办，以使我实现长期目标？</p><blockquote><p>营造环境、明确目标、深度工作和每周回顾，这是使在家工作更高效的四步法。</p><p>但是，至关重要的是：寻找初心。</p><p>初心，是指自己感兴趣的事，能发挥自己优势的事，可以实现自己价值的事。</p><p> 拥有初心，你就具备最大能量，就会取得更多成果。”</p></blockquote><p><strong>时间表参考：</strong></p><table><thead><tr><th>上午时间</th><th>要做的事情</th><th>备注</th><th>其他</th></tr></thead><tbody><tr><td>6:00 - 7: 00</td><td>晨间流程</td><td>先洗漱收拾屋子（30min），然后冥想，做导引（30min），</td><td></td></tr><tr><td>7:00 - 8:00</td><td>吃早餐</td><td>上个流程把该下锅的都下锅</td><td></td></tr><tr><td>8:00 - 9:30</td><td>深度工作</td><td>内容为：</td><td></td></tr><tr><td>…</td><td></td><td></td><td></td></tr><tr><td>12:00 - 13:00</td><td>午饭</td><td></td><td></td></tr><tr><td>…</td><td></td><td></td><td></td></tr><tr><td>…</td><td></td><td></td><td></td></tr><tr><td>16:30 - 17:00</td><td>结束工作</td><td>需要复盘然后规划下一天</td><td></td></tr></tbody></table><p>自我激励:</p><p>  ● 成功之后奖励自己吃冰激凌</p><p>  ● 每天早上起来对着镜子说：我是最棒的</p><p>  ● 回想自己的巅峰时刻</p><p>  ● 告诉好朋友自己的想法</p><p>  ● 先开始一小步</p><p>  ● 记住：完成胜于完美</p>]]></content>
    
    
    <summary type="html">《每个人的OKR》书摘，物质激励的副作用与时间管理术语整理。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="效率" scheme="https://blog.no-claw.com/tags/%E6%95%88%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>使用ipad 串流 MacOS</title>
    <link href="https://blog.no-claw.com/posts/474f606e/"/>
    <id>https://blog.no-claw.com/posts/474f606e/</id>
    <published>2024-03-23T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>服务端是 sunshine，客户端叫做 moonlight</p><p>MacOS 安装的命令如下：</p><p>使用该命令会自动拉取源码包并且自动编译，然后使用 sunshine 命令启动：</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">brew tap LizardByte/homebrew                        </span><br><span class="line">brew install sunshine</span><br></pre></td></tr></table></figure><span id="more"></span><p>这个服务会启动在 47990 端口，进入网页是这个样子</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241746043.png"></p><p>客户端是使用 moonlight，可以在 app store 直接下载</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241743118.png"></p><p>点击 Add Host Manully，填入服务端的 ip。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241749532.png"></p><p>然后会提示提示配对，这里给了一串 PIN 码，需要填写到服务端的 WEB 中</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241751513.png"></p><p>然后把上一步 moonlight 的 PIN 写到这里</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241742465.png"></p><p>然后对于 MacOS 来说，需要给到终端录屏和辅助功能的权限。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241757859.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241757263.png"></p><p>然后就可以开心的玩了～</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202403241759936.png"></p>]]></content>
    
    
    <summary type="html">用 Sunshine + Moonlight 实现 iPad 串流 macOS，把 iPad 变成副屏。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
  </entry>
  
  <entry>
    <title>自制辣椒油</title>
    <link href="https://blog.no-claw.com/posts/eae9c1f1/"/>
    <id>https://blog.no-claw.com/posts/eae9c1f1/</id>
    <published>2024-02-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li>起锅烧油,放入香料(八角,花椒,桂皮,香叶),葱姜,蒜,香菜,洋葱炸至金黄捞出弃用</li><li>碗内辣椒切碎,有条件可以自己研磨五香粉和芝麻混合,用白酒拌匀(激发香味防止炸糊),我放的化花雕酒味道也很不错</li><li>热油泼到辣椒上,然后滴几滴醋增香.</li></ol>]]></content>
    
    
    <summary type="html">自制辣椒油的简单做法，香料配比与泼油技巧</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
  </entry>
  
  <entry>
    <title>电容键盘体验</title>
    <link href="https://blog.no-claw.com/posts/7b93b635/"/>
    <id>https://blog.no-claw.com/posts/7b93b635/</id>
    <published>2024-02-15T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>曾经向往过 HHKB,也用过一段时间的国产宁芝,总体来说体验还算可以,只是买的早但是只支持有线最后还是弃坑了</p><span id="more"></span><p>这里记录一次电容充电的记录,尽管理论上电容键盘是无限寿命,但是也遇到了按键失灵的问题,客服解释说是缺电需要充电,同时提供了对应的驱动软件,大概过程就是插上电脑,然后用检验工具按下任意键就好了.印象里就充了一次电然后好几年都没问题.特此记录下来<br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240216112425213.png" alt="image-20240216112425213"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240216112414072.png" alt="image-20240216112414072"></p>]]></content>
    
    
    <summary type="html">电容键盘使用体验分享，从 HHKB 到国产宁芝</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
  </entry>
  
  <entry>
    <title>MacOS安装的软件无法打开</title>
    <link href="https://blog.no-claw.com/posts/3ab0fe4d/"/>
    <id>https://blog.no-claw.com/posts/3ab0fe4d/</id>
    <published>2024-02-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225081805.png"></p> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225081826.png" alt="image.png"></p><p>因为 PicGo 没有签名，所以会被 macOS 的安全检查所拦下。</p><ol><li>安装后打开遇到「文件已损坏」的情况，请按如下方式操作：</li></ol><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">sudo spctl --master-disable</span><br></pre></td></tr></table></figure><p>然后放行 PicGo :</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">xattr -cr /Applications/PicGo.app</span><br></pre></td></tr></table></figure><p>然后就能正常打开。</p><p><a href="https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md">https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md</a></p>]]></content>
    
    
    <summary type="html">macOS 安装第三方软件提示无法打开的解决方法。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="Apple" scheme="https://blog.no-claw.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>多平台日程同步</title>
    <link href="https://blog.no-claw.com/posts/450f2bc0/"/>
    <id>https://blog.no-claw.com/posts/450f2bc0/</id>
    <published>2024-01-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>本来是 apple 全家桶，无奈 AWS 上的 MacOS 没办法用，于是在 windows 端使用 outlook 对 apple 的日历进行接入。</p><p>首先在 apple 日历部分添加 outlook 账户，这样就可以配置完 apple 日历和 outlook 的双向同步，在 MacOS&#x2F;IOS 上安装 outlook 客户端，然后 windows 只需要使用默认的邮件客户端即可。</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">graph LR</span><br><span class="line">i(ics文件) --导入--&gt; ia(iPhone apple 日历) &lt;--&gt; ic[icloud] &lt;--&gt; ma(Mac apple日历)</span><br><span class="line">ia &lt;--&gt; io(Iphone outlook) &lt;--&gt; os[outlook server] &lt;--&gt; mo(Mac outlook)</span><br><span class="line">os &lt;--&gt; wo(windows outlok)</span><br><span class="line">ia &lt;--&gt; is(iphone sorted3)</span><br><span class="line">is &lt;--&gt; ic &lt;--&gt; ms(Mac sorted3)</span><br><span class="line">ms &lt;--&gt; ma</span><br></pre></td></tr></table></figure><p>从下图可以看到日历分为 outlook 和 icloud 的部分，我们把后续的日程添加到 Outlook 部分就好。另外注意的是，outlook 的默认日历就叫做“日历”，不能修改名字也不能删除。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_BED298D58B69-1.jpeg" alt="IMG_BED298D58B69-1"></p><p>同时我也在 apple 上安装了 sorted3，只有日程会被上边的流程同步，任务只能用软件自带的 icloud 同步（apple 设备），这样就满足了在 window 和 apple 随时查看和编辑的功能了，sorted3 中也可以选择需要同步的日历。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240111100732235.png" alt="image-20240111100732235"></p><p>对于批量的日程，那么需要用到 ICS 文件，实测 iphone 的微信不能唤起 apple 的日历，所以采取了发邮件的办法，这里用到的是 apple 的邮件客户端，倒入 outlook 会有些许同步的问题，就不再继续研究了。手机的 Apple 日历倒入之后，全平台很快就可以进行同步了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_94EF88297C76-1.jpeg" alt="IMG_94EF88297C76-1"></p><p>这样无论是 apple 日历，outlook 还是 sorted3，只要一个软件添加日程，剩下的平台都会同步。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240112085234629.png"></p>]]></content>
    
    
    <summary type="html">实现 Apple 日历与 Windows Outlook 等多平台日程同步</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>Home Assistant 实现米家设备接入 HomeKit，圆梦苹果全家桶</title>
    <link href="https://blog.no-claw.com/posts/aec4a8fe/"/>
    <id>https://blog.no-claw.com/posts/aec4a8fe/</id>
    <published>2024-01-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[ <span id="more"></span><p>最近终于把软路由搞定，群晖上也顺利安装好了 Docker，于是立马安排上迟到了好几年的 Home Assistant（简称 HA）。这篇文章就分享一下如何用 HA 把米家设备接入 Apple 的 HomeKit，真正实现“苹果家庭全自动”的梦想！</p><h2 id=""><a href="#" class="headerlink" title=""></a><!--more--></h2><h2 id="Step-1：用-Docker-部署-Home-Assistant"><a href="#Step-1：用-Docker-部署-Home-Assistant" class="headerlink" title="Step 1：用 Docker 部署 Home Assistant"></a>Step 1：用 Docker 部署 Home Assistant</h2><p>先直接上命令。建议使用 <code>host</code> 网络模式，不然后面 iPhone 添加 HomeKit 的时候经常找不到设备。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> docker run -d --name=<span class="string">&quot;home-assistants&quot;</span> --net=host homeassistant/home-assistant</span><br></pre></td></tr></table></figure><blockquote><p>小提示：我图省事没做目录映射，但大家正式部署还是建议把 <code>config</code> 映射出来，便于备份和迁移。</p></blockquote><p>启动完成后，默认监听在 <code>8123</code> 端口。浏览器访问 <code>http://群晖IP:8123</code> 即可进入 HA 初始界面。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145154221.png" alt="初始界面"></p><hr><h2 id="Step-2：初次配置-Home-Assistant"><a href="#Step-2：初次配置-Home-Assistant" class="headerlink" title="Step 2：初次配置 Home Assistant"></a>Step 2：初次配置 Home Assistant</h2><p>第一次访问会提示你创建账户。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145237852.png" alt="创建用户"></p><p>接着可以选择你的地理位置，后面用于推送天气等信息。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145301645.png" alt="位置设置"></p><hr><h2 id="Step-3：安装米家插件，让-HA-支持-Xiaomi-生态"><a href="#Step-3：安装米家插件，让-HA-支持-Xiaomi-生态" class="headerlink" title="Step 3：安装米家插件，让 HA 支持 Xiaomi 生态"></a>Step 3：安装米家插件，让 HA 支持 Xiaomi 生态</h2><p>参考的是小米官方的 Home Assistant 插件项目：</p><p>🔗 项目地址：<a href="https://github.com/XiaoMi/ha_xiaomi_home">https://github.com/XiaoMi/ha_xiaomi_home</a></p><p>先进入 HA 容器内部，然后安装插件。</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="built_in">cd</span> config</span><br><span class="line">git <span class="built_in">clone</span> https://github.com/XiaoMi/ha_xiaomi_home.git</span><br><span class="line"><span class="built_in">cd</span> ha_xiaomi_home</span><br><span class="line">./install.sh /config</span><br></pre></td></tr></table></figure><p>完成后重启 HA 容器，重新登录到 UI 界面。</p><p>点击左下角“设置” → “设备与服务”，进入集成页面：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145804012.png" alt="进入设备与服务"></p><p>点击“添加集成”，选择 <strong>Xiaomi Home</strong>：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145616238.png" alt="添加米家集成"></p><p>会先看到免责声明，点继续。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145609422.png" alt="免责声明"></p><hr><h2 id="Step-4：通过-OAuth-登录小米账号"><a href="#Step-4：通过-OAuth-登录小米账号" class="headerlink" title="Step 4：通过 OAuth 登录小米账号"></a>Step 4：通过 OAuth 登录小米账号</h2><p>插件采用 OAuth 登录小米账号，这一步会打开小米官方的登录授权页面。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145604122.png" alt="授权页面"></p><p>登录成功后返回回调地址：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145552826.png" alt="登录页面"></p><blockquote><p>⚠️ 注意：默认回调地址是 <code>http://homeassistant.local</code>，很多时候解析不了。可以手动把浏览器地址栏改成 <code>http://你的HA局域网IP:8123</code>，再回车，就能完成回调流程。</p></blockquote><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145548204.png" alt="手动改回调地址"></p><p>成功登录后会同步你米家账号下所有的设备：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145541148.png" alt="导入设备"></p><p>数量感人！</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145526999.png" alt="米家设备清单"></p><hr><h2 id="Step-5：添加-HomeKit-支持"><a href="#Step-5：添加-HomeKit-支持" class="headerlink" title="Step 5：添加 HomeKit 支持"></a>Step 5：添加 HomeKit 支持</h2><p>进入“设置” → “设备与服务”，右下角点击“添加集成”，选择 HomeKit：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145453299.png" alt="添加 HomeKit 集成"></p><p>接着选择 HomeKit Bridge：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322145510157.png" alt="选择 Bridge 模式"></p><p>添加完成后左侧会出现一个 HomeKit 的二维码，使用 iPhone 上的“家庭”App 扫码配对即可。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322150306139.png" alt="生成配对码"></p><p>添加过程中，如果出现以下提示，说明网络设置有问题，大概率是 HomeKit 无法找到设备。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322150928148.png" alt="超时错误"></p><hr><h2 id="Step-6：排查-HomeKit-配对失败的问题"><a href="#Step-6：排查-HomeKit-配对失败的问题" class="headerlink" title="Step 6：排查 HomeKit 配对失败的问题"></a>Step 6：排查 HomeKit 配对失败的问题</h2><p>如果 iPhone 迟迟找不到 Home Assistant 设备，务必检查网络配置：</p><ol><li>进入“设置” → “系统” → “网络”，找到 HA 的网络适配器；</li><li>确保它和你手机所在的 Wi-Fi 是在同一个网段；</li><li>如果是 Docker + 群晖用户，确保容器是 <code>host</code> 网络模式；</li><li>没看到网卡配置？记得开启高级选项！</li></ol><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322150759317.png" alt="网络配置页面"></p><p>左下角点击头像，开启高级：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322151740390.png" alt="image-20250322151740390"></p><p>没开高级的话是这样的：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322150556172.png" alt="开启高级设置"></p><hr><h2 id="Step-7：设备全部导入-HomeKit！"><a href="#Step-7：设备全部导入-HomeKit！" class="headerlink" title="Step 7：设备全部导入 HomeKit！"></a>Step 7：设备全部导入 HomeKit！</h2><p>终于圆梦！所有米家设备都顺利接入了 HomeKit，iPhone 上可以直接语音控制开灯、调温度，真正体验到苹果生态下的丝滑体验。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/picgo-imh/master/image-20250322151016195.png" alt="导入成功"></p><hr><h2 id="尾巴"><a href="#尾巴" class="headerlink" title="尾巴"></a>尾巴</h2><p>从部署 HA 到米家设备接入，再到 HomeKit 配对，中间有点小坑，但整体体验还是很不错的。如果你也想让米家秒变 HomeKit 原生设备，不妨试试这个方法。让智能家居真正融入 iOS 生态，丝滑又稳定！</p>]]></content>
    
    
    <summary type="html">用 Home Assistant 将米家智能设备接入 Apple HomeKit</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>NFC复制卡写手机手环</title>
    <link href="https://blog.no-claw.com/posts/5ce15c7a/"/>
    <id>https://blog.no-claw.com/posts/5ce15c7a/</id>
    <published>2024-01-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>手机无法复制加密的 IC 卡,所以需要需要额外的方法来进行写入</p><h3 id="读原来的卡"><a href="#读原来的卡" class="headerlink" title="读原来的卡"></a>读原来的卡</h3> <span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225075543.png"></p><h3 id="仅复制卡号"><a href="#仅复制卡号" class="headerlink" title="仅复制卡号"></a>仅复制卡号</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225075512.png"></p><h3 id="创建空白卡"><a href="#创建空白卡" class="headerlink" title="创建空白卡"></a>创建空白卡</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202402250843204.png"></p><h3 id="仅复制卡片卡号"><a href="#仅复制卡片卡号" class="headerlink" title="仅复制卡片卡号"></a>仅复制卡片卡号</h3><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_4586.jpg"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225075428.png"></p><h3 id="写入数据"><a href="#写入数据" class="headerlink" title="写入数据"></a>写入数据</h3><p>接下来就是把手机&#x2F;手环放在机器进行写入</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_4588.JPG"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/IMG_4587.JPG"></p><p>添加成功</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20240225084428.png"></p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p>NFC-PRO 至尊双频版操作说明<br>读写卡操作视频:<br><a href="https://cloud.video.taobao.com/play/u/null/p/1/e/6/t/1/409421501065.mp4">https://cloud.video.taobao.com/play/u/null/p/1/e/6/t/1/409421501065.mp4</a><br>加密卡手机手环模拟操作视频:<br><a href="https://cloud.video.taobao.com/play/u/null/p/1/e/6/t/1/409017352153.mp4">https://cloud.video.taobao.com/play/u/null/p/1/e/6/t/1/409017352153.mp4</a></p>]]></content>
    
    
    <summary type="html">使用 NFC 工具复制加密 IC 卡并写入手机手环的教程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>出远门带的电子产品</title>
    <link href="https://blog.no-claw.com/posts/3205bf5/"/>
    <id>https://blog.no-claw.com/posts/3205bf5/</id>
    <published>2024-01-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ul><li>MacBook&#x2F;ipad&#x2F;iphone&#x2F;nuc12&#x2F;鼠标&#x2F;耳机</li><li>Gan&#x2F;移动电源&#x2F;type C 电源</li><li>NFC 复制器+空白卡</li></ul>]]></content>
    
    
    <summary type="html">出远门时随身携带的电子产品清单与搭配建议</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>雷电4传输</title>
    <link href="https://blog.no-claw.com/posts/6cc0c7dd/"/>
    <id>https://blog.no-claw.com/posts/6cc0c7dd/</id>
    <published>2023-12-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>市面上的对拷线总是没有称心如意的,做的最好的应该是绿联,</p><p>于是在 ChatGPT 上询问了相关事宜,大概意思就是一个把雷电设备虚拟成一张网卡,然后设置一个单独的网络通道,两台机器使用 IP 地址进行通讯,知乎上也有成功的雷电 3 案例,只是他们 WebDav 的速度是 600MB&#x2F;S</p><span id="more"></span><p>而我的是雷电 4 也是这个结果,只是在 NUC12 识别时候总是不能满速识别.</p><p>咨询客服之后也没有得到满意的结果,不久前 intel 吧 nuc 业务线给华硕了,结果售后只是给排查方向,看 BIOS 有没有打开雷电开关之类的,或者就是等待十天半个月的返厂检测.</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217205800438.png" alt="image-20240217205800438"></p><p>webdav:</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217205733355.png" alt="image-20240217205733355"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/Snipaste_2024-02-17_12-12-28.png"><br>window 下显示</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/Snipaste_2024-02-17_12-36-44.png"></p><p>20G 的虚拟网卡,在 linux 下也是如此</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202402250826256.png"></p><p>设备管理器显示 USB4 而不是雷电 4</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202402250937502.png"></p><p>直连 MBP 两个雷电口:</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217205707661.png" alt="image-20240217205707661"></p><p>popos 和 MBP 使用 iperf3 对打:</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217205548641.png" alt="image-20240217205548641"></p><p>MacOS: 雷电 4 的线+40G 硬盘盒</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217205355588.png" alt="image-20240217205355588"></p><h4 id="40G-硬盘盒原装测速"><a href="#40G-硬盘盒原装测速" class="headerlink" title="40G 硬盘盒原装测速"></a>40G 硬盘盒原装测速</h4><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217211032287.png" alt="image-20240217211032287"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20240217211041438.png" alt="image-20240217211041438"></p><p>win 测速</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/Snipaste_2024-02-17_13-11-30.png"></p>]]></content>
    
    
    <summary type="html">雷电4接口数据传输方案与对拷线使用体验</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>悠乐汇看雪</title>
    <link href="https://blog.no-claw.com/posts/1fcc0fa5/"/>
    <id>https://blog.no-claw.com/posts/1fcc0fa5/</id>
    <published>2023-12-12T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>才吃过燃面，过午时又得出去觅食。</p><p>门前的松树又多新雪，一夜之间多了些许北国的味道，鹅毛大雪飘散着，伸出手凉凉的就化了。</p><p>北京的冬天很温和，对于东北人来说像深秋，静静的飘雪中撑一把轻伞，就能轻松制造韩剧男女主角的邂逅，北京的人是不是没体会过风刀霜剑？这的雪是留不住的，在气温回暖以及汽车尾气的破坏中，渐渐的融化成水，再蒸发成空气。不喜欢雪的人，是觉得雪堆融化之后道路泥泞，车辆难行，甚至气温来一个急转弯，把这雪水变成冰沙，甚至再光滑一些，这总会影响行人的匆匆。</p><span id="more"></span><p>每一片飘落的雪花，好似这芸芸众生，降生，飘零，陨落以及消亡。一片一片的雪花组成了洁白的雪地，雪地上留下了行人的脚印，有三两好友在讨论，有奔波的快递员外卖骑手，窗下还有小孩子在玩雪，老人在哄孩子，小男孩在团雪球，小女孩在一边看着，穿过街道，已经是光洁的柏油马路，这是雪冢。</p><p>无人的雪，踏上有吱吱的声音，光洁的雪总是能给人遐想，好似可以根据心思任意雕琢，又或者因为不是最佳路径从而无人踏足，而不远的一旁布满了行人的脚印，我却刻意绕开。夏日的大排档被闲置在这里，棚子上积满了厚厚的雪，伸出手来攒个小雪球，等着越滚越大。</p><p>等待手机升级的功夫，顶了一头的芦花。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202312131658623.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/6461702457866_.pic_hd.jpg"><br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/6451702457865_.pic_hd.jpg"></p>]]></content>
    
    
    <summary type="html">北京悠乐汇看雪的日常记录</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%8C%97%E6%BC%82/"/>
    
    
    <category term="北漂" scheme="https://blog.no-claw.com/tags/%E5%8C%97%E6%BC%82/"/>
    
  </entry>
  
  <entry>
    <title>Filebeat 输出日志到 Opensearch</title>
    <link href="https://blog.no-claw.com/posts/8924813e/"/>
    <id>https://blog.no-claw.com/posts/8924813e/</id>
    <published>2023-12-08T05:57:57.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>原文链接，当时一起折腾的，抄个作业。</p><p><a href="https://liarlee.site/2023/12/08/Other/Opensearch_Filebeat%20%E8%BE%93%E5%87%BA%E6%97%A5%E5%BF%97%E5%88%B0%20Opensearch/?highlight=opensearch">https://liarlee.site/2023/12/08/Other/Opensearch_Filebeat%20%E8%BE%93%E5%87%BA%E6%97%A5%E5%BF%97%E5%88%B0%20Opensearch/?highlight=opensearch</a></p><p>这个最后基本上可以确认是一个兼容性问题，测试完成发现， 开启兼容模式的 Opensearch+filebeat 的组合， filebeat 还是会不定期重启。</p><hr><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>需求是，使用 ES + filebeat 模式在收集日志。<br>使用 Supervisor 作为容器的主进程管理工具，启动后分别运行 应用（这里用 nginx 代替） + filebeat</p><p>现在想要用 ECS Fargate， 然后依旧还是这个模式， 尽可能新的变动之前的架构， ES 替换成 OpenSearch。</p><span id="more"></span><p>按照这个路数测试。</p><h2 id="创建-Opensearch"><a href="#创建-Opensearch" class="headerlink" title="创建 Opensearch"></a>创建 Opensearch</h2><p>版本：<br>OpenSearch 2.11 (latest)<br>OpenSearch_2_11_R20231113-P1 (latest)<br>Availability Zone(s)<br>1-AZ without standby</p><h2 id="构建-Supervisor-管理的容器"><a href="#构建-Supervisor-管理的容器" class="headerlink" title="构建 Supervisor 管理的容器"></a>构建 Supervisor 管理的容器</h2><h3 id="创建-Dockerfile"><a href="#创建-Dockerfile" class="headerlink" title="创建 Dockerfile"></a>创建 Dockerfile</h3><p>创建 dockerfile 的部分， 比较难的是 ， 需要找到合适的 filebeat 版本<br>参考页面: <a href="https://opensearch.org/docs/latest/tools/index/#agents-and-ingestion-tools">Agents and ingestion tools</a><br>其他的步骤就下载安装就可以.</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用官方Nginx作为基础镜像</span></span><br><span class="line"><span class="keyword">FROM</span> reg.liarlee.site/docker.io/nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装Supervisor</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y supervisor</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> -p /var/log/supervisor</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> -p /etc/filebeat/</span></span><br><span class="line"><span class="comment">#RUN curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.11.3-amd64.deb &amp;&amp; dpkg -i filebeat-8.11.3-amd64.deb</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-oss-7.12.1-amd64.deb &amp;&amp; dpkg -i filebeat-oss-7.12.1-amd64.deb</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> filebeat.yml /etc/filebeat/filebeat.yml</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> nginx.conf /etc/nginx/nginx.conf</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 将Supervisor配置文件复制到容器中</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> supervisord.conf /etc/supervisor/conf.d/supervisord.conf</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动Supervisor来管理Nginx进程</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [ <span class="string">&quot;/usr/bin/supervisord&quot;</span>, <span class="string">&quot;-n&quot;</span> ]</span></span><br></pre></td></tr></table></figure><h3 id="准备配置文件"><a href="#准备配置文件" class="headerlink" title="准备配置文件"></a>准备配置文件</h3><p>需要准备的配置文件一共 3 个：</p><ul><li>supervisord.conf supervisor 的管理配置， 决定了那些进程被管理。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="meta prompt_">&gt; </span><span class="language-bash"><span class="built_in">cat</span> ./supervisord.conf</span></span><br><span class="line">[unix_http_server]</span><br><span class="line">file=/var/run/supervisor.sock   ; (the path to the socket file)</span><br><span class="line">chmod=0700                       ; socket file mode (default 0700)</span><br><span class="line">chown=nobody:nogroup             ; socket file uid:gid owner</span><br><span class="line"></span><br><span class="line">[supervisord]</span><br><span class="line">logfile_maxbytes=50MB                          ; 日志文件的最大大小</span><br><span class="line">logfile_backups=10                             ; 日志文件的备份数</span><br><span class="line">loglevel=info                                  ; 日志级别</span><br><span class="line">nodaemon=false                                 ; 是否以守护进程模式启动Supervisor</span><br><span class="line">minfds=1024                                    ; 可以打开的文件描述符的最小数量</span><br><span class="line">minprocs=200                                   ; 可以创建的进程的最小数量</span><br><span class="line"></span><br><span class="line">[program:nginx]</span><br><span class="line">command=/usr/sbin/nginx -g &quot;daemon off;&quot;  ; 启动Nginx的命令</span><br><span class="line">autostart=true                             ; 在Supervisor启动时自动启动</span><br><span class="line">autorestart=true                           ; 程序异常退出后自动重启</span><br><span class="line">stderr_logfile=/var/log/nginx/error.log    ; 错误日志文件路径</span><br><span class="line">stdout_logfile=/var/log/access.log   ; 访问日志文件路径</span><br><span class="line"></span><br><span class="line">[program:filebeat]</span><br><span class="line">command=/usr/bin/filebeat -e -c /etc/filebeat/filebeat.yml  ; 启动Filebeat的命令</span><br><span class="line">autostart=true</span><br><span class="line">autorestart=true</span><br><span class="line">stderr_logfile=/var/log/filebeat.err.log</span><br><span class="line">stdout_logfile=/var/log/filebeat.out.log</span><br></pre></td></tr></table></figure><ul><li><p>filebeat.yml filebeat 的配置文件。 这配置文件 GPT 会直接写出一个可以用 <code>output.opensearch:</code>， 其实还是不能的， 只能使用原本的配置文件。 (也许是我选择的 filebeats 版本不正确, 所以不行吧</p><p>filebeat 本身是 es 序列里面的产品， 不支持 opensearch 也合理， 如果写成 opensearch 会找不到 output 的定义， 也说明并不支持这个字段。</p></li></ul><p>2023-12-14T12:03:12.560Z INFO [publisher_pipeline_output] pipeline&#x2F;output.go:145 Attempting to reconnect to backoff(elasticsearch(<a href="https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn/">https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn:443</a>)) with 7 reconnect attempt(s)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">```shell</span><br><span class="line">&gt; cat ./filebeat.yml</span><br><span class="line">filebeat.inputs:</span><br><span class="line">- type: filestream</span><br><span class="line">  id: nginxaccesslog</span><br><span class="line">  paths:</span><br><span class="line">    - /var/log/access.log</span><br><span class="line">  fields:</span><br><span class="line">    log_type: access</span><br><span class="line"></span><br><span class="line">seccomp.enabled: false # 这个不关闭的话可能会是一个干扰。</span><br><span class="line">logging.level: debug # 由于调试方便设置了DEBUG。</span><br><span class="line"></span><br><span class="line"># 这个配置段是关闭 xpack， xpack功能只在es里面提供， 商业版本。</span><br><span class="line">ilm.enabled: false</span><br><span class="line">setup.ilm.enabled: false</span><br><span class="line">setup.pack.security.enabled: false</span><br><span class="line">setup.xpack.graph.enabled: false</span><br><span class="line">setup.xpack.watcher.enabled: false</span><br><span class="line">setup.xpack.monitoring.enabled: false</span><br><span class="line">setup.xpack.reporting.enabled: false</span><br><span class="line"></span><br><span class="line"># output就是还用es</span><br><span class="line">output.elasticsearch:</span><br><span class="line">  enable: true</span><br><span class="line">  hosts: [&quot;vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn:443&quot;] # 这个部分需要手动指定443, 因为是es的默认配置, 所以直接去 9200,就会连接不上.</span><br><span class="line">  protocol: &quot;https&quot;</span><br></pre></td></tr></table></figure><p>xpack 报错的日志大概是这样的：</p><blockquote><p>2023-12-14T12:03:12.560Z ERROR [publisher_pipeline_output] pipeline&#x2F;output.go:154 Failed to connect to backoff(elasticsearch(<a href="https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn/">https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn:443</a>)): Connection marked as failed because the onConnect callback failed: request checking for ILM availability failed: 401 Unauthorized: {“Message”:”Your request: ‘&#x2F;_xpack’ is not allowed.”}<br>2023-12-14T12:03:12.560Z INFO [publisher_pipeline_output] pipeline&#x2F;output.go:145 Attempting to reconnect to backoff(elasticsearch(<a href="https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn/">https://vpc-ecs-nginx-opensearch-qt7m5rmhddggkiuapyybcmz5oe.cn-north-1.es.amazonaws.com.cn:443</a>)) with 7 reconnect attempt(s)</p></blockquote><ul><li>nginx.conf 这个是 nginx 应用文件， 模拟一个应用程序， 提供 webserver 服务。配置文件就是标准的配置文件, 修改一下日志输出的路径.</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">access_log  /var/log/access.log  main;</span><br></pre></td></tr></table></figure><p>由于 baseimage 用的是 nginx 的， 所以 nginx 的日志输出会软链接到&#x2F;dev&#x2F;stdout, filebeat 不收软链接的文件, 开了 DEBUG 会看到跳过这个文件的日志.</p><h2 id="Buildstage"><a href="#Buildstage" class="headerlink" title="Buildstage"></a>Buildstage</h2><p>接下来就可以 Build 镜像然后进行测试了。</p><figure class="highlight shell"><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="meta prompt_">&gt; </span><span class="language-bash">dive build -t reg.liarlee.site/library/superv-nginx:v31 .</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">docker push reg.liarlee.site/library/superv-nginx:v31</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">docker run -it --name superv-nginx --<span class="built_in">rm</span>  reg.liarlee.site/library/superv-nginx:v31</span></span><br></pre></td></tr></table></figure><p>运行启动之后可以看到输出的日志是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">2023-12-14 14:03:31,093 INFO success: filebeat entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</span><br><span class="line">2023-12-14 14:03:31,093 INFO success: nginx entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</span><br></pre></td></tr></table></figure><p>然后查看 Opensearch 创建了默认 index， 名称是<code>filebeat-7.12.1-2023.12.14</code></p>]]></content>
    
    
    <summary type="html">配置 Filebeat 将日志输出到 Amazon OpenSearch 的实践教程</summary>
    
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/categories/AWS/"/>
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/categories/AWS/OpenSearch/"/>
    
    
    <category term="AWS" scheme="https://blog.no-claw.com/tags/AWS/"/>
    
    <category term="OpenSearch" scheme="https://blog.no-claw.com/tags/OpenSearch/"/>
    
    <category term="ELK" scheme="https://blog.no-claw.com/tags/ELK/"/>
    
    <category term="Filebeat" scheme="https://blog.no-claw.com/tags/Filebeat/"/>
    
  </entry>
  
  <entry>
    <title>爱情是婚姻的坟墓么</title>
    <link href="https://blog.no-claw.com/posts/5102820d/"/>
    <id>https://blog.no-claw.com/posts/5102820d/</id>
    <published>2023-11-30T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>婚姻是第二次投胎。</p><p>所谓爱屋及乌，经常有一种爱她就要包容她的一切的言论。那么反过来也是一样的，如果是在几经磨合之后仍然不能付诸真心，或者经历了无数次想去改变对方，再或者对现状无能为力只能默默叹气的时候。举个例子，对妹夫看不顺眼的时候，但凡在他们孩子身上看到任何相似之处，也都会有种莫名的不喜欢，甚至是把情绪转移过来。</p><p>周围的人大概有这样几种状态，单身未婚，同居未婚，已婚未育，已婚养娃。</p><span id="more"></span><p>单身未婚要么没有遇到正缘，要么是感情上面的独行者，常常被他人贴上活在自己世界，适合一个人的标签。他们也许渴望一场轰轰烈烈的爱情，但从不轻易涉足。</p><p>同居未婚，有些人打算长期维持这样的状态，彼此间相对比较独立，而且大多是丁克主义，双方也有稳定的收入，还不用在乎双方的亲戚闲言碎语以及社会舆论，那么在矛盾冷战时候也会发出是不是分手的质疑？</p><p>还有很大一部分人，不管条件如何，总归是先住在一起，然后再慢慢磨合，他们可能是大学生，也可能才刚刚步入社会，经常说出的话是顺应自己内心的想法，而且大多讲究顺理成章顺其自然，一旦有了夫妻之实，在分开之后另寻新欢很难说不是二婚。</p><p>当然也有些是真爱，本就打算结婚，然后住在一起，互相之间总会有个照应。然后一起走过磨合，订婚，再结婚的过程，这也不失为一段佳话，既加深了羁绊，也创造了彼此间的记忆，甚至哪怕新婚燕尔，甚至也能用老夫老妻这样的话语来调侃了。</p><p>养娃的人是最累了，十月怀胎，甚至民间还有一孕傻三年的说法。前几种情况相比，只有这有母性的光辉。而且常常被不孝有三，无后为大的观念所左右。不管是出于传宗接代还是其他的目的，在这的过程中，总会不自觉得强加上去自己的想法，甚至要求他做的更好抑或是不能落后于人，抑或者彻底沦为炫耀的资本。但对于家庭和事业兼顾的人来说，这往往很难两全，总会给孩子留下一些原生家庭的不安与不适感，并且常常对此无能为力。</p><p>几十年后的养老院，还是那样大的院子，周围还是同龄的人，仍然会把人分门别类，依旧听着要那个人的发号施令，江湖依旧是那个江湖。但，最近的新闻总是看的心惊胆寒，现代文明带来的身心不适已经极大的缩短了人的寿命，谁又能保证真的能活到那一天呢？幼年时盼望有人披着晚霞，踏着黄昏带你走出这桎梏，耄耋之年为何又作茧自缚，穿过着一阵阵的喧嚣，却只能看他人天伦。</p><p>对于不婚不育的人，我们一方面要感谢他们为人口减少作出的贡献，另一方面也会出于自己小小的私心，想要伴随生命从小到大的成长，即使无法告知他人生的意义，即使在他18岁的时候会也发出安德烈一样的感慨。谁说不能效仿古人，一门七进士，父子三探花呢？</p>]]></content>
    
    
    <summary type="html">从单身到养娃，聊聊婚姻中的几种状态，以及爱屋及乌背后的现实与无奈。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>从望京吃到酒仙桥</title>
    <link href="https://blog.no-claw.com/posts/512ddea4/"/>
    <id>https://blog.no-claw.com/posts/512ddea4/</id>
    <published>2023-11-10T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为吃了小半个北京的人，基本上可以看到足迹留在望京和酒仙桥这里两个板块，先说一说踩的雷，不喜欢密云的鱼，分店的方砖厂，前门乱七八糟的餐馆还有簋街的小作坊，还有同样的连锁，合生汇里的品质都要和毕淘买相差甚远。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20231111212612551.png" alt="image-20231111212612551"></p><p>这边都是写字楼，所以工作日会针对上班的人有优惠，一般不包括周五，因为要放假了大家都 high 起来了，人多的没有位置。</p><ul><li>郭靖烤全羊 58 抵 100（原来 38）</li><li>阿壹牛杂（美团单人餐 18 送饮料）</li><li>一井烧肉（25 单人餐，菜品半价）</li><li>掌上生活 100 减 50（羲和雅苑，烧肉 Like，小厨娘淮扬菜，上清水产，九割炸猪排，北李妈妈菜等）</li><li>阿诚市井潮汕菜（39.8 双人粥，俩小菜，打卡送豆花）</li><li>将太无二（39.9 鸡肉锅）</li><li>芦花椒麻鸡（晚间 50 抵 100，芦花脆脆茄）</li><li>刘胖妹面庄（晚间 19.9 抵 40，重庆胖妹的弟子点，跟北京胖妹没有关系，北京胖妹从来不打折）</li><li>聚点串吧 11.2 元包浆豆腐</li><li>多乐之日 5 元俩蛋挞</li><li>滋啦小路子烧烤（19.9 抵 50，不咋好吃）</li><li>隐市香·川菜·烧烤小馆（19.9 抵 50，还有午间单人餐）</li><li>中 8 楼（18.4 元黑三剁）</li><li>金掌勺锅包肉 39.9 元</li><li>煲仔皇充 528 送双倍</li><li>怒火八零市井火锅。88.8 元</li><li>赣南人家 98 双人餐（三个人也行）</li><li>功夫潮牛 138 双人餐（原来 128）</li><li>释面（9.9 素面，15.9 牛肉面，量小不推荐）</li><li>~~小湘界 98 2-3 人餐（量很小，很辣，最好两个人去吃）</li><li>管氏串吧 9.9 主食</li><li><del>京院涮肉 98，现在已经 158 不推荐</del></li><li>楠火锅 9.9 抵 100(感觉差评很多)</li><li>霸气大叔 39 抵 100</li><li>胖子龙虾（毛肚锅很不错）</li><li>米湘</li><li>九将烧肉（38 单人餐）</li><li><del>黄海渔村（鲅鱼水饺，很一般不好吃</del></li><li>蒸汽小镇（水饺</li><li>芝湖参鸡汤</li><li>松鹤楼小龙虾拌面</li><li>十八汆（砂锅秒杀很实惠</li><li>老乡鸡 9 块 9</li><li>六丁火烤肉 99 拼盘</li><li>霸碗盖饭（首开</li><li>重庆秘宗火锅</li><li>凯德 mall 椰子鸡火锅</li><li>丸福日式烤肉</li><li>望花路燃面(望京燃面天花板，很容易饿)</li><li>牛锣鼓火锅</li><li>1819 烧烤（其实还不错</li><li>荣焱</li><li>维京乐章三文鱼（意面双人餐）</li><li>小两口淄博烧烤</li></ul><p>可以看到从望京一路吃到酒仙桥～</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20231111212621202.png" alt="image-20231111212621202"></p><p>PS 北京吃的汇总：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20231111221912324.png" alt="image-20231111221912324"></p>]]></content>
    
    
    <summary type="html">北京望京到酒仙桥的美食探店与踩雷记录</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="吃货风云" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%90%83%E8%B4%A7%E9%A3%8E%E4%BA%91/"/>
    
    
    <category term="北漂" scheme="https://blog.no-claw.com/tags/%E5%8C%97%E6%BC%82/"/>
    
  </entry>
  
  <entry>
    <title>几个获取公网IP的网站</title>
    <link href="https://blog.no-claw.com/posts/b893f77b/"/>
    <id>https://blog.no-claw.com/posts/b893f77b/</id>
    <published>2023-09-26T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>如果你固定了公网IP，那么这几个结果都是一样的，如果你是二级运营商，那么可能出口IP不一样，需要向运营商索要动态公网IP。</p><p>speedtest： <a href="https://api-v3.speedtest.cn/ip">https://api-v3.speedtest.cn/ip</a></p><span id="more"></span><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></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="number">0</span><span class="punctuation">,</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;country&quot;</span><span class="punctuation">:</span> <span class="string">&quot;中国&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;province&quot;</span><span class="punctuation">:</span> <span class="string">&quot;北京&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;city&quot;</span><span class="punctuation">:</span> <span class="string">&quot;北京&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;district&quot;</span><span class="punctuation">:</span> <span class="string">&quot;朝阳区&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;isp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;中国联通&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;120.333&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lat&quot;</span><span class="punctuation">:</span> <span class="string">&quot;45.34&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;countryCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123.113.111.178&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;operator&quot;</span><span class="punctuation">:</span> <span class="string">&quot;中国联通&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;msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ok&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>IP.cn: <a href="https://ip.cn/api/index?type=0">https://ip.cn/api/index?type=0</a></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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;rs&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;中国 北京 北京市 联通&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123.113.111.178&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;isDomain&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>IP API: <a href="http://ip-api.com/json/">http://ip-api.com/json/</a></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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;country&quot;</span><span class="punctuation">:</span> <span class="string">&quot;China&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;countryCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CN&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;BJ&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;regionName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Beijing&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;city&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Beijing&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;zip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lat&quot;</span><span class="punctuation">:</span> <span class="number">23.22</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lon&quot;</span><span class="punctuation">:</span> <span class="number">21.222</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timezone&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Asia/Shanghai&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;isp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;China Unicom Beijing Province Network&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;org&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;as&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AS4808 China Unicom Beijing Province Network&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxxx&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><a href="https://checkip.amazonaws.com/">https://checkip.amazonaws.com/</a><br>123.113.111.178</p>]]></content>
    
    
    <summary type="html">汇总几个常用的查询公网 IP 地址的在线工具网站</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
    <category term="租房" scheme="https://blog.no-claw.com/tags/%E7%A7%9F%E6%88%BF/"/>
    
  </entry>
  
  <entry>
    <title>网络维修记录</title>
    <link href="https://blog.no-claw.com/posts/da62ad7a/"/>
    <id>https://blog.no-claw.com/posts/da62ad7a/</id>
    <published>2023-09-11T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一个租房的体验日记：</p><p>加了宽带师傅 B 微信，几天没有结果，经得知属于联通网络，遂工信部投诉，第二天联通打电话，告知他们 IP.cn 和 speedest 都显示是联通的 IP。联通师傅 A 一直在重复不是联通的网，然后安利办理宽带。询问是不是有二级运营商或者其他方式走联通的主线缆，师傅 A 还是在重复不是联通的网，而且谈话提到师傅 B 又还是岔开话题显然知道里面的猫腻，又开始重复不是联通的网让再办一条。无奈只能赶他出去了。</p><p>接线的也是人才，一根 8 芯的线硬生生给拆成了 4 根接 WAN，4 根接 LAN，网线的一头同时接 WAN 口和 LAN 口。</p><h3 id="交叉测试"><a href="#交叉测试" class="headerlink" title="交叉测试"></a>交叉测试</h3><ul><li>客厅原网线 + 原路由器 &#x3D; 80M</li><li>客厅新网线 + openwrt &#x3D; 200M+</li><li>客厅新网线 + 原路由器 &#x3D; 80M 左右（可以说明是百兆路由器<br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202309180951830.png"></li></ul><h3 id="成果"><a href="#成果" class="headerlink" title="成果"></a>成果</h3><p>从 4-80M 不等实现了全屋 200M 宽带覆盖，DHCP 看到连接成功。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202309180954005.png"></p><p>traceroute 结果如下，上边还是有个交换机，好像还是给包了一层，中间商做的 PPPoe。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/202309180952993.png"></p>]]></content>
    
    
    <summary type="html">记录家庭网络故障排查与维修的过程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
    <category term="租房" scheme="https://blog.no-claw.com/tags/%E7%A7%9F%E6%88%BF/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/tags/%E5%8C%97%E6%BC%82/"/>
    
    <category term="装修日记" scheme="https://blog.no-claw.com/tags/%E8%A3%85%E4%BF%AE%E6%97%A5%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>假装南方小土豆,哈尔滨一日游,再也不去了</title>
    <link href="https://blog.no-claw.com/posts/32308110/"/>
    <id>https://blog.no-claw.com/posts/32308110/</id>
    <published>2023-07-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>昨晚开会到 11 点，然后起个大早坐车去哈尔滨，预计的一个半小时实际有了两个小时，换乘地铁到市里再加上吃饭的时间就用了三小时。</p><p>在附近找了一个门店挺大的兰州牛肉面，面中规中矩，就是再来到这片区域，距离上一次，已过廿余年。</p><p>除了逛商场,还要写吐槽。</p><span id="more"></span><h3 id="餐饮篇"><a href="#餐饮篇" class="headerlink" title="餐饮篇"></a>餐饮篇</h3><p>性价比很高</p><ol><li>早餐兰州牛肉面(美团找的),煮好的时候大叔手抓的香菜,牛肉,配菜,萝卜. 当场跟表示注意卫生问题,只恨自己忘记带 pocket3</li><li>午饭特意小红书搜的,刘氏知音酒楼.网上评价很高.差不多锅包肉天花板的感觉.电话预约几乎没怎么排队(高德没找到预约的按钮)</li></ol><p>点餐服务员完全听不到,最后连着叫了三次,慢吞吞的回我,点的第一个菜就对不起没有.也没说明白原因.在我点餐过程中倒是很原因和别人聊天,有被冒犯…(菜品还行吧,性价比很不错,锅包肉带着 120 分期待来了,最后只能给到 95 吧,上来的时候外壳发软,不脆,觉得是锅包肉大忌,然后就是调的汁没有挂匀,萝卜啥的少的可怜,有的感觉在吃炸酥肉)。优点是量大味道纯,肉也多,不是靠面糊的技巧来少放肉的那种.对于餐饮的平时,大家都讲当地及格线是北京的天花板.</p><h3 id="吐槽篇"><a href="#吐槽篇" class="headerlink" title="吐槽篇"></a>吐槽篇</h3><p>感觉不缺游客,几乎没什么好态度</p><p><strong>Part1</strong>:大疆 (商场)</p><p>我:pocket3 现在还断货么</p><p>对方:没货</p><p>我;是不是今年就不会出 pocket4 了</p><p>对方:我不知道</p><p>我:这个无人机是新品吗</p><p>对方:不是</p><p>我:这个支持编程么</p><p>对方:不支持</p><p>不看无人机了,看相机</p><p>问:5pro 和 pocket3,拍 vlog 哪个好一点</p><p>答:看你场景</p><p>我:我知道我还问你???????</p><p>而且在店里走到哪里被跟在哪里,很不舒服</p><p><strong>Part2</strong>:小米店(商场)</p><p>不推销手里哪里好,就说买吧买吧 换个手机</p><p>我:我是 Apple 全家桶,有些功能没发用安卓</p><p>对方:airdrop 能,只有小米原生支持</p><p>我:还有随航和接力呢</p><p>对方:airdrop 能,只有小米原生支持</p><p>用他自己手机演示,当场翻车,一个文件都过不去</p><p>然后看到 iphone 上装了一个小米生态的助手…</p><p>问:这不是还需要额外安装软件么,也有很多三方软件能办到啊</p><p>答:三方如何如何…</p><p>(内心 OS):给你讲开源你也不懂啊….</p><hr><p>拿起一台小米手机,解锁屏幕两行 ssl 的 Exception 报错</p><p>对方:这个是商场的网不好导致的</p><p>………</p><p><strong>Part3</strong>:秋林-里道斯总店</p><p>由于刚去了秋林感觉可能被忽悠了,然后问一下这个是不是总店</p><p>只得到对方的一句,这不写着呢么</p><p>又问鲜酿的格瓦斯的规格,又被没好气的,指了个小牌子这写着呢</p><p>然后给接饮品的时候,把我这个暂停了跟别人聊天,然后聊完继续接,大姐你这单片机中断学的真好…</p><p>挺好喝的,打算邮寄点,但是现场接的没有塑封不确定会不会撒,然后问了一句,得到一句含糊不清的话,人走了 再也没见到过她…..</p><p>另一个展柜,</p><p>问 :秋林和秋林里道斯什么区别呢?</p><p>答:两家公司….</p><p>我知道人多你们忙,没有态度全是质量……</p><p>**Part4:**新一百楼下中央大街</p><p>问:怎么买马迭尔冰棍</p><p>对方手一指,答:那(nei)边</p><p>问:都有啥口味的呢?</p><p>对方手换个方向一指,答:那(nei)边.自己看</p><p><strong>Part5</strong>:中央大街 秋林-里道斯分店</p><p>鲜酿的格瓦斯没喝够(18 块的饮料,17.5 的冰镇和气)</p><p>到中央大街终于看到分店,到档口准备再卖一瓶,又不确定分店品质</p><p>大哥自顾自的把白桦树饮料摆在我面前的台子上,正当我以为他要说,喝点啥的话术的时候,然后他走了…..</p><p>我赶紧盯着他的店内包邮的牌子问多少钱包邮啊,,,没好气的回答 100 多….</p><p>大哥你这么做有生意么?</p><hr><p>想起了之前在北京吃的日料被怼够呛的经历(回去美团找了下评论 倒闭了哈哈哈)</p><p>我:帮忙拿双筷子</p><p>服务员:等会</p><p>我:xxx 菜还没上</p><p>服务员:我知(二声)道(拉长音~)</p><p>我:有没有哪个位置不吵的</p><p>服务员:没有不吵的(斩钉截铁)</p><p>差点喷国粹,大哥你东北的吧.</p><h3 id="红肠篇"><a href="#红肠篇" class="headerlink" title="红肠篇"></a>红肠篇</h3><p>高德导航到秋林食品总店,一个小屋子,还没客厅加上厨房大感觉,屋子里就我一个买货的,询问后不提供试吃,遂买了一根感觉味道不对..发干,而且还有黑点点(以前吃的咋没有),一直在 diss 商委</p><p>抓了路人(纯路人,交警,司机) 差不多都是当地人</p><p>有的推荐秋林总店,秋林商城,商委</p><p>路边的广告大屏幕啥的都是秋林里道斯,跟湖北宣传来菜似的.</p><p>秋林商城:地下一层,感觉各种小商贩的聚集地,小超市的感觉吧,毕竟挂着秋林的名头,但是红场看着就发红,经验感觉不靠谱…(自己标了伊雅红场)看介绍是创始人的名字</p><p>里道斯国际大厦(秋林里道斯):看人数这下应该对了,排的长队都去买散装然后自付两元打包装,说是流动快新鲜些,正常商品包装的几乎没啥人买,两者价格一样的….</p><p>两家都是自己始于 1900.百年老店:口径都是和对方是两家店</p><p>商委:炒的太火了,都有肠贩子了,据说需要排队.距离也挺远的,以后有机会再探吧,</p><h3 id="交通篇"><a href="#交通篇" class="headerlink" title="交通篇"></a>交通篇</h3><p>上车的时候用 apple watch 记录地图,下车消耗 290 卡…</p><p>地铁也很没素质哎,我去哪里排队然后就有人去哪里插到我前面或者是,占用下车区域..换了两三回 还碎碎念</p><p>回去的时候在路边等车，不是什么时候看到小情侣在亲亲我我，啊，这不就是现代的雨霖铃吗，总会升起一种莫名的反感，祝天下有情人终将分手吧。自古以来就没有什么非谁不可，饮食男女，人之大欲。在我这里，竟升起从未有过的陌生感。我会在旁边脑补他们吵架到分手，出轨，以及在原生家庭的冲突下意见不合，然后产生冷漠，偏见，以及厌恶。于是失去理智的歇斯底里，但是又不得不为了下一代继续苟延残喘。又仿佛这一切从未发生过。</p><hr><p>客车上写到这昏昏睡去, 梦醒来满脑子都是“刘彰暗弱无能.星夜奇袭成都,庞统百拜”, ”主公,张松是我出卖的.刘彰背信弃义,在主公退军路上设伏杀了你军师,主公这下师出有名了吧“</p><p>回去复盘这一天的糟糕经历, 再也不想去了.都是说来东北东北虎,我今天甚至都不用买门票.</p><p>啊,东北虎果然是形容词</p><p>不写了……省城都这样,那其他地方得啥样啊….</p>]]></content>
    
    
    <summary type="html">假装南方小土豆的哈尔滨一日游吐槽记录</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>消毒药水</title>
    <link href="https://blog.no-claw.com/posts/ff4866a6/"/>
    <id>https://blog.no-claw.com/posts/ff4866a6/</id>
    <published>2023-07-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="紅藥水"><a href="#紅藥水" class="headerlink" title="紅藥水"></a>紅藥水</h2><p>又稱紅汞，汞會讓蛋白質凝固沉澱，所以有抑菌的作用，因為對人體組織的傷害較小，曾經廣泛使用於局部皮膚創傷，是預防細菌感染最常用的表皮消毒藥水。</p><p>為何當初會拿來作為傷口處理用？大家小時候做生物實驗為了要看清楚細胞或是某些組織，會用染色劑把它們染色方便觀察。紅藥水就是這種作用，紅色顏料嘛！只不過對於細胞來說，染色劑會把細胞毒殺，所以被染過色的細胞，就等於是死了，同樣的道理，細菌本身也是細胞，所以當細菌被染色劑碰到時，也同樣會死亡。古時候醫療沒有很發達，就會使用比較容易取得的染色劑作為傷口殺菌之用。</p> <span id="more"></span><p>使用紅藥水除了要擔心會有汞殘留人體外，還會看到很明顯的紅色，造成傷口色素沈澱，會留下疤痕，另外蠶豆症的患者不可以使用。其實紅藥水抑菌作用微弱，治療效果差，現在幾乎沒有在使用了！</p><h2 id="紫藥水"><a href="#紫藥水" class="headerlink" title="紫藥水"></a>紫藥水</h2><p>主要成分是龍膽紫稀釋溶液，也可以叫結晶紫，聽到結晶紫是不是也勾起大家小時候做實驗的回憶？</p><p>用來分辨細菌的革蘭氏染色法裡面有個步驟就是使用結晶紫。因爲它的陽離子能和細菌蛋白質的羧基結合，影響代謝而產生抑菌作用。它能抑制革蘭氏陽性菌，特別是葡萄球菌，對白色念珠菌也有較好的抗菌作用。外用可治療皮膚與粘膜的創傷感染及潰瘍、小面積的燙傷、鵝口瘡等。</p><p>不過傷口感染化膿時，不適合使用紫藥水，因爲它有收斂作用，會在傷口表面形成一層痂膜，讓壞死組織中的膿難以排出而向深部擴散，加重感染。紫藥水會使皮膚殘留紫色痕跡，脫色不易，所以對較大面積的傷口，盡量避免使用。而且跟紅藥水一樣，蠶豆症的患者也是不可以使用，否則會引發急性溶血反應。</p><p>值得一提的是，因為紫藥水擁有潛在的致癌性，所以現在醫療機構已不用紫藥水來消毒傷口。</p><h2 id="黃藥水"><a href="#黃藥水" class="headerlink" title="黃藥水"></a>黃藥水</h2><p>成分為Acrinol，也是常見的外用消毒劑。它跟紫藥水一樣，陽離子能和細菌蛋白質的羧基結合，影響代謝而產生抑菌作用。</p><p>黃藥水同樣也會染色，不過因為亞洲人皮膚是黃色，加上黃藥水的著色力不比剛剛講的紅藥水和紫藥水強，所以比較容易清除。它的特點是，抗菌效力不受膿血蛋白質影響，所以可以用來治療化膿性傷口。</p><p>要注意的是，黃藥水不適用在大於兩手掌面積的傷口或是深部感染傷口。不過因為他的抗菌效果沒有很好，現在也很少使用了。</p><h2 id="白藥水"><a href="#白藥水" class="headerlink" title="白藥水"></a>白藥水</h2><p>雖然叫白藥水，但其實不是白色，而是一種透明液體，主要成份是陽離子殺菌劑benzakonium加上局部麻醉劑(Dibucaine Hydrochloride)、血管收縮劑(Naphazoline Hydrochloride)、抗組織胺(Chlorpheniramine Maleate)。應用原理為破壞微生物的細胞膜，除了對抗細菌之外，對真菌也有效。白藥水的特色是它沒有顏色，所以對於擔心傷口被染色劑染色或是衣服被優碘染到顏色的人來說，白藥水算是一個很好的選擇。</p><p>白藥水因為添加了局部麻醉劑，所以有止痛的效果，跟擦優碘或是雙氧水比起來，舒服多了。裡面添加的抗組織胺對於減輕局部組織的腫脹及組織液的滲出有不錯的功效，也有止癢的效果。<br>但是在這邊要提醒各位，如果你的傷口已經傷到了真皮或是皮下組織，建議不要使用白藥水作為傷口處理之用，因為血管收縮劑有傷口收斂的效果，會讓你的傷口更容易產生包覆作用，會讓傷勢惡化在裡面，所以白藥水還是只適合表淺的傷口護理。</p><h2 id="雙氧水"><a href="#雙氧水" class="headerlink" title="雙氧水"></a>雙氧水</h2><p>又叫過氧化氫水溶液。小時候我賽跑跌倒，手肘磨破皮，傷口看起來髒髒的，回家媽媽拿醫藥箱的雙氧水幫我消毒。真的很痛！我邊看傷口冒泡泡邊哭！媽媽還會說『哭什麼？誰叫你要不小心跌倒？！』這種狠話也只有家人才說得出口。</p><p>為什麼我的傷口會冒泡泡？其實雙氧水可以殺菌，是利用它接觸傷口發生氧化反應釋放出氧氣的原理。</p><p>雙氧水可以分解血液中的酵素，除去血漬，但其穿透力差，殺菌時效短暫，用在深的傷口，容易造成蛋白質變性，組織壞死，傷口反而不易復原。一般只適合在出血凝塊的表淺傷口處理。目前也不建議將雙氧水直接用作傷口清理之用，因為對皮膚過於刺激，容易傷害到表皮細胞。</p><h2 id="優碘"><a href="#優碘" class="headerlink" title="優碘"></a>優碘</h2><p>優碘呈深褐色，殺菌效果強，可以在20~30秒內殺死病毒、細菌、黴菌、結核菌、原蟲等，為廣效型的殺菌劑，更有預防傷口化膿的功效，而且容易清洗、溫和不刺激，不過塗在傷口在還是會痛啦！優碘是目前使用最廣泛的消毒藥水。優碘會留下黃褐色顏色，容易使皮膚在日曬後形成色斑，頸部以上部分傷口使用時要多加考慮。</p><p>不過大家不要誤會，其實優碘並不會造成傷口的色素沉澱，剛剛提到了曬太陽之後會產生色斑，主要是因為陽光照射之後，容易形成紫黑色的固態碘，外觀上會很像色素沉澱所以會引起誤會。</p><p>當優碘與傷口組織接觸時，會形成薄膜，然後慢慢釋出碘來達到殺菌效果。但值得注意的一點是，此薄膜通常有點黏黏的，如果傷口較大，大多數人會再覆蓋一層紗布，結果優碘形成的薄膜加上組織液與紗布黏在一起，下次換藥時拆紗布的時候相信很多人都有經驗，絕對痛到你嘴歪眼斜！所以建議塗完優碘後，可以在傷口上蓋上石蠟紗布(如下圖)來降低傷口的沾黏。</p><p>今天跟大家分享這麼多消毒藥水，簡單來說，紅、紫、黃藥水目前已經不建議使用，雙氧水如果要用的話，也只適用在第一次消毒傷口，不適合持續使用 ; 頭頸部的表淺傷口可以使用白藥水避免<a href="https://www.youtube.com/watch?v=GFCfjCO2-ss&t=549s">色素沈澱</a> ; 身體跟四肢的傷口可以考慮使用優碘。</p><p>![[Pasted image 20230702120450.png]]</p><p>當然要告訴大家很重要的一點，就是雖然居家常備這些藥水，可以處理小型的傷口，非常方便，但如果過了幾天，傷口都沒好轉的跡象，甚至愈來愈紅，愈來愈痛，就要趕快去看皮膚科，畢竟傷口的照顧是一門很大的學問，它還牽涉到用什麼去覆蓋，要不要擦抗生素藥膏，又要擦哪一種抗生慌藥膏，幾天換藥一次，需不要縫合，要不要貼人工皮……等等很多問題，來防止疤痕的形成，這些都不光是擦個藥水就能解決這麼簡單。</p>]]></content>
    
    
    <summary type="html">常见消毒药水的种类、成分与使用场景科普</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>好叶视频笔记</title>
    <link href="https://blog.no-claw.com/posts/7966bd76/"/>
    <id>https://blog.no-claw.com/posts/7966bd76/</id>
    <published>2023-07-01T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li><p>有明确目标，加速达成规划</p></li><li><p>目标不会不变（喜欢做的&#x2F;擅长的）、</p></li><li><p>成功不需要天赋（结合自己的技能&#x2F;技能优劣分类，找出 5 个）</p></li><li><p>好点子+简单策略，成就&#x3D;潜能&#x2F;方向（更高目标+要事优先+其他想法记录暂缓+成功健康共存），其他选择 say no</p></li><li><p>热情源自于成功，毁于挫败和标签。</p></li><li><p>创造热情的方法（创造，抢新潮流，整合自己的技能点）</p></li><li><p>悲观和负面评价只有悲观的结果（习得性无助）、</p></li><li><p>ABCDE（Adversity 挫折，Belief 信念，Consequence 后果，Disputation 争辩，Energization 获得能量）</p></li><li><p>不要在乎别人的负面评价。讨好别人只会碌碌无为，屈服于这个时代的暴力。</p></li><li><p>正面思维加强做事的活力，加速学东西，悲观思维会否定可能性。</p></li><li><p>正面思维的七个方法：</p><ol><li>观察想法</li><li>感恩日记</li><li>重塑思维（怎么可以达到</li><li>正面想法</li><li>给别人正面想法</li><li>注意想法：放弃受害者思维，清空不幸的想法</li><li>避免负面环境</li></ol></li><li><p>无法致富的四个观念（钱的罪恶感，对别人的话太敏感，保持现状，不愿意先付出，企业需要三五年后或者 10 年）</p></li><li><p>复利思维，每天进步一点点</p><ol><li>小事百分百对自己负责，完成小行动</li><li>做事会吸引相似的人和事</li><li>成为什么样的人有没有这个习惯，不习惯有没有这个习惯，习惯和核心价值观一致吗，习惯合理吗？</li></ol></li><li><p>视觉化和肯定</p><ol><li>视觉化过程而不是结果</li><li>联系自我肯定</li><li>不切实际的幻想可能反效果</li></ol></li><li><p>二八定律：20%的努力产生 80%的结果，外包 80%可替代的事情</p></li><li><p>帕金森定律：有限时间内占用时间会膨胀（限制&#x3D;高效）任务期限前移一半，尊重承诺，只做少数事情</p></li><li><p>恢复能量的方法：</p><ol><li><p>精力管理：</p><ol><li>间歇性休息：（52&#x2F;17）</li><li>睡眠：每天 7-8H 或者午休 20min</li><li>甜食水果补充损失的意志力</li><li>运动加强专注力和记忆力，决定意志力</li></ol></li><li><p>操控意志力，给意志力奖励，自我对话激励意志力</p></li><li><p>记录微小决定待会再做：为大事保存能量</p></li><li><p>if -then 机制：减少决策损耗的能量</p></li><li><p>控制工作环境，为工作做铺垫（霍桑效应：被监视的时候更有生产力</p></li></ol></li><li><p>5 倍提升法：</p><ol><li>关闭手机通知，控制手机（6 秒钟看手机，等于损失 25min 高效时间</li><li>隐藏工作关的 APP（app 放在不容易看见的地方</li><li>设置信息时间段（特定时间接收消息</li></ol></li><li><p>每天的能量循环：90&#x2F;20 间歇：90 分钟深度工作，20 分钟休息，根据自身适当调节</p></li></ol>]]></content>
    
    
    <summary type="html">好叶视频笔记整理，关于目标设定、技能发掘和热情来源的思考。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="效率" scheme="https://blog.no-claw.com/tags/%E6%95%88%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>小米4A刷OpenWrt</title>
    <link href="https://blog.no-claw.com/posts/63a7a8fe/"/>
    <id>https://blog.no-claw.com/posts/63a7a8fe/</id>
    <published>2023-06-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近由于 ipad 随航经常卡断，导致体验直线下降，有如下几个怀疑：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/7796030c5adad4e258b8a5c644fc048b17f87ff1/202307221242310.png"></p><ol><li>小米路由器偷工减料无线功能不行</li><li>电脑带不动</li><li>升级了 MacOS13</li></ol><p>就说咱对于国产的偏见，以及小米路由器频繁抽风挂后台，还有小米的生态做的稀烂，在和客服人员沟通后得到了模棱两可十分不专业的回答之后下定决心把手边的小米 4A 刷成 OpenWrt，毕竟对于爱好者而言，客服就是半路出家的半吊子，毕竟解决不了问题咱就迁走嘛。本身是资深软路由用户，刷机也没啥成本。</p><span id="more"></span><h3 id="刷前提醒"><a href="#刷前提醒" class="headerlink" title="刷前提醒"></a>刷前提醒</h3><ol><li>不要偷懒不刷 breed，否则刷崩了就只能救砖</li><li>救砖只能用小米自己的软件，毕竟封装的太多了，能用开源就用开源，尽量屏蔽厂家这一层</li><li>可以找下国内魔改的 Rom，虽然官方有小米的适配，但是刷完默认 Wi-Fi，还有过几十分钟 5G Wi-Fi 变成公开的问题。</li><li>还是建议使用有线刷机，无线的话无法刷入 Breed</li></ol><p>官方的地址，看看就好</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398595329-b000d4a6-9e2f-4009-af80-a4b7a3ba9358.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398645873-c8085af8-9502-4f8d-a722-bee12bb6e886.png"></p><h3 id="救砖"><a href="#救砖" class="headerlink" title="救砖"></a>救砖</h3><p>刷机之前还是很有必要先说下救砖，只能用到小米的官方工具，而且只有 Windows 版本，所有这里需要准备一台 windows 带网口的设备。</p><p><a href="https://github.com/Xu-Hardy/object-storage/blob/master/mi4A/MIWIFIRepairTool.x86.zip">MIWIFIRepairTool.x86.zip(980 kB)</a></p><p>用网线把电脑网口和路由器的 Lan 口连在一起，这样在刷机工具中会看到电脑有一张网卡被路由器 DHCP 分配了 IP，192.168.31.X, 此时无法登陆 web 后台。这里建议在打开软件之前先执行这个刷机步骤：</p><p>1 断开电源，按住 Rest 键，再接通电源，直到橙灯闪烁再松开<br>2 打开刷机软件，这里回识别一会网卡<br>3 等待路由器恢复，完成之后指示灯会变成蓝色<br>4 然后就可以通过 192.168.31.1 进入后台界面了</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687396836642-b7c73690-1764-4448-9f9b-a40200965113.png"></p><p>这个有个小插曲：我一开始在刷机最后一步断电重启的路由器，试了两三次均不能刷入，论坛上也建议不成功的话多刷几次。没有出现需要降级路由器固件的情况。</p><h3 id="解锁-SSH"><a href="#解锁-SSH" class="headerlink" title="解锁 SSH"></a>解锁 SSH</h3><p>网上提供了一个 Python 脚本来解锁路由器的 telnet 和 SSH，然后会预制一个 Buybox 的环境，其实就是一个 Linux 的工具箱环境，我们可以执行 curl，wget 等命令。telnet 的用户名和密码都是 root。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398788572-5d8abf15-3fca-4755-8572-c6a19f2b83b5.png"></p><p>SSH 会遇到这个错误，所以用 telnet 了：</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398779870-89ff20b4-f71e-4734-9383-77af19f6881b.png"></p><p>本地可以用 Python 启动一个文件服务器方便路由器下载文件，用 SCP，FTP 啥的也行</p><p>可以用浏览器访问 IP:8000 来访问文件服务器，然后 wget 提取文件链接。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687399002714-ed5649b9-0d25-451a-a028-2e404d8dbba2.png"></p><h3 id="刷入-Breed"><a href="#刷入-Breed" class="headerlink" title="刷入 Breed"></a>刷入 Breed</h3><p>Breed 更像是 BIOS 一类的东西，每次 Reset 路由器之后，就会进入到 breed 后台，IP 地址是 192.168.1.1，如果进不去后台可以把 IP 改为 C 类 IP 地址的网段，然后就可以进到 Breed 后台来输入 OpenWrt 固件了。</p><p><a href="https://github.com/Xu-Hardy/object-storage/blob/master/mi4A/breed-mt7621-pbr-m1.bin.zip">breed-mt7621-pbr-m1.bin.zip(95 kB)</a></p><p>路由器没啥空间了，所以都要放在&#x2F;tmp 文件夹下，把链接换成自己文件服务器的链接。</p><ol><li>进入到临时目录:cd &#x2F;tmp.</li><li>下载”breed”:curl <a href="https://breed.hackpascal.net/breed-mt7621-pbr-m1.bin">https://breed.hackpascal.net/breed-mt7621-pbr-m1.bin</a> –output firmware.bin</li><li>执行命令:mtd -r write &#x2F;tmp&#x2F;firmware.bin Bootloader</li><li>重启后浏览器进入”Breed Web 恢复控制台”:192.168.1.1</li></ol><p><a href="https://github.com/Xu-Hardy/object-storage/blob/master/mi4A/v2.5.29%20%E5%B0%8F%E7%B1%B34A%E5%8D%83%E5%85%86%E7%89%88OpenWrt%E5%9B%BA%E4%BB%B6.zip">v2.5.29 小米 4A 千兆版 OpenWrt 固件.zip(39.8 MB)</a></p><p>Breed 的下载页面：<a href="https://breed.hackpascal.net/">https://breed.hackpascal.net/</a></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398443827-03bc7ce7-64b4-4b50-bf36-578511a97050.png"></p><p>备份分区</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687399872176-e43aa92f-9b52-46f2-8cfb-38c6c1eb7ec7.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687399891497-b7c2cdee-66ec-452b-a1b1-cda4a48ac2e8.png"></p><p>我这里先刷的 openwrt-ramips-mt7621-xiaomi_mi-router-4a-gigabit-initramfs-kernel.bin，其实已经可以进入路由器后台使用了，然后刷到 sysupgrade 镜像，其实应该在 OP 后台刷这个 sysupgrade 镜像。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687400148704-316d570e-18d2-4a0d-8abe-037d8b4b951c.png"></p><h3 id="刷入-openwrt"><a href="#刷入-openwrt" class="headerlink" title="刷入 openwrt"></a>刷入 openwrt</h3><p>刷完之后，路由器会自动重启，此时 telnet 会断开。<br>把 IP 地址改成自动获取，这个时候可以进入到路由器后台了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687397739475-03de7ad1-31ab-40bb-94c3-ce9ab6ac47b2.png"></p><p>设置完无线的用户名密码之后有个很奇怪的问题，WI-FI 一直没有网，但是有线正常，后来在 Network 中删除掉 Wan6 的 DHCP Set 就好了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687397990787-de16dd97-0416-41b6-baa1-3e9f2bbdc87c.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398060605-5b0905a5-b4ee-47a6-abcc-06e36753587a.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398216746-31cf48bf-e37c-4fa5-b837-417dc3ce7e36.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/1687398712888-c3963fc7-9ea7-4db7-9554-0de75c2547c9.png"></p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://www.right.com.cn/forum/thread-4317222-1-1.html">https://www.right.com.cn/forum/thread-4317222-1-1.html</a><br><a href="https://blog.51cto.com/xfxuezhang/5866060">https://blog.51cto.com/xfxuezhang/5866060</a></p>]]></content>
    
    
    <summary type="html">小米路由器 4A 刷入 OpenWrt 固件的完整教程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="路由器" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
    
    <category term="路由器" scheme="https://blog.no-claw.com/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>血型</title>
    <link href="https://blog.no-claw.com/posts/d14ad726/"/>
    <id>https://blog.no-claw.com/posts/d14ad726/</id>
    <published>2023-05-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>血型是一种遗传特性，主要由 ABO 系统和 Rh 系统两个主要系统决定。</p><p>ABO 血型系统：这是最重要的血型系统，包括 A型、B型、AB型和O型四种血型。A型血液具有A型抗原，B型血液具有B型抗原，AB型血液具有A型和B型抗原，而O型血液则没有这两种抗原。</p><p>Rh 血型系统：Rh 血型系统是在 ABO 血型系统之后最重要的血型系统。最主要的是 Rh(D)抗原，它决定了一个人是 Rh阳性（Rh+）还是 Rh阴性（Rh-）。因此，我们常常会听到血型被描述为 A+ 或 O- 这样的形式，其中的 “+” 或 “-“ 就是指的 Rh 血型。<br> <span id="more"></span><br>所以，总共有八种血型：A+、A-、B+、B-、AB+、AB-、O+ 和 O-。</p><p>ABO 血型系统是一种基于人体红细胞上存在的抗原类型的分类系统。在 ABO 系统中，主要有 A 型抗原、B 型抗原和没有这两种抗原的 O 类型。</p><p>以下是具体的细节：</p><p>A型血：有 A 型抗原，体内产生抗 B 抗体<br>B型血：有 B 型抗原，体内产生抗 A 抗体<br>AB型血：有 A 型和 B 型抗原，体内不产生抗 A 或抗 B 抗体<br>O型血：没有 A 型或 B 型抗原，体内产生抗 A 和抗 B 抗体<br>关于父母血型和孩子血型的关系，因为 ABO 血型是由两个等位基因决定的，一个从母亲那里继承，一个从父亲那里继承。A 和 B 两个等位基因是显性的，而 O 等位基因是隐性的。所以：</p><p>如果父母的血型都是 A 型，那么孩子可能是 A 型或 O 型（假设其中一个或两个父母携带了 O 基因）。<br>如果父母的血型都是 B 型，那么孩子可能是 B 型或 O 型（假设其中一个或两个父母携带了 O 基因）。<br>如果父母的血型都是 O 型，那么孩子只能是 O 型。<br>如果父母的血型是 A 型和 B 型，那么孩子可能是 A 型、B 型、AB 型或 O 型（假设其中一个或两个父母携带了 O 基因）。<br>如果父母的血型是 A 型和 O 型，那么孩子可能是 A 型或 O 型。<br>如果父母的血型是 B 型和 O 型，那么孩子可能是 B 型或 O 型。<br>如果父母的血型是 AB 型和 O 型，那么孩子可能是 A 型或 B 型。<br>如果父母的血型是 AB 型和 A 型，那么孩子可能是 A 型或 AB 型。<br>如果父母的血型是 AB 型和 B 型，那么孩子可能是 B 型或 AB 型。<br>如果父母的血型是 AB 型，那么孩子可能是 A 型、B 型或 AB 型。</p><p>不同血型的人接受输血时，必须确保血型相容，否则可能产生严重甚至致命的免疫反应。例如，B型血的人如果接受A型血的输血，他们体内的抗-A抗体会攻击输血中的A抗原，导致血液凝块、器官损伤等问题。</p><p>理解 ABO 血型遗传关系的关键是知道 A 和 B 基因是显性的，而 O 基因是隐性的。这意味着如果一个人从父母那里获得了 A 或 B 基因和 O 基因，那么 A 或 B 基因将显性表达，而 O 基因将隐性表达。</p><p>用这个理念，我们可以对血型遗传关系进行一些简化：</p><ol><li><p>如果两个 O 型血的父母（OO 和 OO），他们的孩子只能是 O 型血。</p></li><li><p>如果两个 A 型血的父母（可能是 AA 或 AO），他们的孩子可能是 A 型或 O 型血。</p></li><li><p>如果两个 B 型血的父母（可能是 BB 或 BO），他们的孩子可能是 B 型或 O 型血。</p></li><li><p>如果两个 AB 型血的父母（AB 和 AB），他们的孩子可能是 A 型、B 型或 AB 型血。</p></li><li><p>如果一个 A 型血的父母和一个 B 型血的父母（可能是 AA, AO, BB 或 BO），他们的孩子可能是任何血型。</p></li><li><p>如果一个 AB 型血的父母和一个 O 型血的父母（AB 和 OO），他们的孩子可能是 A 型或 B 型血。</p></li></ol><p>以上简化可能没有涵盖所有的情况，但基本上可以解释大多数情况的遗传模式。请注意，这个遗传模式是理想的，现实中可能存在一些偏差。</p><p>如果你想更详细地理解每种可能的遗传模式，我推荐你使用 Punnett 方格。Punnett 方格是一种在生物学中用于理解和预测遗传模式的工具。在这个方格中，你可以表示每个父母的基因型（例如，A 型血的父母可能是 AA 或 AO），然后看看每种可能的组合。</p><p>此外，血型也在妊娠中起着重要的角色，特别是母亲是Rh-而胎儿是Rh+时，可能会发生Rh不兼容，导致新生儿溶血性疾病。这种情况可以通过在怀孕期间给予母亲抗-Rh(D)免疫球蛋白（Rho(D) immune globulin）预防。</p><p>血型在医学、法医学和人类遗传学研究中都很重要。不过，关于血型和性格、饮食偏好等的关联的说法，尚无科学依据。</p>]]></content>
    
    
    <summary type="html">血型基础知识科普，了解 ABO 与 Rh 血型系统</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>MacOS干扰那些事</title>
    <link href="https://blog.no-claw.com/posts/9afa2bcc/"/>
    <id>https://blog.no-claw.com/posts/9afa2bcc/</id>
    <published>2023-05-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="随航掉帧"><a href="#随航掉帧" class="headerlink" title="随航掉帧"></a>随航掉帧</h2><p><a href="https://discussionschinese.apple.com/thread/253757661">https://discussionschinese.apple.com/thread/253757661</a></p><p>直接查有线之后，速度有很大改进，原来不是随航掉帧，是路由器不行。</p><span id="more"></span><h2 id="airpod-pro-和蓝牙设备串扰"><a href="#airpod-pro-和蓝牙设备串扰" class="headerlink" title="airpod pro 和蓝牙设备串扰"></a>airpod pro 和蓝牙设备串扰</h2><p>2.4G 干扰<br><a href="https://support.apple.com/zh-cn/HT201542">https://support.apple.com/zh-cn/HT201542</a><br>没辙，换 homepod 吧，起码 homepod 还真的不错</p>]]></content>
    
    
    <summary type="html">macOS 随航、隔空投送等功能的干扰问题与解决方法。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="苹果" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%8B%B9%E6%9E%9C/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>地坛游记</title>
    <link href="https://blog.no-claw.com/posts/bb8b63b3/"/>
    <id>https://blog.no-claw.com/posts/bb8b63b3/</id>
    <published>2023-04-28T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>我总会在受伤后行动不便的日子想起史铁生。第一次是拄着拐杖，第二次是十年后坐上轮椅的时候。在此期间，我曾无数次羡慕在这京城中长大的孩子，仿佛从书中描述的都是另外的一个世界，一直持续到我来到北京工作，幸运的是工作和居住都在四环附近，大多的名胜古迹只需要一小时左右的车程。市内有很多前朝的遗址，比如元大都遗址公园，甚至明清时候的九宫八庙，自然也能发现一些陈年趣事， 比如光绪帝用自来水比我早了整一百年这种半封建半现代的事情。</p><p>东直门吃过午饭后，沿着安定路旁边的河流一路骑行，慢悠悠的来到地坛的南门。马路对面就是雍和宫，经常有无数善男信女来此求取姻缘。再往南就是北京很有名的小吃簋街，这附近总是热热闹闹的，丝毫找不到小说中“这院子无人打理”，“杂草从生”的痕迹，园内也是各色人等，还在襁褓中的孩子，放假休息的学生，一边游走一边电话的成年人，还有三两成群过来散步的老人。</p><span id="more"></span><p>沿着地坛外侧砖路散步，自然是脚本慢下来不在乎时间的流逝，一方面是为了找寻小说中提到的地点，另一方面也是想要亲眼见证石门下被照的透亮的坎坷。出门时候尚未规划好行程，又临时起意来到这园子，打算看这里的日落，是否会如湖面一样拖起长长的影子。参观祭坛要额外买票，在我从前去过的许多园林之中，祭坛多被虚掩起来，虽然现在早已不再从事祭祀的活动，所幸遗迹被肉眼可见的保留了下来。</p><p>正午的方泽坛，我装做皇帝一样走向高台，穿过外墙进入内墙，然后一步步的迈过阶梯，前面有人拍照也丝毫不去避讳。登上祭祀台，已经简化了很多东西，目前的摆设是遵循1750年乾隆时代的摆设。方泽坛对面是皇祇室，大堂的中央摆放着皇地灵位，左右内侧摆放着清代卓越贡献的几代君主的灵位，从介绍得知，他们的肉身葬在我不曾听过的地方，左右外侧供奉五岳、五镇、四海、四渎、五陵山神，而皇陵大概率是龙脉所在吧，惭愧对大清历史不求甚解，也不想亵渎前人再去抄送。破旧残缺的青砖总会让人想起故宫，甚至是影视剧午门问斩血溅五步的场景。祭坛低矮墙壁的角落里，有人在练习拉伸，有人刚刚把刀从鞘中抽出来，一时无法分辨刀是真品还是高仿，加上这人的穿着，颇有反清复明吕四娘的装扮。假期里来参观的人自然不少，父亲给孩子讲解着祭祀的斋戒礼教，男人躲在角落打电话谈着生意，小夫妻在拌嘴，走马观花后觉得不该来此，还有三五成群在这野餐拍照留念。我呢，在进门之前摘下耳机，登上这祭祀的高台，甚至已经想好了焚香祷告，烹羊宰牛的画面。殿中前现存的大多是复制品，确实无法像真品那样让人怦然心动。</p><p>去年在故宫大殿参观的时候是有真迹的，我鼓足勇气与石像对视，这石像经历了战国争雄，五代十国，以及数不清楚的动乱时候仍然总算完好的保存了下来，过去的创造他的人早已深埋黄土，几百甚至几千后它还会伫立在这里，变的只是一批有一批的瞻仰者。若不是空气中弥漫的腥臭的味道和铭牌上距今几百上千年的字眼过于突兀，我会沉浸在无尽的历史长河中久久不能回神。还有在东直门某博物馆看见的函谷关的瓦当，不得不又回溯起楚汉争入关中那段荡气回肠的历史。</p><p>从祭坛出来后，继续播放《我与地坛》的有声读物，重游重温一遍内容，新鲜的风景和记忆里尘封的旧的内容交织融合。他是后天的残疾，最后也没有看透生死，只得出来生没得选死不必急于求成的结论。倘若生下来从未体会过奔跑的感觉，那么便也只能怨天尤人，要是他一蹶不振不再写作，那就是另外的故事了。这是否又是今生来体验人间苦难的下凡？不管怎么样，我从不把这种这当作励志的故事。</p><p>于是我开始去设想从前的园子，大小应该不会有很大的变化。但西北口钟的那十分破旧的那一口钟现在已经无从得见，到处的荒野杂草野路被修缮成了整齐划一的灰砖，不变的是，倒还能在路边看见近乎晒干的了蚯蚓。尽管现在重修了钟楼，封闭了马厩，同时又加上了诸多娱乐设施。只是现代的气息太浓，若来追忆经典定会觉得索然无味。还好能找见几棵400年前明代的古树，不远处还有一个妇女在教孩子聆听树的声音。上了年纪的树自然是被铁圈围起来的，让人不自觉就想起摇曳尾于涂的典故了。而古树的枝条上，总会紫藤会围着这样的老树生长，交织和抱，开出淡淡的小花，引得路人争相过来拍照。园子北侧翻修的太彻底，丝毫没有小说中的痕迹，我尝试在树的附近寻找史铁生车轮的痕迹，当前正如他所说，这园子正在被不懂它的人精心雕琢。现在每个公开场所都有所谓的商业模式，这点798为首的文艺社群中尤为突出，自然地坛也无法幸免。来地坛之前，在想象是多么幽静的环境，比人高的杂草，以及东南角那高亢嘹亮的声音，每天从北到南从南往北的女工程师。现实却是丝毫没有桃花源的感觉，似乎成为人们茶余饭后来消遣的场所了，又不由得感叹现代人肤浅的享乐简直来的太过容易，无论每个公园都会有游乐设施在大煞风景，还有不知道是哪个年代的流行歌曲一直响个不停。思绪拉回现在，老人们在吹奏手风琴还有小号，另外一旁还有正在进行的门球的赛场，倒是孩子们在无所顾及的疯跑嬉闹。</p><p>但，对于这园子的一年四季，我总是无暇来观望的，就连楼下的后花园也很少去宠幸。除了文中提到的雨燕外，还有喜鹊，乌鸦，鸽子这些鸟类，算是为数不多超出作者的笔墨吧。倒是最近每年清明都去龙潭湖散步，看鸭子高飞，鸳鸯戏水。大概是距离首都图书馆比较近的缘故吧，常常是在潘家园的二手市场中恣意寻找后骑车到方庄。但别人用过的东西从风水上讲毕竟还会残留上一个人的精气，还无法完全接受。</p><p>在我那密不透风的学生时代，总觉得许多异乡游记是和三皇五帝一样传说的故事，而我却生在一个山高皇帝远的地方，守着些不为外人道的自然风光。甚至我在工作以后才有机会到一些不算太远的旧址吊唁。比如渤海湾曾经甲午海战的地方，东鸡冠山上到处是日俄战争枪林弹雨的痕迹，就连误入的墓群主人，也分不清到底是旧时的未亡人还是几年十几年甚至几十年前疲于奔命的过劳者，甚至脚踏的这片四方黄土就是乃木宝典战死之处，还有作为标志性的建筑的弹壳，我甚至不愿想象战争年代白发人送黑发人情感被上司大如天的威严严苛所淹没，死后被历史记住已经是最大了补偿了。大连是到处都有墓碑的，在每一个所谓现代定义的景点公园，凡事对外的，总会在一不留神间瞧见墓碑或者墓群就在不远处。中日之间是有宿命的姻缘的，这点在那些发生过大规模屠杀战争的地方尤为严重。大连的每一片土地都渗着鲜血，有革命先烈的，有当代年轻人的。当地的大户说，采集百年弱冠的血，用温热的殷红把混着泥土的陈年凝块化开，喝了可以长生不老。</p><p>故宫是溥仪的家，地坛也是史铁生的后花园。至于龙潭湖附近，长眠着一位《碧血剑》中的故人，功高盖主而殁于崇禎。两次拜会，第一次因为疫情，第二次由于天色渐晚行到庙前又不忍打扰。倒不是有多喜欢古迹，除了闲逛外，多半是朋友邀约或泛读诗书自然想去凭吊一番。</p><p>十九点按时日落，大概是因为现在楼宇太多的缘故吧，守了一个下午，从中午等晚上，一直到路灯亮起，也没有在祭坛的石门前瞥见落日的余晖。索性来到簋街，在胖妹面庄的胡同前排起长长的队，再顺手把这些记录下来。</p><p>2023年4月29日于地坛</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/naimukdehun.jpg" alt="地坛西门"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/jiepian.jpg" alt="街边写文"></p>]]></content>
    
    
    <summary type="html">重游地坛，边听《我与地坛》有声书边寻找史铁生笔下的痕迹，古树、祭坛与现代公园的交织。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="游记" scheme="https://blog.no-claw.com/tags/%E6%B8%B8%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>雨</title>
    <link href="https://blog.no-claw.com/posts/13ca4a36/"/>
    <id>https://blog.no-claw.com/posts/13ca4a36/</id>
    <published>2023-04-27T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>小时候，<br>雨是村头泥泞的路口，<br>墙角湿滑跌在原地，<br>不穿雨靴，不披雨衣，<br>一把天堂伞默默撑到彼岸。</p><p>上学后，<br>雨是城中疾驰的孩子，<br>结队成群疾行风中<br>前有单车，后有乌云，<br>一盏日光灯悄悄驱散阴霾。</p><p>工作了，<br>雨是都市湿漉的地板，<br>半干半湿透着氤氲，<br>昨夜雨疏，今夕风骤，<br>一个外乡人匆匆留念海棠。</p><p>其实呢，<br>雨该是镜湖踉跄的渔夫，<br>驿外断桥咏叹春梅。<br>清者濯缨，浊者濯足，<br>一代文化人静静独留风骨。</p>]]></content>
    
    
    <summary type="html">一首关于雨的诗，从村头泥路到都市地板，记录不同人生阶段与雨的交集。</summary>
    
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>深夜随笔</title>
    <link href="https://blog.no-claw.com/posts/fb8d6bf1/"/>
    <id>https://blog.no-claw.com/posts/fb8d6bf1/</id>
    <published>2023-04-02T15:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>要说从读书开始伊始到大学毕业，最大的收获是什么。大概是独立之精神，自由之思想。通俗的来讲就是官逼民反，在一直听话的过程中一直无法满足期望与要求，厌倦了君臣父子的这种病态。从那时候起，开始很多的不相信，文化，教育，以及文凭。中国人喜欢毫无意义的卷，仿佛比别人多努力一点就能多得一些，长此以往，自然而然催生了大多数精致的利己主义者，以及优秀的绵羊。</p><span id="more"></span><p>从能力或者文凭来看，中国人更加趋近于文凭，一部分是为了面子，在邻里之间吹嘘，好似现代版读书人的事叫盗而不叫偷，另一部分是为了尽可能的避免别人像被挑白菜扔来拣去。<!--more-->做营销的人总会有一套，先给你制造各种各样的焦虑，然后宣称自己有灵丹妙药。从当今互联网的招聘状态来看，颇有捡芝麻丢西瓜的意味。从某种程度来讲，我们所谓的快乐式教育已经荡然无存，家长一方面希望孩子能一个快乐的童年，而另一方自己有深陷漩涡，希望下一代续写昨日的故事。所以，这样的单纯，善良最后就都变成了无知与不作为残害的牺牲品。甚至不知道我们为了什么以及为什么要这么做。</p><p>喜欢看武侠，风清扬说最厉害的招式不在武功之中，而是阴谋陷阱。这句话在这里也同样适用现在的教育，资源在少数人手中，算是寒门再难出贵子的境况。暂且不论这样的教育弊端如何，但在传统儒家的观念里的尊师重道仍然被保留了下来，但是却不会有对于道德的一些标准。于是我们常常在讲，是招一个道德好但是能力一般的人还是没什么道德但是专业能力很强的人？大概是从陈平开始，从举孝廉到任人唯能，所以汉王灭楚，魏武伐仲。宋襄兵败，任侠多累。</p><p>古代的功名利禄，是和科举考试有很大关系的。至于后人妄论八股骈文，却是身在其中而不自知。又有一群人，不管心里怎么想，但是做给别人看的总要符合这个身份和当时的规则。从这点看来，现在的人没有气节，也可以称的上是某种程度的投机分子。假意逢迎不合理的制度并且为了追求自身利益最大化而劳心费力，这样的人多了，就会对这样的环境起到了正反馈的作用，从人性来讲，不反对等于隐式认同。这其实和抗战时的汉奸特务无疑，他们也只是在居无定所中安身立命，尽管这会奴役和残害自己的同胞。但，对于国素质而言，从某种程度来讲， 冷漠遗忘不作为，却更胜于杀人分尸，路边人的健康温饱甚至生死，依旧不会被关心。偏偏很大一部分人又喜欢站在道德制高点来讲述这些话。唯一不同的一点是，现在人不必被钉在耻辱柱上，因为残害杀人的定义，是由律法来决定的。</p><p>文人的圈子中从来不缺乏批判，但单多半只是自娱而已，伴随的还有求而不得。只有身居高位才能付诸忧国忧民，血染疆场才能保家卫国。而治国之策能否施展，自然有历史为证：贾生才调，武侯余生，东坡鹰犬，放翁镜湖，嵇康放荡，阮籍穷哭，守仁流放，嗣同快哉。</p><p>回到现在。纵有诸多不适，但总归会隐忍着走完这一程，随之而来的是被贴上言简意赅的标签，然后用机械化的方式来查询 SQL。似乎现在的一切都能用古代文字来解决，“世有伯乐然后有千里马，千里马常有而伯乐不常有，故虽有名马，，，。安求其能千里”。教育可以做什么，历史可以带来什么，似乎有一双看不见的手，伸出大气层，蒙在了太阳之上。我们可以不关心国际形势，因为这是政治家的事情，当然也可以不关心战争，按照俄乌的节奏，世界大战是早晚的事，那其实科技也不必关心，程序员成了码农，人机融合遥遥无期。</p><p>小人物又能做些什么呢？余华笔下的福贵是凄惨的，莫言总是在讲述村野往事，韩寒的天才少年在夕阳下凌乱。在某个不经意的瞬间，我时常想起屈原的渔夫，苏轼的洗儿，陆游的咏梅，以及闻一多的暗杀……</p>]]></content>
    
    
    <summary type="html">深夜随感，从教育内卷到文人气节，聊聊独立精神与小人物的无奈。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.no-claw.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>ventoy引导wtg</title>
    <link href="https://blog.no-claw.com/posts/874b576b/"/>
    <id>https://blog.no-claw.com/posts/874b576b/</id>
    <published>2023-02-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="ventoy"><a href="#ventoy" class="headerlink" title="ventoy"></a>ventoy</h2><p>ventoy 是一个可以支持启动很多镜像的工具，可以理解为 win + linux 的版本的 PE。现在也兼容了 openwrt，chromeOS， EXSI 这些系统了，虽然还不支持 MacOS 哈哈哈哈</p><h2 id="WTG"><a href="#WTG" class="headerlink" title="WTG"></a>WTG</h2><p>现在民间有萝卜头的 Windows To Go 辅助工具|WTG 辅助工具 v5.6，可以轻松的安装系统到 U 盘。</p><p><a href="https://bbs.luobotou.org/thread-761-1-1.html">https://bbs.luobotou.org/thread-761-1-1.html</a></p><p>我的系统是 CZ880 的 256G 版本,读写均可达到 400MB&#x2F;S 左右，这个速度已经很接近 Sata3 固态硬盘了。</p><span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210161015798.png" alt="Disk mark"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210160038389.png" alt="atto benchmark"></p><h2 id="WTG-ventoy"><a href="#WTG-ventoy" class="headerlink" title="WTG + ventoy"></a>WTG + ventoy</h2><ol><li>按照下图设置好，vhdx 容量选择 64G。然后选好 U 盘和对应镜像，写盘完成之后从 U 盘启动完成初始化，进入一次系统，不然据说 ventoy 进去的时候会导致系统起不来。（动态存储，64G，实际安装完只有 8G，随着使用会慢慢增大）</li></ol><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210160343597.png" alt="启动方式"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210160428464.png" alt="设置64G"></p><ol start="2"><li>备份刚才安装好了 vhdx，然后安装 ventoy，这次会格式化所有的分区，而且默认分区是 EXFAT，如果想使用 WTG 的话需要把系统格式化 NTFS，这样才能运行 windows。不格式化的话，就会….Any way 就是花式错误</li><li>把一开始 vhdx 拷贝回来就行啦。（需要安装个插件 新建 ventoy 目录里 ventoy_vhdboot.img</li></ol><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210161420893.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210161504316.png" alt="image-20230210161504316"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230210161504316.png"></p><h2 id="白名单"><a href="#白名单" class="headerlink" title="白名单"></a>白名单</h2><p>有时候不想让 ventoy 显示其他的东西，比如黑苹果的 EFI，office 的 ISO，这样就需要设置显示 image_list 的白名单。</p><p>就是在 ventoy 目录下 ventoy.json 这个格式，而且只会显示这里的镜像。</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><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;image_list&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/win10wtg.vhdx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/archlinux-2023.02.01-x86_64.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/CentOS-7-x86_64-DVD-2009.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/chromeos_15117.112.0_reven_recovery_stable-channel_mp-v2.bin&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/Fedora-Workstation-Live-x86_64-37-1.7.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/FydeOS_for_PC_iris_v16.0-stable&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/kali-linux-2022.4-installer-amd64.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/linuxmint-21.1-xfce-64bit.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/manjaro-gnome-22.0.2-230203-linux61.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/pop-os_22.04_amd64_intel_22.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/pop-os_22.04_amd64_nvidia_22.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/linux/ubuntu-22.04.1-desktop-amd64.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/windows/cn_windows_10_enterprise_ltsc_2019_x64_dvd_9c09ff24.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/windows/win7.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/windows/zh-cn_windows_11_business_editions_version_22h2_updated_jan_2023_x64_dvd_82450200.iso&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;/ventoy/OS/windows/zh-cn_windows_server_2022_updated_oct_2022_x64_dvd_884ce1ea.iso&quot;</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>PS：找 chatgpt 要了一份自动生成 json 文件的代码：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line">dir_name = os.path.basename(os.getcwd())</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">convert_to_unix_path</span>(<span class="params">windows_path</span>):</span><br><span class="line">    <span class="keyword">return</span> windows_path.replace(<span class="string">&#x27;\\&#x27;</span>, <span class="string">&#x27;/&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_files_in_directory</span>(<span class="params">path</span>):</span><br><span class="line">    files = []</span><br><span class="line">    <span class="keyword">for</span> root, _, filenames <span class="keyword">in</span> os.walk(path):</span><br><span class="line">        <span class="keyword">for</span> filename <span class="keyword">in</span> filenames:</span><br><span class="line">            file_path = os.path.join(root, filename)</span><br><span class="line">            relative_path = <span class="string">f&quot;\&#123;dir_name&#125;\&#123;path&#125;\&#123;os.path.relpath(file_path, path)&#125;&quot;</span></span><br><span class="line">            files.append(convert_to_unix_path(relative_path))</span><br><span class="line">    <span class="keyword">return</span> files</span><br><span class="line"></span><br><span class="line">ventoy_json = &#123;</span><br><span class="line">    <span class="string">&quot;image_list&quot;</span>: get_files_in_directory(<span class="string">&quot;OS&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;ventoy.json&#x27;</span>, <span class="string">&#x27;w&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    json.dump(ventoy_json, f, indent=<span class="number">4</span>)</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">使用 Ventoy 引导 Windows To Go，实现 U 盘启动 Windows 系统。</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    <category term="装机" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/%E8%A3%85%E6%9C%BA/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>intel 12代CPU折腾记</title>
    <link href="https://blog.no-claw.com/posts/5821c3b8/"/>
    <id>https://blog.no-claw.com/posts/5821c3b8/</id>
    <published>2023-02-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近小米新出了迷你主机，甚至连 nuc 贴纸都没摘，所以还不如直接买 intel 的 nuc 华尔街峡谷，毕竟从 nuc8 的黑苹果一路走过来的。什么都不用改，直接用豆子峡谷的 vesa 壁挂。但是也有一些问题。</p><h2 id="大小核的问题"><a href="#大小核的问题" class="headerlink" title="大小核的问题"></a>大小核的问题</h2><p>12 核 16 线程其实是 4 大核 8 小盒，所谓大小核问题，就是在以 vmware 为首的很软件，经常出现小核跑满大核限制的情况，然而在任务管理器中还显示 CPU 占用 100%。 <span id="more"></span></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/226d7d36d7d81004813969549ca17af.jpg" alt="大核围观"></p><p>需要在电源模式中开启性能模式，然后才能解开大小核的显示，后续的时候没有发现特别不适应的地方。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230206090754.png" alt="开启性能模式"></p><!-- more --><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230206090907348.png" alt="修复之后的大小核"></p><h2 id="显示器延迟问题"><a href="#显示器延迟问题" class="headerlink" title="显示器延迟问题"></a>显示器延迟问题</h2><p>因为主机是双 HDMI 和双 typec 的接口，所以必须要接一台 typec 的的显示器，但是 Philip 网红显示器接上有很长的延时点亮问题，有时候甚至比开机时间还要长，同时也咨询过 intel 和 Philip 的售后支持人员，答复如下：</p><ul><li>intel：从 Xe 显卡开始就有这种兼容问题，存在和 philip，aoc 部分型号的兼容问题</li><li>philip：客服说显示器 10s 点亮正常，到了售后说是更长的时间，最后到厂家说 32s 才正常（东西不错，售后真的太差。。。</li></ul><p>另外显示器有时候不能 4K60@刷新，客服说要把显示器的 hub 改成 2.0，但是这样就只能接一些键鼠摄像头一类的东西了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/17fd7fcd64712dd242b6c3db3144daa.jpg"></p><h2 id="2-5g-网口驱动问题"><a href="#2-5g-网口驱动问题" class="headerlink" title="2.5g 网口驱动问题"></a>2.5g 网口驱动问题</h2><p>2.5G 驱动目前好像只能离线安装。目前 win10 和 win11 系统内部没有集成 2.5G 网口的驱动，所以需要手动去官网下载驱动，然后手动安装。这里不太推荐驱动精灵一类的第三方软件。但是其他驱动只能联网之后安装，所以还是先准备个 U 盘吧。</p><h2 id="Win11-强制登陆-MS-账户"><a href="#Win11-强制登陆-MS-账户" class="headerlink" title="Win11 强制登陆 MS 账户"></a>Win11 强制登陆 MS 账户</h2><p>win11 虽然很丑，但是据说只有这个对大小核兼容好一点，现在只有企业版核教育版可以不用强制登陆 MS，其他的都绕不过去，有的输入神秘代码亲自测无效，估计是 MS 家修复了这个问题。</p><h2 id="系统缓存的问题"><a href="#系统缓存的问题" class="headerlink" title="系统缓存的问题"></a>系统缓存的问题</h2><p>装好系统之后，遇到了 C 盘占用特别高的问题，网上查询之后是因为内存太大，导致 OS 自动做了休眠文件，为了把内存都压到磁盘里。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/7525dec0f39e5c5c9644a569cb09733.jpg"></p><h2 id="PCLE4"><a href="#PCLE4" class="headerlink" title="PCLE4"></a>PCLE4</h2><p>唯一值得安慰的是 PCLE4 的 NVME，顺序读取有 6600MB&#x2F;S，总之还算不错。但是总归是 TLC 的，毕竟我连 MLC 都能写坏…</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230206100743367.png" alt="PCLE4"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230206101325494.png" alt="aida64"></p>]]></content>
    
    
    <summary type="html">Intel 12 代 CPU 与 NUC 小主机的折腾记录</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>打印机改无线</title>
    <link href="https://blog.no-claw.com/posts/79388b04/"/>
    <id>https://blog.no-claw.com/posts/79388b04/</id>
    <published>2023-01-24T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>过年回家给家里的老式打印机改装了无线打印，了解到目前无线打印分为以下几种：</p><ul><li>自带的无线功能，插网线就能用</li><li>有线打印机 + 小白盒子</li><li>有线打印机 + 电脑共享</li></ul><span id="more"></span><h3 id="安装驱动"><a href="#安装驱动" class="headerlink" title="安装驱动"></a>安装驱动</h3><p>前两种基本没有什么差异，不过需要手机和电脑分别安装打印机驱动，无线的可能支持微信无线打印，但是用盒子自己改的目前还不支持移动端，这种一般是打印机使用连接路由器，一般公司是这种。</p><h3 id="无需安装驱动"><a href="#无需安装驱动" class="headerlink" title="无需安装驱动"></a>无需安装驱动</h3><p>当然有办法可以让手机不用安装驱动，需要使用 PC&#x2F;MacOS 安装驱动之后再局域网共享打印机，这样 windows 和安卓都能搜到。</p><h3 id="苹果特殊的协议"><a href="#苹果特殊的协议" class="headerlink" title="苹果特殊的协议"></a>苹果特殊的协议</h3><p>有一些老式的打印机会出现苹果设备搜不到打印机的情况， 是因为没有兼容 airprint 协议导致的。当然新的打印机无需考虑这个问题。曾经有个 airprint 的软件，现在已经停更，也不想折腾了。Anyaway，劝退 apple 全家桶的最后一根稻草。</p>]]></content>
    
    
    <summary type="html">将老式有线打印机改装为无线打印的几种方案</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="打印机" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E6%89%93%E5%8D%B0%E6%9C%BA/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>家有吃货之自制纯肉肠</title>
    <link href="https://blog.no-claw.com/posts/75eceee6/"/>
    <id>https://blog.no-claw.com/posts/75eceee6/</id>
    <published>2023-01-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li><p>淘宝买的猪小肠的肠衣，以前也使用过其他肠衣，效果不好。基本上是腌渍的肠衣，回来用料酒泡了一个多小时，然后用水冲了几遍场子内部，看看有没有坏的地方，有小孔不影响灌肉的可以忽略。<img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/4e2429485712be78190031ab1420247.jpg" alt="清洗肠衣"> <span id="more"></span>  <img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124151952481.png" alt="肠衣灌水"></p></li><li><p>趁着清洗肠衣的时间调制肉馅，用绞肉机打碎，加上卖家的肉料（其实也可以自己调制），每斤肉加入30g淀粉，用来填补肉之间的孔位，红曲粉看颜色放入，差不多一斤肉0.5g的样子，主要为了颜色发红，多了会暗红。这个添加剂据说无害。最后加入葱姜水，六斤多肉我放了两碗。搅拌到最后越来沉，是在没力气搅拌的拉丝，直接就下一步了。<img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124151923265.png" alt="调肉馅"></p></li><li><p>使用用灌肠神器，其实就是一个大号针管。视频里都是很顺利的灌进去了，<del>我这个有肉堆积的现象，然后得用手一点点往下缕，不然会撑到爆开。</del> 把肠衣完全套在神器上。灌好之后用绳子打结，分出每根的大小，最好打两层的绳子，保证切的时候不会破坏肠衣，然后用牙签戳，防止煮的时候肠衣爆开。完事之后进行风干操作，屋内低温自然2小时或者烤箱30风干30分钟。<img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152816774.png" alt="套上肠衣"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152010560.png" alt="灌肠"> <img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152032595.png" alt="风干"></p></li><li><p>风干之后可以继续扎眼，然后冷水下锅煮，中间可以自由翻面，有明显气泡可以直接用牙签戳破。全程小火，开锅了就加凉水，40-50分钟之后香肠都飘起来就可以了。记得扎两层绳子，防止切的时候肠衣剥开。 <img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152042980.png" alt="小火煮"></p></li><li><p>煮好的香肠可以冷冻上，吃多少烤多少，煎炒烹炸都可以。烤箱的话180度15分钟就可以，想吃脆皮的可以多烤一会。<img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152052396.png" alt="煮好的肉肠"></p></li><li><p>基本上做出来的和网上买的火山石烤肠相差无几，不过自己家做的纯肉的，99. 99%纯肉，外边的比不了哦。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152101388.png" alt="烤箱烤完的"><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124152845846.png" alt="新年挂鞭"></p></li></ol><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/f418004f44313a04bcfdfe4c7b7020c.jpg" alt="不加红曲粉的"></p>]]></content>
    
    
    <summary type="html">在家用猪肠衣自制纯肉肠的详细做法与经验</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="吃货风云" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%90%83%E8%B4%A7%E9%A3%8E%E4%BA%91/"/>
    
    
    <category term="家有吃货" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E6%9C%89%E5%90%83%E8%B4%A7/"/>
    
  </entry>
  
  <entry>
    <title>自制味精</title>
    <link href="https://blog.no-claw.com/posts/9599d85d/"/>
    <id>https://blog.no-claw.com/posts/9599d85d/</id>
    <published>2023-01-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/03324fac5f9df0ccc576361f7b34354.jpg" alt="烤好的虾"> <span id="more"></span></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/e08d7fe9931b33e1696d85e59257f86.jpg" alt="虾粉"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/acc1aacddf61f60ca6b34dae172c996.jpg" alt="鲜虾猪肉馄饨"></p>]]></content>
    
    
    <summary type="html">用虾等食材自制天然味精鸡粉的做法</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="吃货风云" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%90%83%E8%B4%A7%E9%A3%8E%E4%BA%91/"/>
    
    
    <category term="家有吃货" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E6%9C%89%E5%90%83%E8%B4%A7/"/>
    
  </entry>
  
  <entry>
    <title>工具集锦</title>
    <link href="https://blog.no-claw.com/posts/cf8e3176/"/>
    <id>https://blog.no-claw.com/posts/cf8e3176/</id>
    <published>2023-01-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>工欲善其事必先利其器，所以干活之前要先准备好工具，也就是基础设施一类的东西，对于电工网工而言，就是电气设备网线一类的东西了。</p><h3 id="电工必备"><a href="#电工必备" class="headerlink" title="电工必备"></a>电工必备</h3><p>大概是从自制插排开始，一把 10 元螺丝刀螺丝刀开始，自制插排，换插座吗，就把线接一起就行，没啥难的，注意接的时候断电。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/ac2ebb51f599d07f7b08ce6a033df4f.jpg" alt="ac2ebb51f599d07f7b08ce6a033df4f"></p><span id="more"></span><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/8f057a8ce34cce664cee70b01be375b.jpg" alt="8f057a8ce34cce664cee70b01be375b"></p><p>如果你想自己做插排，你可以使用以下步骤：<br>准备材料：你需要一个电缆，一个插头盒，一些电线和电缆接头。<br>切线：按照需要的长度将电缆切为若干段，并分别给每一段线剥去绝缘层，留下裸露的电线。<br>做接头：将裸露的电线与电缆接头相连，并给接头做好绝缘。<br>安装插头：在插头盒中安装电缆接头，并确保每个接头的接触良好。<br>测试：插上插头，确认每个插口都能正常工作。<br>注意：请确保使用适当的工具和安全设备，避免造成危险。如果你不熟悉电器装置，建议请专业人员帮助。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230209213808831.png" alt="绝缘胶带"></p><h3 id="电烙铁"><a href="#电烙铁" class="headerlink" title="电烙铁"></a>电烙铁</h3><p>手残党哎，一言难尽。忘记留图。以后争取买一把好的电烙铁。</p><h3 id="网工"><a href="#网工" class="headerlink" title="网工"></a>网工</h3><p>以前是因为路由器和电脑网卡不行导致延迟太大，所以需要自己做一段网线，需要买网线钳，水晶头一类的东西。另外顺便把光猫和 NAS 的网线也给换了。</p><p>蓝色是自己做的。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/2694813af1a1dbfc3c8f5f91d188f53.jpg" alt="2694813af1a1dbfc3c8f5f91d188f53"></p><p>如果你想要做网线，你可以使用以下步骤：<br>准备工具：你需要一把剪刀、一台网线剥线器和网线插头（RJ45 插头）。<br>剥线：使用网线剥线器将网线剥去外层的绝缘层，留下八条裸露的线芯。<br>分类：根据网线插头的颜色代码将线芯分为五组，分别是白色、橙色、绿色、蓝色和棕色。<br>排列：排列五组线芯，按照颜色代码将线芯排成一条直线，从左到右依次是白、橙、绿、蓝和棕。<br>对接：将网线插头与网线相连，把排列好的线芯插入网线插头中，直到线芯完全插入插头。<br>测试：使用网络测试仪测试网线的连通性。<br>注意：网线插头的颜色代码有不同的标准，在选择网线插头时请确认使用的是适合你的网线标准。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230209221352329.png" alt="image-20230209221352329"></p><h4 id="水晶头"><a href="#水晶头" class="headerlink" title="水晶头"></a>水晶头</h4><h4 id="剥线钳"><a href="#剥线钳" class="headerlink" title="剥线钳"></a>剥线钳</h4><p>其实现在的网线都是 B 类的直通线，网线线序 xxx，不够据说现在的交换机都能智能识别线序，从二层这里兼容了双绞线和直通线，当然本人偶尔也有把线序弄错的时候，但是竟然还可以正常传输，这点不得而知。</p><h3 id="螺丝刀"><a href="#螺丝刀" class="headerlink" title="螺丝刀"></a>螺丝刀</h3><p>学生时代买的 10 元螺丝刀用了很多年，遇到其他尺寸也要单买不同的规格，况且买东西经常赠送小螺丝刀什么的，每次换一个地方都要买一个螺丝刀套装，miniso 的 15 元套装也用了很多年。不过入手了电动螺丝刀之后兼职不要太幸福，拆电脑，门锁窗户把手都是分分钟的事。按左边拆螺丝，按右边拧螺丝。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230209213910580.png" alt="乐歌螺丝刀"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/264a0349f20fd7b7235f6ab6be370b2.jpg" alt="264a0349f20fd7b7235f6ab6be370b2"></p>]]></content>
    
    
    <summary type="html">电工网工常用工具集锦，工欲善其事必先利其器</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>无限续面</title>
    <link href="https://blog.no-claw.com/posts/45c86e6c/"/>
    <id>https://blog.no-claw.com/posts/45c86e6c/</id>
    <published>2022-12-29T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>聊一聊北京那些无限续面的小吃</p><p>外面的面条不知道什么材质做的，怎么吃也感觉吃不饱，就是胀肚还觉得饿，大脑也没有能量。</p><h3 id="刘胖妹面庄"><a href="#刘胖妹面庄" class="headerlink" title="刘胖妹面庄"></a>刘胖妹面庄</h3><p>应该是望京这边最好吃的面了，能免费续面（大碗），但是不能免费加汤。首重三两半，续重二两半。单买了肉末和豌杂。</p><p>有打油诗为证：</p><span id="more"></span><p>挑战一天内只吃刘胖妹面庄<br>刷新店内最高续面记录失败<br>和老大哥差了二两半的白面<br>不愧网红的小面店下次再战</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094341992.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094605274.png"></p><h3 id="和府捞面"><a href="#和府捞面" class="headerlink" title="和府捞面"></a>和府捞面</h3><p>从三里屯种草之后就一发不可收拾，三十多一碗的价格不续面都觉得亏，用草本汤稀释番茄汤味道不错，最高记录6碗，可以免费加香菜，现在改了菜单，理性考虑吧。个人感觉沦为路边小吃快餐水准了。（有些店完全没有中国风的感觉。。。</p><p>ps：已经粉转黑</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094646084.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094655638.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094712700.png"></p><h3 id="陈香贵"><a href="#陈香贵" class="headerlink" title="陈香贵"></a>陈香贵</h3><p>号称拉面界的海底捞，其实就是普通的兰州牛肉面，觉得跟和府捞面的牛肉面也差不多。好处是无线续面，店里人一直不少。</p><p>羊肉串比较薛定谔，有次用的打折券就很难吃，串又小又苦，很难不让人觉得有什么猫腻。之前单点的还行。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094744482.png" alt="陈香贵初探"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125094844077.png" alt="陈香贵五碗"></p><h3 id="方砖厂"><a href="#方砖厂" class="headerlink" title="方砖厂"></a>方砖厂</h3><p>外地人慕名而来，其实还没自己家的炸酱面好吃，商业化了之徒有其表，不怎么推荐。拌面续面的结果就是吃着吃着就凉了，不给加菜，还是第一次续面吃不下去的，排队很长。酸梅汤和拌肚很不错。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125095325338.png" alt="酸梅汤"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125095346950.png"></p><h3 id="蓉李记"><a href="#蓉李记" class="headerlink" title="蓉李记"></a>蓉李记</h3><p>商场角落的一员。打折券13.9还能无限续面，不过每碗面很少，不过不耽误我吃6碗，最后辣子鸡很不错。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125101151882.png" alt="image-20230125101151882"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230125101129523.png" alt="辣子鸡"></p>]]></content>
    
    
    <summary type="html">盘点北京那些可以无限续面的小吃店</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="吃货风云" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%90%83%E8%B4%A7%E9%A3%8E%E4%BA%91/"/>
    
    
    <category term="家有吃货" scheme="https://blog.no-claw.com/tags/%E5%AE%B6%E6%9C%89%E5%90%83%E8%B4%A7/"/>
    
  </entry>
  
  <entry>
    <title>工作台改造</title>
    <link href="https://blog.no-claw.com/posts/baed0386/"/>
    <id>https://blog.no-claw.com/posts/baed0386/</id>
    <published>2022-10-17T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="办公"><a href="#办公" class="headerlink" title="办公"></a>办公</h2><h3 id="无线打印扫描一体机"><a href="#无线打印扫描一体机" class="headerlink" title="无线打印扫描一体机 "></a>无线打印扫描一体机 <span id="more"></span></h3><h3 id="照片打印机"><a href="#照片打印机" class="headerlink" title="照片打印机"></a>照片打印机</h3><h3 id="实木电动升降号桌"><a href="#实木电动升降号桌" class="headerlink" title="实木电动升降号桌"></a>实木电动升降号桌</h3><h3 id="4K-显示器"><a href="#4K-显示器" class="headerlink" title="4K 显示器"></a>4K 显示器</h3><p>要带菊花链</p><h3 id="墨水屏显示器"><a href="#墨水屏显示器" class="headerlink" title="墨水屏显示器"></a>墨水屏显示器</h3><h3 id="移动固态"><a href="#移动固态" class="headerlink" title="移动固态"></a>移动固态</h3><h3 id="人体工学椅"><a href="#人体工学椅" class="headerlink" title="人体工学椅"></a>人体工学椅</h3><h3 id="降噪耳机"><a href="#降噪耳机" class="headerlink" title="降噪耳机"></a>降噪耳机</h3><p>现在有 airpod pro2， qc45，华强北 airpod</p><h3 id="Mesh-路由"><a href="#Mesh-路由" class="headerlink" title="Mesh 路由"></a>Mesh 路由</h3><h3 id="屏幕挂灯"><a href="#屏幕挂灯" class="headerlink" title="屏幕挂灯"></a>屏幕挂灯</h3><h3 id="随身-WIFI"><a href="#随身-WIFI" class="headerlink" title="随身 WIFI"></a>随身 WIFI</h3><h2 id="娱乐"><a href="#娱乐" class="headerlink" title="娱乐"></a>娱乐</h2><h3 id="4K-投影仪"><a href="#4K-投影仪" class="headerlink" title="4K 投影仪"></a>4K 投影仪</h3><h3 id="吊椅"><a href="#吊椅" class="headerlink" title="吊椅"></a>吊椅</h3><p>地投幕布</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><h3 id="即热饮吧"><a href="#即热饮吧" class="headerlink" title="即热饮吧"></a>即热饮吧</h3><h3 id="自动烘干机"><a href="#自动烘干机" class="headerlink" title="自动烘干机"></a>自动烘干机</h3><h3 id="全自动扫地机器人"><a href="#全自动扫地机器人" class="headerlink" title="全自动扫地机器人"></a>全自动扫地机器人</h3><p>可以自己换水自己换垃圾那种</p><h3 id="自动拉窗帘"><a href="#自动拉窗帘" class="headerlink" title="自动拉窗帘"></a>自动拉窗帘</h3><h3 id="家庭中枢-HomePod-3"><a href="#家庭中枢-HomePod-3" class="headerlink" title="家庭中枢 HomePod * 3"></a>家庭中枢 HomePod * 3</h3><h3 id="全屋智能家居定制"><a href="#全屋智能家居定制" class="headerlink" title="全屋智能家居定制"></a>全屋智能家居定制</h3><h3 id="智能门锁"><a href="#智能门锁" class="headerlink" title="智能门锁"></a>智能门锁</h3><h3 id="智能猫眼"><a href="#智能猫眼" class="headerlink" title="智能猫眼"></a>智能猫眼</h3><h1 id="家里要布置的电子设备"><a href="#家里要布置的电子设备" class="headerlink" title="家里要布置的电子设备"></a>家里要布置的电子设备</h1><h2 id="电脑设备"><a href="#电脑设备" class="headerlink" title="电脑设备"></a>电脑设备</h2><p>4415U 缝缝补补还能再战三年</p><h2 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h2><h3 id="10G-内网"><a href="#10G-内网" class="headerlink" title="10G 内网"></a>10G 内网</h3><h3 id="24-口交换机"><a href="#24-口交换机" class="headerlink" title="24 口交换机"></a>24 口交换机</h3><h3 id="机柜"><a href="#机柜" class="headerlink" title="机柜"></a>机柜</h3><h3 id="现在的设备"><a href="#现在的设备" class="headerlink" title="现在的设备"></a>现在的设备</h3><ol><li>乐歌自动升降桌 E6—HD</li><li>双 4K 显示器(philip，samsung)</li><li>NUC8 黑苹果(双系统，内存 64G) <!--more--></li><li>Ipad air5(apple pencil2 + magic board)</li><li>无线打印机(Lenovo M7400 Pro 改)</li><li>键鼠(Ikbc，logtech)</li><li>耳机(Bose QC 45，Airpod pro 2)</li><li>小米路由器 AX3000</li><li>山特 UPS</li><li>NAS(QNAP 453Dmini， 内存 64G)</li><li>EXSI 双软路由(ikuai + openwrt)</li><li>小米台灯 PRO</li><li>公牛插座 6 排 * 2</li><li>绿联 Gan 100W</li><li>8K typec 视频线</li><li>apple magsafe</li></ol><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/60abeac9c3c566680d56d37f8fe06e6.jpg" alt="我的桌面"></p>]]></content>
    
    
    <summary type="html">办公工作台改造计划与桌面布局优化</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>“智能”家居相伴的日子</title>
    <link href="https://blog.no-claw.com/posts/49e53210/"/>
    <id>https://blog.no-claw.com/posts/49e53210/</id>
    <published>2022-06-19T06:43:24.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>你是程序员吧，我家电脑有点慢，网络有点卡，帮我看一下吧。”</p><p>尽管网上关于程序员修电脑的段子已经层出不穷了，似乎大多数人也不再吐槽程序员修电脑这个事情。事实上，不管走到哪里都总会有人找我修电器，从广告 LED 屏幕，电子秤，再到手机电脑，电视盒子，最后到路由器。所以当别人问我大学读的什么专业，我干脆回答修家电的。</p> <span id="more"></span><p>我似乎已经形成了习惯，不管走到哪里都要准备一套趁手的设备：螺丝刀套装，钳子，万用表和绝缘胶带，准备了这些电工和网工必备的工具来提升维修的效率，至于网线钳实在是太累手，已经抛弃不用了。</p><p>这几年 NAS 的成品百花齐放，有对爱好者入门很友好的群晖，有适合更加专业一些的威联通，甚至于联想和绿联这样的商家也纷纷推出成品来降低使用者的门槛。其实驱动着自建 NAS 的爱好者不断折腾不过以下几个原因，网盘限速，限制文件大小，还有各种网盘的不定时监控。在我自从被用百度网盘被莫名限制文件下载之后，便开始了在家里自建数据中心的想法，这样就可以不受拘束自由的下载和分享文件。实施下来除了晚上各种网线灯和电源灯的光污染之外，其他方面提升了很高的幸福指数，上传下载可以跑满家里的千兆内网，也可以分享体积大于 5GB 的单个文件，至于存储空间就取决于钞能力了，也希望早日能够实现家庭百 T 存储的梦想。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230131120825.png"></p><p>一开始由于资金的原因，我打算在 Centos7 上自己部署一些开源服务，但是鸟哥私房菜实在太厚啃不动，加之后期的维护成本巨大，每个服务维护起来都要敲指令。后来我索性花重金买了 QNAP 当红机器，同时也不定时和同学柜子里的黑群晖在做着异地同步。在这一切准备完毕后，我找到了联通公司要了动态公网 IP 地址，结束了与内网穿透的相爱相杀的艰难的日子，同时利用闲置域名设置了一套动态域名解析，用手机随时随地连回家里的服务。唯一遗憾的是回老家是用树莓派做的网页监控也经常被运营商封掉，后来从群友处得知 80&#x2F;8080&#x2F;443 这几个端口不能使用，联通客服只能从后台看见网络状态，其他的超出了支持范围，同时也驳回了我想去找网警咨询的要求。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131120904333.png" alt="image-20230191120904333"></p><p>后来了我解到，如果是在家里建站好像是要和公安部门申请的，走一个类似备案的流程。而联通的上传带宽只有 30MBPS，似乎满足不了随时连回家观影 4K 电影的需求。在极客心的驱使下我走上了单宽带叠加的路程，使用家里 200M 的宽带同时拨号三次可以达到 550M 的网速，再后来趁着活动免费提升到了 500M，单线多播这才告一段落。但是搬家到北京之后，出租房的网络太差并且带宽只有 100M，在因为网络不稳和在经历了无数次和运营商以及二级运营商的扯皮后，只能选择额外加上一个其他运营商的线路，京东下单了小米 AX5400 路由器，在上边设置了一个双宽带叠加。相对而言没有软路由那么自由，硬路由的叠加只能是不同的运营商的宽带，比如移动和联通。而且运算速度也不如 X86 架构的软路由，优点是 NAT 性能很好。所以还是推荐软路由加上个普通路由做个 AP。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131120925835.png" alt="image-20230131120925835"></p><p>经过群友不完全统计，有些地区的运行商很大方会直接给公网 IP，有些地方运营商政策不一，需要等一定的时间，然后手动更改为路由器拨号。还有一些运营商甚至明码标价，公网 IP 每月固定收费，这个典型的代表就是北京移动。</p><p>解决了存储问题后，下一个面对的问题是网络。我想到很多国外的设备仍然需要连接海外的服务器进行激活，比如 Oculus 的 VR 眼镜，包括 Google Pixel 在内的原生安卓，这些都需要在连接海外服务器的情况下激活，这样的功能默认在国内是无法使用的。所以我想玩的痛快游一些，于是便在路由器的上挂一层代理，这样电脑手机都不用安装专门的客户端了，甚至连 kindle 都可以愉快的科学上网了。我折腾了下面三种：</p><ol><li>X86 平台的工控机</li></ol><p>这种产品现在已经成为产业链了，主要供货渠道来是各个平台短视频平台的科技 UP 主，基本上是一次购买终身答疑的制度，还有对应售后的微信群以及用于下载资料和固件的博客网站。对于小白用户，大概要熟悉一周左右，需要安装 EXSI 和两个路由器系统虚拟机，其中一个是国产路由器操作系统 IKUAI，另一个是大名鼎鼎的 Openwrt。再把网卡直通（类似 EC2 的增强联网）。IKUAI 的系统是免费的，也可以在这里安装虚拟机安装 openwrt，当然禁止套娃。Openwrt 在 Github 上有源代码和 release 文件，也有开源的编译方法。这里有个小插曲：</p><p>a) 我装系统一直用的 ventory，出现安装之后无法启动的错误，给技术支持打电话之后，的工程师表示镜像没有问题，换了刻录镜像的方式之后可以安装。后来调查应该是 ventory 的支持不好。</p><p>b) 用自己的四核笔记本电脑编译 OP 源码，几个小时 CPU 一直满载，风扇噪音比冰箱制冷的声音还要大。（我的冰箱因为压缩机问题本身噪音就很大，妥妥的人工噪音）</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121025897.png" alt="image-20230131121025897"></p><ol start="2"><li>普通路由器降级再刷官改固件</li></ol><p>实习的时候部门老大过来跟我闲聊，他说把家里的 AC86U 刷了梅林固件，后来因为信号问题影响孩子写作业又刷回原厂固件了。梅林固件我是用的斐讯 K3 刷的，江湖人称漏油机，好像是因为散热的硅脂不太行，只要加热就会有油析出，拆开之后散热器上是油和灰尘的混合体（画面太美就不放了，想看可以移步朋友圈）。而且斐讯的其他产品也可以随便刷机，我把两个 K2 分别刷了 openwrt 和<a href="https://www.jianshu.com/p/6b8403cdea46">Padavan</a>的系统，N1 和 T1 都刷成了安卓电视盒子，其中个人觉得 T1 作为影音系统的效果更好一些。不过 N1 还可以刷成 openwrt 制作旁路由。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230131121100.png"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121112296.png" alt="image-20230131121112296"></p><ol start="3"><li>旁路由</li></ol><p>我的网络理论知识不太行，虽然不懂原理，但是按照提示把主旁路由设置为相互的网关之后，屋内的设备就能愉快的上网了。这样流量每次都会在旁路由上转一圈，然后能做的事情就更多了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121128926.png" alt="image-20230131121128926"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230131121138.png"></p><p>当然言归正传，折腾完之后还是有一定的维护的成本，这一套部署下来家里感觉变成了一个 KTV，24 小时的噪音和灯光污染，软路由的网线口已经基本插满，家里每晚都闪烁着黄绿交替的灯。当然是黄灯闪烁，绿灯常亮。在一次在通电的情况下拔掉系统盘导致了磁盘坏块，如下图，数据丢失，虚拟化需要手动操作，然后上面的流程又需要重新开始。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121205703.png" alt="image-20230131121205703"></p><p>虽然大多数折腾的产品都很耗费时间精力，不过还是极大的方便了我的生活。在有了外网环境之后，下一个要折腾的就是原生安卓系统了。</p><p>我是四年的米粉换了 Iphone，还要从小米 8 刷原生安卓开始，来叙述这一段爱恨情愁。。。。。。</p><p>我在换了苹果全家桶之后，有种整个世界都安静的感觉，不用经常清理内存，也不必忍受普铺天盖地的广告。相信对于大多数爱好者来说，折腾安卓刷机的那个年代还是还留下了很多美好的回忆。对我来说，一切都是从高一那阵给同学手机刷机救砖开始，同学在手机 root 之后删除了系统关键文件导致无法开机。于是我一直花费整个晚上去找适配 Android2.3.5 的 ROM 包，到后来也会尝试用一些软件破解手机锁屏密码，比如爱思助手，奇兔刷机之类的。直到最近几年，国内各大安卓厂商技术已经相对成熟，刷机精灵也早已倒闭，在安卓系统开始变得封闭的那段时间里，我渐渐忘记了安卓刷机的事情。直到有一天，我手里的小米 8 在过保后经常屏幕反复黑屏，系统变卡之后，我终于忍无可忍重操旧业。正巧了解到有个关于原生安卓的开源项目” <a href="https://download.mokeedev.com/">MoKee</a>“，这个刷机步骤和以前大同小异。如果是品牌手机需要把系统降级到开发版，然后刷入 TWRP（也就是以前的<a href="https://baike.baidu.com/item/Recovery/9995978">Recovery</a>），最后双清卡刷 ROM 包。由于我在家里已经安装好了软路由，激活时候完全不用担心连接国外服务器延迟卡顿的问题。终于在刷机完成后，我的小米 8 好似复活了一般，清爽的没有广告，和三星相似的 UI，再装上 Google 三件套，我仿佛嗅到了自由的味道。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230131121228.png"></p><p>然而好景不长，大概半年以后，原生安卓的系统出现了和国内硬件水土不服的问题，经常发生内存爆满，应用卡顿，需要多次手动释放内存才能正常使用，而且还有很多本地化的功能无法使用，比如 NFC 门禁，小米公交钱包。而我在因为手机卡顿无法刷码差点被赶下公交车后，便开始尝试其他社区的官改包了，所谓官改包就是在官方的包上剔除广告和预装应用，然后再加上一些提升效率的小工具。我找了一个人气还行的 ROM，不过在刷机期间发生了一点点误操作，安卓不小心被刷成变砖头了，还是出现了熟悉的兔子界面，玩笑一语成谶。而对于小米来说，官方从根源上解决了用户自己救砖的问题，用户使用自己的设备刷机还需要官方的授权密码，而维修店的刷机设备则不需要。我在和小米官方售后交涉了小半年之后，终于在用 9008 免密刷机的方式刷回了最新版的小米安卓系统。还真是应了群友的话，愿你刷机半生，最后 MIUI 养老。</p><p>在分享过这个经历之后，便有朋友过来向我请教把手机刷成砖头的”秘诀”了。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121250138.png" alt="image-20230131121250138"></p><p>除此之外，闲来无事翻出来一个闲置的 36W 的三色 LED 灯，我起初想本着废物利用的原则，然后又买了一段 220V 的开关延长线，淘宝了买羊皮纸做了个简单的照明灯壳子，也用了闲置的米家智能插座。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121308175.png" alt="image-20230131121308175"></p><p>对于 IOS 设备来说还可以和 Siri 进行联动，只需要在快捷方式里加上一个唤醒米家设备的设置，最后用 HomePodMini 唤醒智能设备，唯一美中不足的是京东海淘的音响的是英版的插座，需要买额外的转换插头，如果用多口氮化镓的话会还重新分配工功率影响使用。此外 HomePod 还可以作为屋子里所有 Apple 设备的音响，无论你是 MacOS, Ipad 还是 iphone 都可以无缝连接，HomePodMini 是名副其实的 WIFI 音响，你值得拥有。</p><iframe src="https://www.bilibili.com/video/BV1t84y1L7SA/" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height=600 width=800> </iframe><p>除了电子设备之外，生活总会有些突如其来的小插曲，比如洗衣机坏了，面板报错 E3，朋友在电话里告诉我盖子传感器有问题，开微信视频指导我修，怎么拆盖子和电路板。我在和厂家确认过维修价格之后，便驳回了房东给的报销，还是自己拆更放心些，洗衣机报错盖子盖不严，拆机后发现传感器生锈了，最后使用了 320 目砂纸打磨搞定。一段时间过后，在某读书群认识一个同样修过洗衣机的姑娘，跟我讲如何给家里更换洗衣机电路板。顺便还晒了一波男朋友给的全家桶 – R2S，Pixel 加上 Google 三件套。她也给男朋友订阅了 Jetbrains 全家桶。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230131121415.png"></p><p>我甚至有个大胆的想法，希望以后传统家电的厂家可以暴露给用户一些接口，让用户写代码或者使用图形拖拽的方式来对品牌家电进行互联，等以后有了房子后搞一套 HomeKit 智能家居，下图是我现在用到的智能家居的 app，给各大厂商定一个小目标，就希望有一天可以互相整合开放 SDK 吧。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230131121432325.png" alt="image-20230131121432325"></p>]]></content>
    
    
    <summary type="html">记录智能家居设备的日常使用体验与感受</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>小米Ax5400双宽带叠加</title>
    <link href="https://blog.no-claw.com/posts/4625bf56/"/>
    <id>https://blog.no-claw.com/posts/4625bf56/</id>
    <published>2022-05-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>硬路由宽带叠加作为临时解决方案，目前只有 AX5400 和 AX9000 支持，并且只能是不同的运营商。</p><p>高级设置 -&gt; 其他 -&gt; 双 WAN 设置<span id="more"></span></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150814.png" alt="双WAN"></p><p>注意：</p><p>1.开启双 WAN 功能后会与部分功能冲突，若仍想使用冲突功能，请先尝试关闭双 WAN 后再试</p><p>2.若某些终端设备出现金融类 app 无法正常使用的情况，可尝试在“WAN 口策略”功能中将该设备设置为“WAN1 优先”或“WAN2 优先”后再试</p><p>3.请勿将 WAN1 或 WAN2 口与其他 Mesh 组网设备相连</p><p>设置完成的结果</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150839.png" alt="效果图"></p>]]></content>
    
    
    <summary type="html">小米 AX5400 路由器实现双宽带叠加的配置方法</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="路由器" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
    
    <category term="路由器" scheme="https://blog.no-claw.com/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>北京租房心得</title>
    <link href="https://blog.no-claw.com/posts/cdd2a06/"/>
    <id>https://blog.no-claw.com/posts/cdd2a06/</id>
    <published>2022-04-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong>房屋选择</strong></p><p>刚毕业租房是 40 平米的公寓，实际也有 30 平，租金是全部的实习工资。学校蜗居够了，不想再委屈自己。来北京刚开始只能合租了，后面黑中介，无良二房东，奇葩室友都见识够了，然后自己自己住了，一般中介都会虚标房间大小和朝向，还是得实际去看房。看房子的话，心理先有个预期，如果没有心仪的房子怎么样。如果是地下室或者一楼性价比再高也不要去看了，尽量租新一点和治安好的房子，北京老破小太多了，可以住，但是定期要去市区溜达吃点好的。看了没相中只会浪费时间。职场人首先要活的体面一点。</p><p>不要觉得房子只是一个睡觉的地方，起码的生活设施要有：稳定的水电网，燃气和隔音(手机有很多 app 可以测试分贝，大于 60 的房子就不要选择了)。而且之前疫情影响居家办公已经是家常便饭了。贵点不怕，总之还是要让自己觉得舒服。</p><p><strong>朝向：</strong> 南&gt;东&gt;西&gt;北</p><p>如果不是实在差钱的话，还是建议住在公司附近或者通勤方便的地方，很多人说单程一个半到两个小时。五环以外都是村里了，基础设施和人文设施和市里完全没有办法比，为了省心的话，还是住在繁华地带，毕竟一边工作也要一边生活，不然去市里总有村里孩子进城的感觉。二来北京可以玩的地方很多，地铁站附近去市中心也就半个小时。通勤要算往返时间，尽量减少换乘的等车时间，单程半个小时往返就是一个小时。能坐地铁不要坐公交，人多很挤，乘务员人工报站，还要维持车内秩序，没有降噪耳机的天花板建议慎选公交通勤，无听之于耳很难，很消耗精力。就算下班晚，23 点左右公交都没有座位。另外北京有共享单车，可以选择单车+地铁混合交通，单车会员每个月 15 左右。</p><p>不建议极端天气去看房，会漏调一些点。一定找不要临街的房子，五环以为打车没有管制，窗外 24H 大车通行。</p><h3 id="租房好物"><a href="#租房好物" class="headerlink" title="租房好物"></a><strong>租房好物</strong></h3><p>第一条，选择搬家好搬且轻量的东西，买之前想好搬家怎么处理。然后才是其他，同城搬家可以无视，因为有某拉拉这些，百元以内师傅给搬(非广告，遇到的师傅可以，但是 app 做的很差，客服跟僵尸一样)。毕竟北京房租高，自己买点小玩意跟房租比根本微不足道，就任性的买买买吧，毕竟以后结婚了就不一定有机会了。(最近看中了一套设备 10W+， NUC12 飞龙峡谷和 M1 Ultra 双机双显示器，apple 官网推荐的屏幕)</p><h3 id="工作效率物品（电脑和显示器台灯）"><a href="#工作效率物品（电脑和显示器台灯）" class="headerlink" title="工作效率物品（电脑和显示器台灯）"></a><strong>工作效率物品（电脑和显示器台灯）</strong></h3><p>现在基本都离不开电脑，有大桌子的买大显示器，否则就 15.6 便携屏（typec 一线通），还能带去图书馆。建议双屏或者带鱼屏，幸福指数提升很高，MacOS 用户可以用 slide bar，win 用户用 duet 都可以把 ipad 当作拓展屏。建议显示器预算 2000 左右，分辨率 4K@60HZ。现在轻薄本也性能炸裂了，没必要买大机箱，台式机的话 Macmini 和 NUC，APU 都是不错的选择，或者干脆买的轻轻的Macbook Air或者Thinkpad X1.</p><p>关于机械键盘，如果你的房租隔音好，放心用青轴和茶轴，万一被听见，还能给人留下半夜认真写代码的印象。</p><p>嗯，夜深人静的时候一定要选一个暖黄色的灯光，会觉得柔和放松。</p><p>还有，多备用几个插排，自己备个路由器，防止别人在公用路由看见你的设备和智能家居。</p><h3 id="生活用品"><a href="#生活用品" class="headerlink" title="生活用品"></a><strong>生活用品</strong></h3><p><strong>大型洗衣机（4L 左右）：</strong>自从在学校看见有人用公共洗衣机洗鞋，再也不用这玩意了。某鱼，某宝，某东 500 以下能买到很好的。（个人觉得别人买的洗衣机最多值 200）</p><p><strong>内衣洗衣机：</strong> 懒癌晚期，不想手洗，还有内衣洗衣液。</p><p><strong>洗鞋机：</strong> 因人而已。</p><p><strong>便携式烘干机：</strong> 如果不是朝南建议入手一个。</p><p><strong>消毒酒精：</strong>家中常备，可以买个自动喷酒精的机器</p><p><strong>降噪耳机：</strong> 2000 左右买个降噪耳机绝对值得，就当投资自己吧，开降噪听着音乐假装进入元宇宙。</p><p><strong>厨具：</strong>单身贵族不推荐了，能清水煮就行了。毕竟打折券很多，北京很多美食羊毛可以薅。豆浆机是个 bug，大的豆浆机一个人喝不完，小的费半天劲就一杯还得浪费时间清洗。</p><p><strong>灯具：</strong>自己可以换个好的搬家拆下带走，如果中介免费给换就更好了。</p><p><strong>窗帘：</strong> 自己可以换个好的搬家拆下带走，没必要买自动电机。</p><p><strong>拖布和苕帚：</strong>自己屋里备一个吧。</p><p><strong>垃圾桶和垃圾袋：</strong> 不多说。</p><p><strong>床上小桌子：</strong>不知道为啥不给上床下桌，床上是真舒服。</p><p><strong>即热过滤饮水器：</strong> 自带过滤3S 出热水，适合不喜欢和水龙头水的人。偶尔也得买点矿泉水喝。</p><p><strong>公交卡：</strong>手机 NFC</p><h3 id="不得不面对的人"><a href="#不得不面对的人" class="headerlink" title="不得不面对的人"></a><strong>不得不面对的人</strong></h3><h3 id="室友"><a href="#室友" class="headerlink" title="室友"></a><strong>室友</strong></h3><p>你的预算够了，遇到素质高的室友概率也会大大提高。当然能租开间最好，群租大家回家都是各回各屋，交流极少。</p><p>出于公共资源利用效率考虑，还是建议和室友们错开起床和睡觉时间，判断一副眼镜舒服的标准是你感觉不到它的存在。好室友的标准也是也感觉不到他的存在（eg：你上班了，他还未起，你睡觉了，他还没下班回来）</p><p>有些女室友通常不化妆不见人，所以女室友洗衣服做饭的时候就委屈一下错开空间吧。有些女室友不成熟，小孩子气，还有的茶，总之不要希望通过合租能找到女朋友。</p><h3 id="中介"><a href="#中介" class="headerlink" title="中介"></a><strong>中介</strong></h3><p>一定要找个靠谱的中介，你第六感有一丝丝不舒服就不要租了（比如看起来就不厚道的人），因为不知道以后还有什么狗血的事发生，进入的社会才发现，很多的人不要脸的没有底线，无底洞。高素质的人没必要惹这个气受。如果你觉得中介不给办事但是自己可以解决的话，还是建议不要浪费这个时间，北漂人时间很宝贵，不管你是 996 还是 965。</p><blockquote><p>比较靠谱的:</p></blockquote><ul><li>链家 （整租，一个月中介费）</li><li>我爱我家</li><li>暖房小程序</li><li>豆瓣租房</li><li>小红书好像也行（现在中介也很多了）</li><li>自如（但是房东租客两头吃，但是大家都图省心忍气了）</li></ul><p>据说假房源很多，低价吸引的：</p><ul><li>安居客</li><li>58</li></ul><p>哪个中介都会有一些负面信息，房东直租也有坑，记得用法律手段保护自己，50 块开个民事诉讼之类的。</p><p><strong>慎选小中介！！！！！！不要先交定金</strong></p><p>（如何投诉黑中介，待补充）</p><h3 id="房东"><a href="#房东" class="headerlink" title="房东"></a><strong>房东</strong></h3><p>如果是和房东手里租，查好房产证和房东身份证，留作证据。</p><p>从中介手里租的话，随便怼房东吧。（不清楚房东和中介的 py 交易是怎么样的）</p><p>也遇到过冬天暖气坏了房东不给换的情况，在行业无良的中介和房东很多，纯看运气。</p><h3 id="不要怕麻烦别人"><a href="#不要怕麻烦别人" class="headerlink" title="不要怕麻烦别人"></a><strong>不要怕麻烦别人</strong></h3><p>公共区域基本都是先来先占用，不需要过于考虑其他人感受，毕竟大家都可以放东西，别太乱到过分就行。毕竟花钱租了房子，说话硬气点。记得查好水电价格。</p><p>每个人的忍耐程度不同，不要人云亦云，说你事多的直接怼回去，毕竟都是试探对方底限。别委屈自己迁就别人。</p><h3 id="最后关于-GTD"><a href="#最后关于-GTD" class="headerlink" title="最后关于 GTD"></a><strong>最后关于 GTD</strong></h3><p>跟你说 GTD 没用的都是没做好的，靠脑子真是记不住，冥想+GTD+复盘，形成规律，别在决策上浪费时间。</p><p>北漂人很忙，但是不要慌张。</p>]]></content>
    
    
    <summary type="html">北漂租房经验总结与实用心得分享</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%8C%97%E6%BC%82/"/>
    
    
    <category term="租房" scheme="https://blog.no-claw.com/tags/%E7%A7%9F%E6%88%BF/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/tags/%E5%8C%97%E6%BC%82/"/>
    
  </entry>
  
  <entry>
    <title>2022年6月2日 北京医院拒诊（中日友好医院）</title>
    <link href="https://blog.no-claw.com/posts/69b5547e/"/>
    <id>https://blog.no-claw.com/posts/69b5547e/</id>
    <published>2022-04-22T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>凌晨 5 点 50.大概是被疼醒，流了一夜的血，床单被罩都是血，昨晚处理伤口到 1 点半，伤口有些深，于是跟领导请好假，和同事交接好今天手头的活，于是开始在各个医院打电话。</p><p>太早了没几个电话能打通，无奈只能 120 你要救护车吗，我们只管派救护车。高德上电话基本打不通，114 很不耐烦的说已经查了三个电话了，直接转了机器报号。附近药店只卖药不处理伤口。打了一个多小时电话，终于有个电话能打通了。这几天没出小区就没做核算，也防止交叉感染。中日友好医院，给了三个方案：1. 120 直接能进，显然我神志清醒，不用这个 2. 去发热门诊，等着核酸结果，核酸结果出来不能走 3. 等核酸结果出来再看病，估计我也快失血过多了，要是昏倒在路边估计也没人搭理。没办法，硬着头皮打车去医院，于是挂号，门诊发了 N95，说我万一是阳性这就得关门了，没办法只能说拿人头担保。</p><span id="more"></span><p>发热门诊要求了核算和流感抗原，好说好商量下，门诊某大夫说已经通知外科大夫过来了，等了好久，外科那边只有一个大夫，说病人多，有空过来，于是问多久能过来，有没有给个时间什么的，回答是没有，体制里的托词，懂得都懂，看来要把我一直晒这了，没办法只能继续磨门诊大夫，反正门诊这人也不多。过了半个多小时，某大夫给写个了条，告诉我直接过去门诊外科就行。一瘸一拐找了半天路，敲开外科大夫的门，礼貌说着刚才的情况，外科科室 1 里面只有一个男大夫，大概四五十岁的样子，因为刚刚发热门诊大夫已经打过招呼了，于是简单说明情况，对方一副不耐烦的样子，哪个门诊？哪个某大夫？然后把我赶出诊室，没办法只能给门诊某大夫打电话，外科大夫在电话里跟某大夫说，意思病人没有持续性出血，有纱布包扎止血，等核酸结果吧。其实纱布是我自己出门简单包扎的，给大夫看伤口时，纱布往下掉了点，很多干的血迹，裤腿有点紧，还没掀到伤口那，大夫扭头就回诊室了，扔下一句，你这没有持续性出血，俨然一副趾高气扬的样子。我的腿还流着血，我也不知道还有没有命等核酸结果出来。于是质问他是不是今天今天我死在这你也拒诊。“是，我不能违反国家规定，没有 48 小时核酸不能进手术室”说完大夫扭头就进屋了，和他确认刚才说的话，他似乎也察觉到了说错了话，转过头就不认了，就说国家规定什么的。录音我会上报有关媒体，之前这样的事已经民怨载道了。保安凑过来了，目光盯着那个医生，我的腿还流着血，我尝试跟保安解释，这个医院是不能呆了，就没想好好给治，再来个公报私仇犯不上。估计大家都看新闻了，也没人出来拦着我。后悔当时没有直接报警。或者不讲中文用日语跟他们说。</p><p>我也是第一次有底气敢和人吵架，没想竟然在三甲医院。古语讲医门多疾。现在的医生不要求有医德，不要求跟病人好好说话，能把本质工作做好就可以去了，不要求不给开太贵的药。当然不是针对所有医院，仅仅用这个医生举例子，大家结合自己经历自行脑补就好。自疫情开始，多少人因为医院的政策问题被拒在门外，本人命大两次死里逃生，上次是打疫苗之后发烧 39 度，窗外下着暴雨打着雷，我要去治疗发烧却因为发烧不能看病。否则需要做抗原四项，一共 770。再等 4-6 小时出结果，期间不能离开，发烧 39 度在急诊那个环境呆半天估计是直接去另外的世界了。一年之内就遇到两起拒诊案件，其他人呢？因此去那个世界的人呢？和变相杀人有什么区别？不作为故意杀人罪可以进局子了吧。这种没有医德乃至道德的人直接革职查办，行政拘留吧。（不作为故意杀人：处死刑、无期徒刑或者十年以上有期徒刑;情节较轻的，处三年以上十年以下有期徒刑）</p><p>中日友好医院是压死骆驼的最后一根稻草，北京又如何，上海又如何？6 月 1 日，上海刚刚解封。6 月 2 日北京就重复上演这样的事情，把国家政策当挡箭牌，把百姓的命不当回事。自古有之敢冒天下之大不韪，杀伐决断，后来的人称之为任侠。古来救死扶伤，而今一个个伪君子，在其位不谋其事。</p><p>发文可能会被网暴，键盘侠从来不会关心你过的好不好，放心我不会自杀。不想做一个忍气吞声苟活于世的人，洪流之下，很多人都活成了龙啸云。</p><p>医院不是法外之地，还轮不到地方自己立规矩。</p>]]></content>
    
    
    <summary type="html">记录 2022 年疫情期间北京中日友好医院拒诊的经历</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E5%8C%97%E6%BC%82/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
    <category term="北漂" scheme="https://blog.no-claw.com/tags/%E5%8C%97%E6%BC%82/"/>
    
  </entry>
  
  <entry>
    <title>小白零基础线上配镜，拔草蔡司。</title>
    <link href="https://blog.no-claw.com/posts/b97d7b57/"/>
    <id>https://blog.no-claw.com/posts/b97d7b57/</id>
    <published>2021-10-07T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="购买理由"><a href="#购买理由" class="headerlink" title="购买理由"></a>购买理由</h2><p>网络越来越发达了，很多事情都可以在网上完成，学习外卖快递什么的比以前方便多了，甚至配眼镜也可以了。希望有一天可以在线吃饭吧。</p><h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><p>先谈一下线上配眼镜的优缺点吧:</p><p><strong>优点：</strong></p><ul><li>便宜，线下普通眼睛的钱就能配到蔡司的镜片</li><li>可以熟悉配眼镜的流程，防止日后被坑 <span id="more"></span></li></ul><p><strong>缺点：</strong></p><ul><li>需要线下验光，线下有些家不提供验光或者验光度数不准确</li><li>眼睛框可能带着不舒服</li><li>需要 3-7 天才能拿到成品，没有备用眼镜的话很难受</li></ul><p>几个月前被种草蔡司镜片，在蔡司睛选小程序上询问最普通的莲花膜也要上千，更何况我每天超过 10 小时面对电子屏幕，必须要蓝光膜，姑且不说是不是智商税，起码图个心里安慰，不要以为有蓝光膜就可以随便作了，眼睛病了也是病啊，就跟不好好吃饭等于慢性自杀一样。</p><p>找了几篇帖子，最终在 1688 上选择了一家深圳的眼镜店，我配的是钻立方的蓝光膜，应该是比较入门的一款镜片，毕竟第一次尝试，打算成功了后下一个眼镜直接上顶配。实测一周后发现，除了选镜框的时候没注意到尺寸有点小之外，其他的都算不错。基本流程就是，选镜框-验光-下单镜片-邮寄镜框-镜框加工。</p><h3 id="镜框"><a href="#镜框" class="headerlink" title="镜框"></a>镜框</h3><p>镜框由于经费原因没有选择蔡司原厂，在京东上选了个商务风格纯钛材质了。品牌是精工，日本的老牌子了，眼镜店里的品牌在网上都搜不到，索性买了大品牌风格类似的。</p><p>京东下单，不过还需要调货，精工店比宝岛店还便宜一些。对这镜子试了试觉得风格挺好看就联系顺丰邮寄走</p><p>了，邮费 18 大洋。</p><p>后期收到货的是时候才发现同样的镜框，近视镜和平镜的感觉是不一样的，也可能是第一次用半框，总会看见边框。</p><h3 id="验光"><a href="#验光" class="headerlink" title="验光"></a>验光</h3><p>验光是在宝岛眼镜，搜索公众号选择验光师就可以了(不要选配镜师)，之后的数据会同步到小程序上，验光的费用是 50,个人觉得还是挺划算的，毕竟一堆参数，验光师操作一堆设备操作起码半个小时，再加上我问了一堆问题。当然也不排除有些店嫌麻烦不提供验光服务的，去之前要电话沟通好。当然电话里说 50 一次然后去店里不给验光的也有。</p><p>如果想货比三家防止度数不准确的话，那么就不要去同一个连锁店了。毕竟他们都可以查到数据……</p><p>总之，宝岛还是挺靠谱的。</p><h3 id="镜片"><a href="#镜片" class="headerlink" title="镜片"></a>镜片</h3><p>镜片根据需要下单就可以了，上边的加个是一片的，记得数量要选 2,然后还有加工费，算上这些差不多是线下的三分之一。加工之前卖家会确定下镜框和度数，剩下的就是加工时间和物流的时间了，顺丰发出。顺便吐槽下顺丰没有之前快了。</p><h3 id="收货"><a href="#收货" class="headerlink" title="收货"></a><strong>收货</strong></h3><p>收到的第一感觉是轻，毕竟配眼镜成功的标准就是感觉不到它的存在。后来觉得我的树脂镜框就像一块石头压在了脑袋上。</p><p>除了天没亮剩下的都亮了的感觉。</p><h3 id="如何保养"><a href="#如何保养" class="headerlink" title="如何保养"></a>如何保养</h3><p>保养的话，买了美的超声波清洗仪，190 入手。当然也可以洗假牙手表珠宝什么的。没有精力每天去洗，家里又多了一个长期吃灰的设备。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总体来看的话，还算挺成功的一次配镜。毕竟有句话叫做能用钱搞定的不要花是时间。懒人不止一次的为这个社会带来变革，希望有一天穷人也可以吧。</p>]]></content>
    
    
    <summary type="html">零基础线上配镜体验，蔡司镜片选购与配镜全流程分享</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>关键20小时</title>
    <link href="https://blog.no-claw.com/posts/614ff5d2/"/>
    <id>https://blog.no-claw.com/posts/614ff5d2/</id>
    <published>2021-01-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h4 id="快速学习"><a href="#快速学习" class="headerlink" title="快速学习"></a>快速学习</h4><p>●分解步骤——把技能做最大程度的细分，分成若干小步骤。</p><p>●充分学习——对每个小步骤进行充分学习，以便进行灵活的练<br>习，并在练习中自我纠正。</p><p>●克服困难——克服在练习中出现的生理、心理或者情绪上的障<br>碍。</p><p>●集中练习——至少用20小时集中学习最重要的小步骤。</p><h4 id="技能习得"><a href="#技能习得" class="headerlink" title="技能习得"></a>技能习得</h4><h5 id="1-早期认知。"><a href="#1-早期认知。" class="headerlink" title="1.早期认知。"></a>1.早期认知。<span id="more"></span></h5><p>了解你即将学习的技能是什么，探索研究，想想整<br>个过程，把技能细分为几个可以控制的小步骤。</p><h5 id="2-中期联想。"><a href="#2-中期联想。" class="headerlink" title="2.中期联想。"></a>2.中期联想。</h5><p>训练、注意环境反馈，根据反馈调整方法。</p><h5 id="3-后期自主训练。"><a href="#3-后期自主训练。" class="headerlink" title="3.后期自主训练。"></a>3.后期自主训练。</h5><p>不用怎么有意识地注意方法步骤就可以很自如地开展技能训练</p><h4 id="速习得技能的10个方法"><a href="#速习得技能的10个方法" class="headerlink" title="速习得技能的10个方法"></a>速习得技能的10个方法</h4><h5 id="1-选择方向；"><a href="#1-选择方向；" class="headerlink" title="1.选择方向；"></a>1.选择方向；</h5><p>如果你优先学习你感兴趣的事，那么你肯定花不了多少时间就能学成。</p><h5 id="2-集中精力；"><a href="#2-集中精力；" class="headerlink" title="2.集中精力；"></a>2.集中精力；</h5><p>“集中精力，只学一门”对快速习得技能的重要性是不言而喻的。<br>当然，这并不意味着你把其他技能拒之门外，只是你暂时将之搁<br>置，等待未来重拾。</p><h5 id="3-制定目标；"><a href="#3-制定目标；" class="headerlink" title="3.制定目标；"></a>3.制定目标；</h5><p>你的目标制定得越轻松，掌握相关技能的速度就越快。快速习得技能的目的并不是要尽善尽美，成为世界级大师，而是要在兼顾能力和效率的同时，快速提高<br>技能。</p><h5 id="4-分解技能；"><a href="#4-分解技能；" class="headerlink" title="4.分解技能；"></a>4.分解技能；</h5><p>我们学习的大部分技能都需要细化步骤。当定下想学习的技能后，就该把这项技能细化为若干步骤。学会对步骤进行筛选。把关键的步骤先找出来，再集中时间和精力去学习。</p><h5 id="5-获得工具；"><a href="#5-获得工具；" class="headerlink" title="5.获得工具；"></a>5.获得工具；</h5><p>对于某些技能而言，只有获得了必备工具，我们才可能最大限度地利用时间充分学习。</p><h5 id="6-扫除障碍；"><a href="#6-扫除障碍；" class="headerlink" title="6.扫除障碍；"></a>6.扫除障碍；</h5><p>●训练前的准备工作。例如，训练前找不到工具放哪儿了；训练前<br>还没有选到合适的工具；忽略训练的必要条件。<br>●使用临时训练工具。例如，借别人的装备，也就是说，你使用的<br>器材是有时间限制的，随时会被要求归还。<br>●环境干扰。例如，开着的电视；突然响起的电话；刚刚收到的电<br>子邮件。<br>●情绪障碍。例如，害怕；怀疑；害羞。</p><h5 id="7-腾出时间；"><a href="#7-腾出时间；" class="headerlink" title="7.腾出时间；"></a>7.腾出时间；</h5><p>腾出时间,建议你下决心保证20小时的训练量。一旦训练开始，就别停下来。如果中途卡住了，一定要坚持住，直到达到20小时这个初级训练目标。如果你没有毅力投入20小时，那么请你放弃。</p><h5 id="8-及时反馈；"><a href="#8-及时反馈；" class="headerlink" title="8.及时反馈；"></a>8.及时反馈；</h5><p>快速反馈有助于快速习得技能。如果反馈及时或只有一点点延迟，我们会更容易把实际操作情况和目前所获得的结果联系起来加以分析，再做出适当的调整</p><h5 id="9-计时训练；"><a href="#9-计时训练；" class="headerlink" title="9.计时训练；"></a>9.计时训练；</h5><p>初学一门新技能时，往往容易过分估计实际投入训练的时间。特别是当你状态不佳时，你会发现训练时间过得像蜗牛爬一样，慢慢吞吞。事实上，你实际训练的时间远没有自我感觉的时间那么长。你只需遵守一个规则：一旦计时开始，中途一定不能停下来。记住：在你练得有点泄气时，这个方法会让你更加轻松地完成更为持久的训练。<br>持续训练的时间越长，技能习得就越快。每天腾出时间做3~5次这样的分段计时训练，短时间内就会看到明显步。</p><h5 id="10-数量速度。"><a href="#10-数量速度。" class="headerlink" title="10.数量速度。"></a>10.数量速度。</h5><p>刚开始习得新技能时，人们往往希望做得尽善尽美，但力求完美很容易让人产生挫败感。因为我们永远都不可能做到百分之百的完美。别去设想有完美的表现，保持良好状态的同时，保证训练量和训练速度才是最应该考虑的。练就一门技能必须用心，必须坚持。刚开始学习时，不要盲目追求质量，相反，必要的训练量和训练速度才是制胜法宝。练得多，练得快，才能学得快。</p><h4 id="有效学习的10个方法"><a href="#有效学习的10个方法" class="headerlink" title="有效学习的10个方法"></a>有效学习的10个方法</h4><h5 id="1-收集信息；"><a href="#1-收集信息；" class="headerlink" title="1.收集信息；"></a>1.收集信息；</h5><p>实践前，查阅一下和这门技能相关的信息是十分必要的。例如，<br>花20分钟的时间上上网、去书店或者去住所附近的图书馆找找<br>相关参考书和资料。总之，你应该想方设法地找到关于这门技能<br>的参考书（至少三本）、教学DVD、教学课本或者别的学习资料。</p><h5 id="2-克服困难；"><a href="#2-克服困难；" class="headerlink" title="2.克服困难；"></a>2.克服困难；</h5><p>在早期研究中，参考资料里有很多让人疑惑不解的地方，有的是<br>概念，有的是方法，有的是理念。通常情况下，你很清楚它们的<br>重要性，可是你始终无法理解它们的意思。既无法理解为什么要<br>这么说，也无法理解为什么要这么做。别紧张，有困惑很正常。</p><p>不愿克服困难是影响技能习得快慢的主要因素。愚蠢的感觉的<br>确不好受，不过请你随时提醒自己：随着技能实践的不断深入，<br>你会慢慢解决这些疑惑，最终理解这门技能</p><h5 id="3-关联类比；"><a href="#3-关联类比；" class="headerlink" title="3.关联类比；"></a>3.关联类比；</h5><p>不愿克服困难是影响技能习得快慢的主要因素。愚蠢的感觉的<br>确不好受，不过请你随时提醒自己：随着技能实践的不断深入，<br>你会慢慢解决这些疑惑，最终理解这门技能</p><h5 id="4-逆向思维；"><a href="#4-逆向思维；" class="headerlink" title="4.逆向思维；"></a>4.逆向思维；</h5><p>学习新技能前，别去幻想你会学得多么完美。多设想一下最坏的<br>结局吧！</p><h5 id="5-咨询交流；"><a href="#5-咨询交流；" class="headerlink" title="5.咨询交流；"></a>5.咨询交流；</h5><p>投入时间和精力学习新技能前，有必要和内行聊一聊。<br>这样我们可以提前预知技能训练的每个阶段会遇到的情况，从<br>而消除对技能学习的疑虑和误解，使我们在技能训练的过程中<br>不但不会灰心丧气，反而会更有兴趣坚持下去。</p><h5 id="6-排除干扰；"><a href="#6-排除干扰；" class="headerlink" title="6.排除干扰；"></a>6.排除干扰；</h5><p>干扰因素越少，技能习得就越有效。</p><h5 id="7-间隔重复；"><a href="#7-间隔重复；" class="headerlink" title="7.间隔重复；"></a>7.间隔重复；</h5><p>“间隔重复”是一个很好的记忆方法，它可以帮助我们定期且系统<br>地回顾所获取的知识和信息。对于那些记忆起来有难度的信息，<br>我们更要经常复习，而对于那些记忆起来相对简单的旧知识，我<br>们不必经常复习</p><p>推荐：Anki  SuperMemo  Smartr</p><h5 id="8-创建定式；"><a href="#8-创建定式；" class="headerlink" title="8.创建定式；"></a>8.创建定式；</h5><p>大多数技能学习都有一套固定的模式：确定项目、着手准备、坚<br>持学习等。建立一套简单的定式可以让我们比较轻松地了解其<br>中的关键环节。罗列清单方便我们记住学习要点，使技能训练的流程更加系统<br>化，以便我们把精力投入到关键环节上。创建定式确保每次训练都有一套固定的模式。</p><h5 id="9-预期测试；"><a href="#9-预期测试；" class="headerlink" title="9.预期测试；"></a>9.预期测试；</h5><p>预期测试是指依靠已知经验，在尝试实践前假设接下来会发生<br>的变化或者产生的结果。如果你养成了预期测试的习惯，那么你<br>学技能会更高效。科学地讲，预期测试结果随着以下四个因素的<br>变化而变化</p><p>●观察——你最近在关注什么？<br>●经历——你对这个领域了解多少？<br>●假设——怎样做会才会更进一步？<br>●测试——下一步你有什么新尝试？</p><p>我建议用一个笔记本或者其他的工具记录你在训练中做出的假<br>设。如果你不断思考这些假设，并且形成新的想法，那么你的实<br>验成果会更加丰硕。</p><h5 id="10-尊重生理。"><a href="#10-尊重生理。" class="headerlink" title="10.尊重生理。"></a>10.尊重生理。</h5><p>我们的大脑和身体都处在各自的生理系统中，因此，它们都有生<br>理需求，比如食物、水、锻炼、休息、睡眠。我们不能把自己逼得<br>太紧，这样会适得其反。因为大脑和身体在没有足够能量储备的<br>情况下是不可能高效运转的</p><p>最佳学习周期是90分钟左右，在这个周期内，人的精力是最集中的。大脑和身体都<br>需要一个自然的休息。因此，我们要瞄准时机锻炼、休息、吃饭、<br>享用零食、打盹儿或做别的事情。</p><p>定期腾出时间练习，这就是练习的诀窍。</p>]]></content>
    
    
    <summary type="html">《关键20小时》读书笔记，快速学习的核心方法：分解步骤、充分学习、自我纠正。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="效率" scheme="https://blog.no-claw.com/tags/%E6%95%88%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>代码之髓</title>
    <link href="https://blog.no-claw.com/posts/fc64123d/"/>
    <id>https://blog.no-claw.com/posts/fc64123d/</id>
    <published>2021-01-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h4 id="1。-从需要的地方开始阅读"><a href="#1。-从需要的地方开始阅读" class="headerlink" title="1。 从需要的地方开始阅读"></a>1。 从需要的地方开始阅读</h4><h4 id="2。-先掌握概念再细读"><a href="#2。-先掌握概念再细读" class="headerlink" title="2。 先掌握概念再细读"></a>2。 先掌握概念再细读</h4><h4 id="3。-如果以上不奏效，从头开始手抄。"><a href="#3。-如果以上不奏效，从头开始手抄。" class="headerlink" title="3。 如果以上不奏效，从头开始手抄。"></a>3。 如果以上不奏效，从头开始手抄。</h4><p>按照时间间隔来衡量学习效果，每隔 25 分钟看学习了多少。</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230213180300265.png" alt="单核工作法"></p>]]></content>
    
    
    <summary type="html">《代码之髓》读书笔记，三种代码阅读方法：按需阅读、先概念后细节、手抄代码。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="读书" scheme="https://blog.no-claw.com/tags/%E8%AF%BB%E4%B9%A6/"/>
    
  </entry>
  
  <entry>
    <title>效率（忘记哪里摘抄的了</title>
    <link href="https://blog.no-claw.com/posts/26314a8/"/>
    <id>https://blog.no-claw.com/posts/26314a8/</id>
    <published>2021-01-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-两分钟法则"><a href="#1-两分钟法则" class="headerlink" title="1.两分钟法则"></a>1.两分钟法则</h3><p>如果一件事可以在两分钟内完成，比如回复邮件，一个简单的家务，就立即完成，因为记住它、计划时间、在后来完成它，可能会花费五分钟甚至更多的时间。</p><h3 id="2、五分钟法则"><a href="#2、五分钟法则" class="headerlink" title="2、五分钟法则"></a>2、五分钟法则</h3><p>拖延症是影响工作效率的大魁首之一，想要治愈它不是给自己定下目标、下定决心去完成一项大任务，而是只在那件事上花五分钟。你会发现在大多数情况下，它在超出五分钟的时候依旧很顺利，因为你已经进入了平坦期。</p><h3 id="3、不要坚信自己的记忆力很强悍"><a href="#3、不要坚信自己的记忆力很强悍" class="headerlink" title="3、不要坚信自己的记忆力很强悍"></a>3、不要坚信自己的记忆力很强悍</h3><p>即使你是一个记忆天才，也要把每件事都从你的脑子里清理出来，并进行记录，方法不重要，可以把它们写在一个笔记本里，也可以放进专门的应用里等等。 <span id="more"></span></p><h3 id="4、30分钟专注于一件事"><a href="#4、30分钟专注于一件事" class="headerlink" title="4、30分钟专注于一件事"></a>4、30分钟专注于一件事</h3><p>在30分钟内只做手头上的事，不去理会其他任何事，没有电话、没有邮件、不说话、不看QQ消息提醒，除非发生火灾。当然，如果老板找你谈话例外，这是没办法的事。</p><h3 id="5、尽量保持睡眠时间，白天小睡"><a href="#5、尽量保持睡眠时间，白天小睡" class="headerlink" title="5、尽量保持睡眠时间，白天小睡"></a>5、尽量保持睡眠时间，白天小睡</h3><p>睡眠少可能导致许多能力退化，不是危言耸听，美国军方研究表明，“每天少睡 1 小时持续一周会导致相当于 0.1 血醇水平的认知退化”。</p><p>《睡眠的秘密世界》指出：熬夜之后无论白天你干得有多好，情绪也不会太高。更重要的是前瞻性思考能力、行动的意愿、对冲动的抑制力、乐观程度、同理心、情商等也会下降。</p><p>所以，白天小睡真的是个好习惯，你没有发现爱熬夜的自己变笨了吗？</p><h3 id="6、总是戴着耳机"><a href="#6、总是戴着耳机" class="headerlink" title="6、总是戴着耳机"></a>6、总是戴着耳机</h3><p>戴着耳机不一定在听音乐，这可以防止别人接近你、打扰你。有些人听音乐时工作效率更高，比如小编。</p><h3 id="7、程序员离开手机一会儿真的没关系"><a href="#7、程序员离开手机一会儿真的没关系" class="headerlink" title="7、程序员离开手机一会儿真的没关系"></a>7、程序员离开手机一会儿真的没关系</h3><p>工作时把手机调成静音，放在眼睛看不到的地方，可以分批集中时间处理电话短信。</p><p>还有邮箱也一样，不用一直去关注，特别是放在早上和晚上处理，把需要解决的和代办事件列表连在一起，有用的存档，做到邮箱清零。</p><h3 id="8、MI3"><a href="#8、MI3" class="headerlink" title="8、MI3"></a>8、MI3</h3><p>早上理出最重要的三件事，或是改成一件必做的，3件应该做的，或是五件可以做的，然后开始做最重要的一件事。不要认为意志力可以解决一切，把最重要的事情放在早晨做，并且尽可能地让所有事情自动化（委托，分批等等）。</p><h3 id="9、有计划的拖延"><a href="#9、有计划的拖延" class="headerlink" title="9、有计划的拖延"></a>9、有计划的拖延</h3><p>你的大脑需要休息，并且有时候新一期的绿箭侠可以比最好的TED演讲创造更大的奇迹。</p><h3 id="10、假装无能"><a href="#10、假装无能" class="headerlink" title="10、假装无能"></a>10、假装无能</h3><p>有时候假装无能能让你更有精力去展示自己的能力。</p><h3 id="11、不追求完美，程序员更不需要"><a href="#11、不追求完美，程序员更不需要" class="headerlink" title="11、不追求完美，程序员更不需要"></a>11、不追求完美，程序员更不需要</h3><p>达尔豪斯大学的心理学教授 Simon Sherry 的完美主义与生产力研究发现，完美主义是生产力的绊脚石：</p><p>完美主义者完成任务需要花费更多的时间。</p><p>完美主义者因此等待完美时刻而耽搁。就商业而言，如果你等到了完美时刻时间已经太迟。</p><p>完美主义往往因为一叶障目而不见泰山，因为过于关注小事情而错失了大场面。</p><p>所以，真的，差不多就行了。</p><p>最后，还有一个方法就是把信用卡刷爆，当你觉得对工作不满意、没动力、效率不高时，去把你的信用卡刷爆，绝对鸡血满满，这个方法只为大家开心一下，要是这样还没动力：</p><p>“吃苦耐劳”真的是优良品质吗，与你怎么做相比，老板们应该更关心你做了什么、达到的效果。所以，效率，还是效率，希望这些实用小技巧对大家有所帮助。</p>]]></content>
    
    
    <summary type="html">效率提升技巧摘抄，两分钟法则、五分钟法则等实用时间管理方法。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="读书" scheme="https://blog.no-claw.com/tags/%E8%AF%BB%E4%B9%A6/"/>
    
  </entry>
  
  <entry>
    <title>自控力</title>
    <link href="https://blog.no-claw.com/posts/ca2dc828/"/>
    <id>https://blog.no-claw.com/posts/ca2dc828/</id>
    <published>2021-01-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>重读《自控力》，几点感受如下：</p><h3 id="1-意志力是可以消耗的、"><a href="#1-意志力是可以消耗的、" class="headerlink" title="1.意志力是可以消耗的、"></a>1.意志力是可以消耗的、</h3><p>意志力像肌肉一样，是可以消耗的。</p><h3 id="2-容忍罪恶，自我救赎"><a href="#2-容忍罪恶，自我救赎" class="headerlink" title="2.容忍罪恶，自我救赎"></a>2.容忍罪恶，自我救赎</h3><p>中国自古以来的耻感文化不利于自我救赎，会造成轻微的心理创伤，时间长容易患抑郁症，什么都不想干就是对当前的行为表示不满，并且没有更好的方法。 <span id="more"></span></p><h3 id="3-意志力会传染"><a href="#3-意志力会传染" class="headerlink" title="3.意志力会传染"></a>3.意志力会传染</h3><p>一个好的团体至关重要。</p><h3 id="4-我不要的我力量"><a href="#4-我不要的我力量" class="headerlink" title="4.我不要的我力量"></a>4.我不要的我力量</h3><p>不要想一头粉色的大象，你的脑海里出现了什么呢？</p><h3 id="5-拥抱无聊"><a href="#5-拥抱无聊" class="headerlink" title="5. 拥抱无聊"></a>5. 拥抱无聊</h3><p>允许自己就这样的活着。</p>]]></content>
    
    
    <summary type="html">重读《自控力》的几点感悟，意志力像肌肉一样可以消耗也可以锻炼。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
  </entry>
  
  <entry>
    <title>记录mokee刷机</title>
    <link href="https://blog.no-claw.com/posts/a8322290/"/>
    <id>https://blog.no-claw.com/posts/a8322290/</id>
    <published>2021-01-21T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="How-does-flash-mokee-as-Android-System"><a href="#How-does-flash-mokee-as-Android-System" class="headerlink" title="How does flash mokee as Android System?"></a>How does flash mokee as Android System?</h2><p>如果你的系统是稳定版的话，可能需要先降级回到开发版</p><h3 id="0-unlock-and-image-zip-download"><a href="#0-unlock-and-image-zip-download" class="headerlink" title="0. unlock and image zip download"></a>0. unlock and image zip download</h3><p>You can use SD card or OTG USB as your image zip storage.</p><ul><li><input disabled="" type="checkbox"> mokee MK-${version}.zip (system zip)</li><li><input disabled="" type="checkbox"> Magisk-v21.0.zip (ROOT solution &amp; Universal Systemless Interface provided by John Wu)</li><li><input disabled="" type="checkbox"> open_gapps (GSM services)</li></ul><span id="more"></span><h3 id="1-twrp"><a href="#1-twrp" class="headerlink" title="1. twrp"></a>1. <a href="https://twrp.me/xiaomi/xiaomimi8.html">twrp</a></h3><h6 id="find-your-android-device"><a href="#find-your-android-device" class="headerlink" title="find your android device"></a>find your android device</h6><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">adb device</span><br></pre></td></tr></table></figure><h6 id="reboot-into-fastboot"><a href="#reboot-into-fastboot" class="headerlink" title="reboot into fastboot"></a>reboot into fastboot</h6><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">adb reboot bootloader</span><br></pre></td></tr></table></figure><h6 id="flash-twrp-into-fastboot"><a href="#flash-twrp-into-fastboot" class="headerlink" title="flash twrp into fastboot"></a>flash twrp into fastboot</h6><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">fastboot flash recovery twrp.img</span><br></pre></td></tr></table></figure><p>after flash twrp rec</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">fastboot reboot</span><br></pre></td></tr></table></figure><h3 id="2-use-image-to-flash-device"><a href="#2-use-image-to-flash-device" class="headerlink" title="2. use image to flash device"></a>2. use image to flash device</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">fastboot flash recovery twrp-x.x.x-x-x.img</span><br></pre></td></tr></table></figure><p>2023 年 mokee 已经停更了</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/image-20230124204514395.png" alt="image-20230124204514395"></p>]]></content>
    
    
    <summary type="html">记录为手机刷入 MoKee 魔趣 ROM 的完整过程</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="软件技巧" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E8%BD%AF%E4%BB%B6%E6%8A%80%E5%B7%A7/"/>
    
    
  </entry>
  
  <entry>
    <title>生活小妙招</title>
    <link href="https://blog.no-claw.com/posts/29d0960/"/>
    <id>https://blog.no-claw.com/posts/29d0960/</id>
    <published>2020-01-14T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong>### 冰箱去霜</strong></p><p>开水倒入不锈钢盆，放入冰箱，几分钟之后冰块就掉下来了 <span id="more"></span></p><p><strong>### 锅去顽渍</strong></p><p>白醋和小苏打浸泡一会就可以擦掉了，注意铁锅不能用</p><p><strong>### 玻璃水杯</strong></p><p>尽量买双层的隔热杯，防止烫手，单层玻璃不管用。</p>]]></content>
    
    
    <summary type="html">实用生活小妙招合集，冰箱去霜等家居技巧</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="碎碎念" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E7%A2%8E%E7%A2%8E%E5%BF%B5/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>吃掉那只青蛙</title>
    <link href="https://blog.no-claw.com/posts/4141dd35/"/>
    <id>https://blog.no-claw.com/posts/4141dd35/</id>
    <published>2018-10-24T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li>明確目標：確定你自己究竟想要什麼。目標清晰至關重要。在每天開始工作之前，把你的目標全部寫下來。</li><li>精心計劃：把你的想法付諸筆端。你花費在準備工作上的每一分鐘時間，都將使你在工作過程中節約 5 ～ 10 分鐘的時間。</li><li>運用 80&#x2F;20 法則：20%的活動能產生 80%結果。因此，盡量把你的精力集中在那 20%的活動上。</li><li>著眼未來：對你來說，最重要的、應該優先處理的事情就是那些將對你以後的生活或者工作產生最重要影響的事情，無論該影響是正面的還是負面的。首先全力以赴地處理這些事情。 <span id="more"></span></li><li>學會說「不」：既然你沒有時間去做所有的事情，你必須學會推遲處理那些沒有什麼意義的工作，從而騰出時間來處理那些少數的、重要的工作。</li><li>使用 ABCDE 法：根據自己列出的工作清單開始工作之前，先花費一點兒時間，根據輕重緩急對這些事情進行安排，從而確保你先處理最重要、最有價值的事情。</li><li>抓住關鍵：要想圓滿完成自己的工作，你必須具備哪些能力和技能？找出這個問題的答案，然後通過日復一日的努力來解決上述問題。</li><li>抓大放小：找出你工作中最重要的三件事情，你對公司 90%的貢獻都來自這三件事情。無論如何，先把這些事情做好，然後，你才能騰出更多的時間來安排你的生活。</li><li>精心準備：著手工作之前，先把一切都準備就緒：所有的資料、信息、工具、辦公用品，以及你可能用得到的數據，然後，你就可以全心全意地工作了。</li><li>循序漸進：如果你下定決心一步一個腳印地前進，那你就能完成最艱巨、最複雜的工作。</li><li>精益求精：你在自己的領域內知識越淵博，技能越嫻熟，你動手就越快，任務完成得就越早。</li><li>施展才華：確定你最擅長做什麼事情，或者說你能把什麼事情做得最好，然後全身心地投入這些工作，精益求精。</li><li>突破瓶頸：確定影響你實現自己目標的瓶頸所在，或者是主要障礙，無論是來自外界還是來自自身，然後集中精力消除這些障礙。</li><li>自我施壓：假設你即將離開你所在的城市，外出一個月的時間，離開之前必須把所有重要的工作都處理完。</li><li>挖掘潛能：確定你每天什麼時候處於腦力和體力的最佳狀態，然後據此安排自己一天的工作，盡量在這段時間裡處理那些最重要、最緊迫的任務。要注意充分休息，從而保證最佳狀態。</li><li>說幹就幹：做你自己的拉拉隊隊長。在任何情況下，都尋求事情積極的一面。把精力集中在如何解決問題上，而不是問題本身。要保持樂觀向上的態度。</li><li>避開科技陷阱：你可以利用高科技來提高自己的通訊質量，但是注意不要讓自己成為科技的奴隸。要學會偶爾把所有的通訊設備都關掉，切斷自己與外界的聯繫。</li><li>化整為零：把複雜而又艱巨的大任務分割開來，變成許多部分，每次只處理一小部分。</li><li>創造大塊時間：為自己安排一份日程表，然後集中精力用那些大塊的時間來處理對你來說最重要的事情。</li><li>培養緊迫感：養成迅速處理重要工作的習慣。把自己培養成一個能迅速、圓滿完成工作的人。</li><li>全力以赴：根據事情的輕重緩急來安排優先處理的事情，然後立即著手處理最重要的、必須優先處理的事情，全心全意地去做這件事情，決不中途停止，直到百分之百地完成為止。這是工作高效、業績突出的關鍵所在。</li><li>下定決心，每天都實施這些規則，直到它們成為你的第二本性。一旦你在時間管理方面養成上述習慣，使之成為自己個性中一個不可分割的部分，你的前途將不可限量。說做就做！吃掉那只青蛙！</li></ol>]]></content>
    
    
    <summary type="html">《吃掉那只青蛙》读书笔记，21 条时间管理法则，从 80/20 法则到 ABCDE 优先级排序。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="效率" scheme="https://blog.no-claw.com/tags/%E6%95%88%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>驯服头脑中的野兽</title>
    <link href="https://blog.no-claw.com/posts/101d9cf0/"/>
    <id>https://blog.no-claw.com/posts/101d9cf0/</id>
    <published>2018-10-24T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="野兽特征"><a href="#野兽特征" class="headerlink" title="野兽特征"></a>野兽特征</h1><ol><li>第一点<ol><li>讨厌难的事情 （为了防止浪费能量）</li><li>对所以刺激都有反应 （并行处理）</li><li>力量大 （处理速度快）</li></ol></li></ol><h3 id="驯兽师"><a href="#驯兽师" class="headerlink" title="驯兽师"></a>驯兽师</h3><ol><li>第一点<ol><li>逻辑导向 （只能串行处理）</li><li>能力消耗大 （很多依赖大脑的工作记忆）</li><li>力量弱小 <span id="more"></span></li></ol></li></ol><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><ol><li>第一点<ol><li>驯兽师无法战胜野兽。（战斗力，野兽是发散意识，驯兽师是集中记忆</li><li>世界上不存在擅长集中注意力的人。（重新来过就好）</li><li>如果能引导野兽，就能获得巨大的力量。（信息干消耗专注力）</li></ol></li></ol><h3 id="给野兽喂食"><a href="#给野兽喂食" class="headerlink" title="给野兽喂食"></a>给野兽喂食</h3><p>填饱肚子，和咖啡（减少疲劳感，专注时间会变长，150-200mg 咖啡因）</p><h4 id="和咖啡的原则"><a href="#和咖啡的原则" class="headerlink" title="和咖啡的原则"></a>和咖啡的原则</h4><ol><li>不要和太多，超过 300mg 效果消弱，400mg 产生副作用</li><li>咖啡加速牛奶或者奶油（有些人心跳加速可以缓解）</li><li>起床 90min 内不喝（防止和皮质醇冲突）</li><li>2B-Alert（请 google，一个美国的算法）</li><li>和绿茶一起喝（咖啡因 + 茶氨酸 效果提升）</li></ol><h4 id="饮食方法"><a href="#饮食方法" class="headerlink" title="饮食方法"></a>饮食方法</h4><p>补充必要营养</p><ul><li>铁、锌、镁等矿物质</li><li>维生素 D</li><li>叶酸、维生素 B12</li><li>ω-3 脂肪酸</li><li>胆碱</li><li>必需氨基酸</li><li>S-腺苷甲硫氨酸（SAM-e）</li></ul><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124151148.png"><br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124151158.png"><br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124151209.png"></p><ol><li>“MIND”饮食（多吃对大脑有益的，少吃有害的，不限制卡路里）</li><li>良好饮食习惯，记录 mind 饮食法的日期</li><li>专注力日志+记分板（记录专注多久了）<br><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124151227.png"></li></ol><h3 id="当成游戏"><a href="#当成游戏" class="headerlink" title="当成游戏"></a>当成游戏</h3><p>① 增加有效的获得回报的预感。<br>② 减少无效的获得回报的预感。</p><p>影响注意力的因素（单调乏味，难度不对）</p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124151243.png"></p><h3 id="内观和正念"><a href="#内观和正念" class="headerlink" title="内观和正念"></a>内观和正念</h3><ol><li>意志力会越用越少</li><li>控制自己会消耗能量</li><li>能力用尽无法控制</li><li>已经否定了补充糖分理论</li></ol><h4 id="观察自己"><a href="#观察自己" class="headerlink" title="观察自己"></a>观察自己</h4><ol><li>观察自己所想，比如目前状态，走路状态，</li><li>走神之后拉回来，然后继续</li><li>一次只做一件事，吃饭的时候不要刷剧</li><li>幻想平静的场景</li></ol><h3 id="工作环境"><a href="#工作环境" class="headerlink" title="工作环境"></a>工作环境</h3><ol><li>专用空间</li><li>专用设备（工作手机电脑和生活手机电脑）</li><li>账号分区（同工作和生活）</li><li>内容拦截（短信和广告）</li><li>噪音管理（降噪耳机）</li><li>自身性格（外向音乐加强效率，内向降低）</li><li>纯音乐（歌词会降低专注力）</li><li>工作间隙听音乐</li><li>适当记录情绪（比如无聊，焦虑，把情绪物质化，像什么。。。</li><li>观察情绪而不是控制</li></ol><h3 id="学会放弃和休息"><a href="#学会放弃和休息" class="headerlink" title="学会放弃和休息"></a>学会放弃和休息</h3><p>很多人会有如下误区，在众多的压力中，自责是伤害最大的。所以要自我接纳。</p><p>① 过分追求专注力。<br>② 过于责备自己没有专注力。</p><ol><li>既然事情已经发生了，接纳就好，不需要评判，只是去观察。</li><li>做一些补救的办法，尝试相关的新事物（有优势的，能学到东西的）</li></ol><h4 id="休息的方法"><a href="#休息的方法" class="headerlink" title="休息的方法"></a>休息的方法</h4><ol><li>微休息(几十秒到几分钟)</li><li>任务休息（和番茄工作法类似。不过是防止休息跑远了，在复杂任务间隙做简单任务，达到休息的目的。降低大脑运算速度。</li><li>积极休息（休息时做温和的运动，比如定期散步）</li><li>超积极休息（高专注力的多巴胺，剧烈运动释放压力）</li><li>美军睡眠法（从头到下冥想放松）</li><li>偶尔睡个午觉（自己加的，毕竟多阶段休息）</li></ol>]]></content>
    
    
    <summary type="html">《驯服头脑中的野兽》读书笔记，识别并驯服影响效率的内心野兽。</summary>
    
    
    
    <category term="读书有感" scheme="https://blog.no-claw.com/categories/%E8%AF%BB%E4%B9%A6%E6%9C%89%E6%84%9F/"/>
    
    
    <category term="效率" scheme="https://blog.no-claw.com/tags/%E6%95%88%E7%8E%87/"/>
    
  </entry>
  
  <entry>
    <title>房间</title>
    <link href="https://blog.no-claw.com/posts/e1677cc4/"/>
    <id>https://blog.no-claw.com/posts/e1677cc4/</id>
    <published>2018-02-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>他还是饶有兴味的看着戏剧。对他来说没有什么比戏剧更能打发这个黄昏了。他在想他的妻子在他身边该有多好。前一天他们吵了一架，她一气之下离家出走就再也没有回来。现在他只能在这个没有几个观众的看台下，痴痴地望着台上，当然心里想的还是他的结发妻。只不过他的戏剧能让他感觉稍微好些。</p><p>“先生，麻烦你跟我们走一趟”两个警察模样的人出现在他面前，冷冰冰的声音划破了这一沉寂。我们这位好好先生可是从来不问缘由的，更何况他觉得自己也没犯什错。甚至于老婆吵架时，他都是默默的承受着家庭暴力。</p><span id="more"></span><p>这次的目的地不是警察局，竟然是他的家里。屋中仿佛听见一男一女在吵架，女的似乎在咆哮：“看你昨天带回来的那个小男孩，再这么养下去我们都要穷死了！”男的成熟稳重，但也极富心机，他似乎都看不到那人的脸。那人说的每一句话都好似千斤：“我自有安排。”那个女人的声音听起来好像他的妻子，不过凭着他对她的相信，这一荒唐的想法很快就被否定了，哪怕他们只是新婚。就算新婚之夜他们刚刚吵了一架，他们也相信彼此都知道，两个人的完美主义还要慢慢磨合。世界上绝对没有任何人可以替代彼此的位置。本想等她气消了之后再和他一起游山玩水的。结果他发现现在像重物一样被人摔在地上，然后被拖进了一间黑屋子里。</p><p>清醒了一会，他发现这个屋子的布局竟然和他家一模一样，而且他也告诉自己，仅仅是相似而已。这个黑屋子很像是他的卧室，只不过没有了灯，整个屋子就好一间暗房。他瞪大眼睛似乎不敢相信这是真的。难道是谁要向他复仇吗？怎么选择了个这么相像的地方。他是个好好先生，但是也得罪过不少人。慢慢的，他发现这个屋子里还有其他人。角落里蹲着一个小男孩，目光有些呆滞，衣衫褴褛，头发也有些凌乱，而且无论他怎么喊，男孩就是不理他。男孩的周围，有一排排的月饼礼盒，上边贴满了个各种情绪的标签。走上前，他看见“智慧”和“勇气”，他曾经毫不犹豫的选择了前者，不过他觉得现在应该换个决定了。只不过男孩前面标有“自卑”与“抑郁”的盒子已经空了。“哎，今天可是中秋，看来今晚是很难见到月亮的了。”他自言自语道。</p><p>还好是中秋，月光可以透过这些黑暗，给每一间黑屋子带来光明。“喂，你看见前面那扇窗了吗？打开它，我们就可以出去了。”男孩仍然不理他，径直向前，甚至连头也不回。“终究还是孩子。”他一边叹着气，一边跟了过去。走了几步他就看见一个小女孩的骸骨横在前方，月光下显得格外阴森。而小男孩由于留恋外边的新鲜空气再也听不到他的声音了。就好像月光之下有塞壬的歌声似的。</p><p>他也不晓得为何清楚的记得过去了两周，大概是处于艰难险境的人对时间更为敏感吧。小男孩最终还是倒在了小女孩的身旁。“是我害了他。”不过这句话他没有说出口，因为他的身边已经没有其他人了。求生的欲望越来越强烈，回过头，门是虚掩这的，而且无论如何都无法反锁。他想过从这扇门中穿过去，只不过竟然有些怕那些人都聚集在门口，他一时间招架不住，于是还是选择跳窗。</p><p>不知道是已经没有退路还是求生的愿望过于强烈，拉开窗户，几个跟头，他就翻入了下一层。</p><p>虽然是高层，但是还是阴森森的像一个地下室。昏黄的灯光下边，照亮的几处蜘蛛网。他只有拼命的奔跑，只不过这里除了实验室还是实验室，难道这里住着一个和他一样的工作狂吗？他很清楚他这样很快就会被后边的人追上。“快过来这里，这里很安全！”走近细看，那人竟然是他的姑姑。他也来不及多想了。</p><p>暂时松了一口气，他瘫软在地上，余光扫过地面，一批批的黑影掠过。那她姑姑呢？为什么他可以随意走动？为什么她知道知道这里就是绝对安全的？为什么……难道？他不允许自己再这么想下去了。</p><p>“他就藏在这里。快点！”一个似曾相识的声音，十分钟之前是如何告诉自己这里是如何安全，现在有是如何把自己推向绝望的深渊的，当然这就是人性，他早就已经见惯，这次却疏忽大意。当然他终究是个理性的动物，抓紧门锁，外边向右转一圈，他就向左转一圈，累了就干脆锁死，就这样一直僵持着。</p><p>“无论如何我也要救我的孩子。”门外传来一阵叹息。</p><p>母亲受人驱使只是为了救子吗？他的心忽然颤了一下，他知道这种情况母爱是绝对不会讲道理的。他松开门锁，放弃了抵抗。</p><p>“你要用我来换你的孩子可以，但是我出去一定杀了你！”这话他是吼出去的。</p><p>“好！”对面毫不迟疑。</p><p>门开了。</p><p>里面走出了一个瞋目怒视的人 ，浑身散发着杀气，仿佛眼神都可以杀人。只有那个为了救儿子可以赔上性命的人还站在那里，似乎在等待最后的仲裁。现在两方都已经切身感受到，面对死亡本身就该无所畏惧。</p><p>随从的人早已经被吓破了胆，她还是义无反顾的站在那里，他死死的扼住她的咽喉，哪怕是他的长辈，哪怕帮助过他又马上背叛了他。这些他都不再想，因为他早已经被愤怒所淹没。他死死的攥住她的脖子，仿佛下一秒就能像向捏死一只蚂蚁一样捏死他。只不过，他突然松开了手。“带我去见他。你快点！”冷冰冰的语气，一点亲情的味道都没有了。她不回应，只是不停的咳嗽。</p><p>回到了之前的地方，之前的一男一女早已不见，地下只剩下一个破旧的书包，是他少年时期用过的，他解释不清楚。黑屋里走出个人影，正是先前日子倒下的小男孩，不同的是，他变得干净帅气又阳光。“是你救了我！”男孩嘴角上扬。</p><p>“我要去报警。你去自首？”这位好好先生第一次表现的这么富有攻击性。</p><p>去警局的路上，之前发生的一切好像都消失了，就连那件屋子也在慢慢的崩坏。<br>路的尽头，她的妻子在等着他，她也经历了同样的事。<br>她首先打破了这一宁静，“亲爱的我想写本书。”</p><p>“嗯？”</p><p>“你之前不是说过每个人走不出自己的世界吗？”</p><p>“那你这本书的名字就叫做《房间》好了。”</p><p>“这是一个主人格被邪恶人格囚禁的故事。”</p><p>“而且主人格终将获得胜利。”</p><p>他们彼此一次笑，又是黄昏，落日的石阶前，一切坎坷都被照的透亮。</p>]]></content>
    
    
    <summary type="html">一篇悬疑短篇小说，好好先生被带入与自家一模一样的黑屋子，在月光下寻找出路。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="梦" scheme="https://blog.no-claw.com/tags/%E6%A2%A6/"/>
    
  </entry>
  
  <entry>
    <title>佛•教化•屠杀</title>
    <link href="https://blog.no-claw.com/posts/b4d03554/"/>
    <id>https://blog.no-claw.com/posts/b4d03554/</id>
    <published>2017-10-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>东鸡冠山迷了路，误闯了田间的坟地。</p><p>以至于到了日俄监狱，那份恐惧仍旧没有消失。高高厚厚的城墙，勾起了几年前途经伪皇宫的回忆，如果说高城壁垒是为了防止犯人逃窜，那么皇宫岂不是帝王将相的牢狱？这样想着，愿生生世世莫生于帝王之家倒是一种悲哀的绝唱了。</p><p>事实上，穿过城墙，里面并没有想象中的庄严，本以为这里的冤气会压迫灵魂，让人窒息，事实上是，一路惊魂未定，却连一丝血腥气都未曾嗅到过。<span id="more"></span></p><p>监狱明显翻新过，若是复古风格，经历过枪林弹雨的山石和被抓破了的淋满殷红的墙壁才与它相称。角落里卧着的一口大钟倒是引人注意，介绍上说，这是日本帝国主义卑劣行径的体现，过去每天都有僧人到这里来超度，借以来消磨人们的反抗意志。看到这里我无意为日本开脱，若是余生充满痛苦，透过佛法来看世界倒是一个不错的选择。佛教从古印度传到中国，日本，最后成为世界三大宗教之一，自然是可以为大众消灾解祸的。这里仅仅表明立场，不做任何评价。</p><p>不过倒是想起了关于安倍的一则新闻，安倍夫妇通过初等教育渗透“爱国主义思想”，那么这些东西就像一种信仰，正在这些孩子们大脑中根深蒂固，那么与宗教，还有我们从小被培养的抗日情怀，其实都是同样一种意识形态。</p><p>整个监狱不大，猜测犯人不足一千，里面有很多过去工厂的牌坊，现在一些关闭，一些则摆放一些遗物供人们参观。大概以前犯人们就都在这里劳作。还有医务室，对于重症的病人，当然安安静静的等待他们死去。因为在这里最便宜的就是人命，人命是换取劳动果实的易耗品，当然也是供这里的人取乐的工具。把犯人双眼用黑布蒙上，让其跪在地板上，然后把脖子套进绳索，再把木板抽走，人就掉在了几米之下的木桶里，然后被几个同伴默默地抬起奔赴坟场，狱卒在后面举着短枪，准备随时扣下扳机。</p><p>复原图倒是有几具骷髅，混着朽木和尘土。当然我十年前在黑龙江省博物馆便见过真实的骨架，除了不理解为什么手心那里是凹下去的，其余并没有什么意外。小时候也埋葬过燕子，所以对骨头并不怎么畏惧，也不觉得好端端的一个人忽然变成一堆白骨有什么奇怪的地方。那个年代，人命真的是不值钱的，有些人终其一生不过吃了几颗枪子而已。</p><p>那么与那时相比，我们是否更加幸运？当然是否定的。那时的冲突是意识形态，现在则是文化断层。我们是否要缅怀先烈？那也不必，这种历史不会再重演。若是生在战争年代，那么终生梦想便是世界和平，人生安稳。现在一定与那时不一样。有人说历史一共分为三种：第一种是已经逝去的岁月，往事不可追忆；第二种写在了各类史诗典籍中，可能迫于当时时局，略加修改；那么第三种便是自己阅读各类典籍，加上自己的心得体会，认为历史应当是这个样子。那么历史是不必躺在水晶棺中任人观赏的了。与华丽相比，应该更接近于真实。</p><p>那么现在中日关系如何？我们作为小人物当然不想太清楚。国际上有一条交友准则，没有永恒的朋友，只有永恒的利益。从旅顺回到大连，写下了这样的话：</p><p>旅大是日本的情妇，被百般虐待，所有的灵感都来源于她，荣誉则是成为一份作品，而毁灭也恰好是她。</p>]]></content>
    
    
    <summary type="html">旅顺日俄监狱游记，从佛教超度到意识形态渗透，战争遗址中的历史反思。</summary>
    
    
    
    <category term="散文随笔" scheme="https://blog.no-claw.com/categories/%E6%95%A3%E6%96%87%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="游记" scheme="https://blog.no-claw.com/tags/%E6%B8%B8%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>外设常见问题汇总</title>
    <link href="https://blog.no-claw.com/posts/6f08b777/"/>
    <id>https://blog.no-claw.com/posts/6f08b777/</id>
    <published>2017-10-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="苹果外设"><a href="#苹果外设" class="headerlink" title="苹果外设"></a>苹果外设</h2><ul><li>秒控键盘和笔没有平替</li><li>有了 homepod 还需要一台手机转发，手机关机指令无效</li><li>苹果建议不低于 4K，不然屏幕发糊</li></ul><h2 id="键盘"><a href="#键盘" class="headerlink" title="键盘"></a>键盘</h2><ul><li>ikbc 有时候锁定 win 按键 [^1] 锁定 Fn+左 win   解锁 Fn+ 右 win(ikbc w200 实测)</li><li>电容键盘没电要使用软件给电容充电</li><li>NIZ plum 静电容键盘的 alt 和 win 键互换位置: 按住 Fn 和侧面标注有 Win&#x2F;Mac 的按键 3 秒以上，看到指示灯闪烁就代表成功切换。</li></ul><h2 id="typec-一线通"><a href="#typec-一线通" class="headerlink" title="typec 一线通"></a>typec 一线通</h2><ul><li>便携显示器可以使用笔记本给屏幕供电，此时显示器作为 hub</li><li>大型显示器可以使用显示器给屏幕供电，此时显示器作为 hub</li><li>c 口 ipad 也可以一线通，此时显示器作为 hub</li><li>部分安卓手机可以一线通，此时显示器作为 hub</li><li>部分小米手机能充电，能做 hub，不支持视频</li><li>显示器不支持给充电 c 宝供电</li><li>三星客服说 vesa 壁挂和支架分开的显示器不能升降旋转</li><li>大于 65W&#x2F;90W 以上的机箱不建议 typec 一线通，功率跟不上会关机</li><li>typec 用了 Dock 就不能一线通了，因为已经不是全功率了</li><li>4K 视频线和屏幕线一般可以混着用</li><li>充电线一般是 2.0，4K 线可以 3.0 满速</li></ul><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150507.png" alt="壁挂和旋转二选一"></p><h2 id="100W-充电宝和氮化镓"><a href="#100W-充电宝和氮化镓" class="headerlink" title="100W 充电宝和氮化镓"></a>100W 充电宝和氮化镓</h2><ul><li>加个设备会重新分配功率</li><li>2Wmah 充电宝给电脑只能支持俩小时</li><li>不用 apple 自家的充电头，magsafe 最多 10W</li></ul><h3 id="PC-typec-无法充电，MacOS-开机无显示器不进系统"><a href="#PC-typec-无法充电，MacOS-开机无显示器不进系统" class="headerlink" title="PC typec 无法充电，MacOS 开机无显示器不进系统"></a>PC typec 无法充电，MacOS 开机无显示器不进系统</h3><ul><li>NUC 维奇说白苹果也是没显示器不进系统</li><li>typec 无法充电试试退出运输模式</li></ul><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150547.png" alt="运输模式"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150603.png" alt="运输模式"></p><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/20230124150621.png" alt="诱骗器"></p><h3 id="电脑的坑"><a href="#电脑的坑" class="headerlink" title="电脑的坑"></a>电脑的坑</h3><ul><li>有些笔记本没有主板电池，内置电池用完了 bios 会恢复出厂设置</li><li>标称最大支持 16G&#x2F;32G 还可以插更多内存</li></ul><p><img src="https://raw.githubusercontent.com/Xu-Hardy/image-host/master/7036babdd227a619ea99d8c8ce1baee.png" alt="aida64"></p><p>[^1]: <a href="https://blog.csdn.net/norman_irsa/article/details/114735798">IKBC 键盘 Win 键失效的解决办法_NXGG 的博客-CSDN 博客_ikbc windows 键</a></p>]]></content>
    
    
    <summary type="html">盘点硬件选购和使用中常见的坑与避坑指南</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="外设" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E5%A4%96%E8%AE%BE/"/>
    
    
    <category term="外设" scheme="https://blog.no-claw.com/tags/%E5%A4%96%E8%AE%BE/"/>
    
  </entry>
  
  <entry>
    <title>身份证科普</title>
    <link href="https://blog.no-claw.com/posts/7a47d12b/"/>
    <id>https://blog.no-claw.com/posts/7a47d12b/</id>
    <published>2017-10-05T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>身份证号码是由18位数字组成的，分别表示：</p><p>　　1、前1、2位数字表示：所在省份的代码。</p><p>　　2、前3、4位数字表示：所在城市的代码。</p><span id="more"></span><p>　　3、前5、6位数字表示：所在区县的代码。</p><p>　　4、第7～14位数字表示：出生年、月、日，7、8、9、10位是年，11、12位是月，13、14位是日。</p><p>　　5、第15、16位数字表示:所在地的派出所的代码。</p><p>　　6、第17位数字表示性别:奇数表示男性，偶数表示女性。</p><p>　　7、第18位数字是校检码:校检码可以是0～9的数字，有时也用X表示。</p><p>　　8、＊尾号X是作为尾号的校检码，是由号码编制单位。X是罗马数字的10，用X来代替10，可以保证公民身份证符合国家标准。</p>]]></content>
    
    
    <summary type="html">身份证号码 18 位数字的含义与编码规则科普</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    <category term="碎碎念" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/%E7%A2%8E%E7%A2%8E%E5%BF%B5/"/>
    
    
    <category term="生活" scheme="https://blog.no-claw.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>SSD介绍</title>
    <link href="https://blog.no-claw.com/posts/2e34d332/"/>
    <id>https://blog.no-claw.com/posts/2e34d332/</id>
    <published>2017-03-09T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="slc、mlc、tlc-闪存芯片颗粒区别介绍"><a href="#slc、mlc、tlc-闪存芯片颗粒区别介绍" class="headerlink" title="slc、mlc、tlc 闪存芯片颗粒区别介绍"></a>slc、mlc、tlc 闪存芯片颗粒区别介绍</h2><p>在 U 盘、SSD 等固态存储产品中，闪存芯片颗粒是核心，其关乎产品成本、寿命以及速度。闪存芯片颗粒主要有三种类型，分别为 SLC、MLC、TLC，三者之间的区别，如下。</p><p>SLC &#x3D; Single-Level Cell<br>，即 1bit&#x2F;cell，速度快寿命长，价格贵（约 MLC 3 倍以上的价格），约 10 万次擦写寿命；</p><p>MLC &#x3D; Multi-Level<br>Cell，即 2bit&#x2F;cell，速度一般寿命一般，价格一般，约 3000—10000 次擦写寿命</p><span id="more"></span><p>TLC &#x3D;Trinary-Level Cell，即 3bit&#x2F;cell，也有 Flash 厂家叫 8LC，速度慢寿命短，价格便宜，约 500-1000 次擦写寿命。 <!-- more --></p><h5 id="1-速度最快，寿命最长，价格最贵的-SLC-颗粒"><a href="#1-速度最快，寿命最长，价格最贵的-SLC-颗粒" class="headerlink" title="1.速度最快，寿命最长，价格最贵的 SLC 颗粒"></a>1.速度最快，寿命最长，价格最贵的 SLC 颗粒</h5><p>intel 傲腾 900P 固态硬盘是英特尔针对商业客户和游戏发烧友推出高性能 SSD，它使用的就是 SLC 闪存颗粒，</p><p>持续写入速度 2.0G&#x2F;S，4K 写入 50 万 IOPS；</p><p>持续读取速度 2.4G&#x2F;S，4K 读取 55 万 IOPS；</p><p>寿命约为全盘写入 15000 次，每天全盘写入一次，预估寿命为 40 年；</p><p>SLC 闪存颗粒就像这款搭载它的傲腾 900P 一样，尽管已经是“宇宙级最强 SSD”了，但因为贵，所以销量惨淡！</p><h5 id="2-速度较快，寿命较长、价格较贵的-MLC-颗粒"><a href="#2-速度较快，寿命较长、价格较贵的-MLC-颗粒" class="headerlink" title="2.速度较快，寿命较长、价格较贵的 MLC 颗粒"></a>2.速度较快，寿命较长、价格较贵的 MLC 颗粒</h5><p>三星 960 Pro 是三星针对游戏发烧友和专业级用户推出的高性能 SSD，它使用的是 MLC 闪存颗粒，</p><p>持续写入速度是 2.0G&#x2F;S,4K 写入 33 万 IOPS;</p><p>持续读取速度是 3.2G&#x2F;S，4K 读取 33 万 IOPS；</p><p>寿命约为全盘写入 1500 次，每天全盘写入一次，预估寿命为 4 年；</p><p>对普通人用户来说，SLC 颗粒就像劳斯莱斯幻影，好是好，但是太贵，所以，极少有人买；MLC 颗粒就像宝马 7 系，车还算可以，但还是贵，买的人还是少；</p><p>只有 TLC 颗粒就像福克斯，虽然性能和品质远不如劳斯莱斯和宝马 730，但是价格实惠，所以，买的人最多；</p><h5 id="3-速度较慢，寿命较短、价格最便宜的-TLC-颗粒"><a href="#3-速度较慢，寿命较短、价格最便宜的-TLC-颗粒" class="headerlink" title="3.速度较慢，寿命较短、价格最便宜的 TLC 颗粒"></a>3.速度较慢，寿命较短、价格最便宜的 TLC 颗粒</h5><p>三星针对消费级市场推出的廉价版 SSD，虽然它采用了速度最慢的 TLC 闪存颗粒，但因为它使用了 PCIE3.0*4 通道作为数据传输通道，所以，它的读取速度还是甩了所有 SATA 接口 SSD 几条街；</p><p>三星 960EVO 持续写入速度为 1.5G&#x2F;S，4K 写入 30 万 IOPS；</p><p>持续读取速度为 3.2G&#x2F;S，4K 读取 33 万 IOPS；</p><p>寿命约为全盘写入 700 次，每天全盘写入 1 次，预估寿命为 2 年；</p><p>当然，普通用户根本没有那么大的写入数据需求，以每天 20G 的数据写入量，即便是寿命最短的三星 960EVO，预期寿命也有 20 年；</p><p>最后，我想说，以普通用户的日常数据处理量，TCL 闪存颗粒肯定够用了，没有必要去追求什么 SLC、MLC；我们只要性价比，我突然发现一个事实：目前某东上热销的 SSD 都是 TLC 固态！</p><p><a href="https://zhidao.baidu.com/question/2055990913373032707.html">转载地址</a></p>]]></content>
    
    
    <summary type="html">SSD 固态硬盘基础知识介绍，了解 SLC/MLC/TLC 闪存颗粒区别</summary>
    
    
    
    <category term="电子产品" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/"/>
    
    <category term="电脑" scheme="https://blog.no-claw.com/categories/%E7%94%B5%E5%AD%90%E4%BA%A7%E5%93%81/%E7%94%B5%E8%84%91/"/>
    
    
  </entry>
  
  <entry>
    <title>晨间流程</title>
    <link href="https://blog.no-claw.com/posts/5e8400b1/"/>
    <id>https://blog.no-claw.com/posts/5e8400b1/</id>
    <published>1999-12-31T16:00:00.000Z</published>
    <updated>2026-05-07T08:04:18.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="晨间正念启动时间表（25–30-分钟）"><a href="#晨间正念启动时间表（25–30-分钟）" class="headerlink" title="晨间正念启动时间表（25–30 分钟）"></a>晨间正念启动时间表（25–30 分钟）</h2><table><thead><tr><th>步骤顺序</th><th>步骤名称</th><th>预计耗时</th><th>说明与正念要点</th></tr></thead><tbody><tr><td>①</td><td>轻醒 &amp; 伸展</td><td>2 分钟</td><td>闹钟响后深呼吸 3 次，慢慢伸展四肢，感受身体苏醒。倒热水</td></tr><tr><td>②</td><td>整理床铺</td><td>2 分钟</td><td>拉平被子、摆正枕头，专注手部触感和动作节奏。</td></tr><tr><td>③</td><td>洗漱 &amp; 冷暖唤醒</td><td>5 分钟</td><td>刷牙、洗脸时专注水流和泡沫触感，最后用凉水拍脸 2–3 次。</td></tr><tr><td>④</td><td>补水 &amp; 接触自然</td><td>3 分钟</td><td>喝温水（可加柠檬），打开窗户感受空气与光线。</td></tr><tr><td>⑤</td><td>正念冥想</td><td>10–20 分钟</td><td>正念练习 + 记录</td></tr><tr><td>⑥</td><td>今日启动</td><td>5 分钟</td><td>写下 3 件最重要的事，看镜子微笑，对自己说“我准备好了”。</td></tr></tbody></table>]]></content>
    
    
    <summary type="html">一套 25 分钟的晨间正念启动流程与时间表</summary>
    
    
    
    <category term="零碎生活" scheme="https://blog.no-claw.com/categories/%E9%9B%B6%E7%A2%8E%E7%94%9F%E6%B4%BB/"/>
    
    
  </entry>
  
</feed>
