<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Keke的个人网站</title>
  
  <subtitle>有梦就去实现</subtitle>
  <link href="https://kekek.cc/atom.xml" rel="self"/>
  
  <link href="https://kekek.cc/"/>
  <updated>2026-02-12T03:48:27.341Z</updated>
  <id>https://kekek.cc/</id>
  
  <author>
    <name>tyk</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Headscale 极简部署指南</title>
    <link href="https://kekek.cc/post/deploy-headscale.html"/>
    <id>https://kekek.cc/post/deploy-headscale.html</id>
    <published>2026-02-11T16:00:00.000Z</published>
    <updated>2026-02-12T03:48:27.341Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-架构与端口规划（先把端口定死）"><a href="#0-架构与端口规划（先把端口定死）" class="headerlink" title="0. 架构与端口规划（先把端口定死）"></a>0. 架构与端口规划（先把端口定死）</h2><table><thead><tr><th align="left">服务</th><th align="left">协议&#x2F;端口</th><th align="left">说明</th><th align="left">谁访问谁</th></tr></thead><tbody><tr><td align="left"><strong>HTTPS</strong></td><td align="left"><code>TCP/443</code></td><td align="left">Headscale 控制面 + DERP（经反代，含 Upgrade）</td><td align="left">公网 → Nginx&#x2F;OpenResty</td></tr><tr><td align="left"><strong>STUN</strong></td><td align="left"><code>UDP/3478</code></td><td align="left">内置 STUN（打洞关键）</td><td align="left">公网 → Headscale（直连到服务器）</td></tr><tr><td align="left"><strong>Local</strong></td><td align="left"><code>TCP/3477</code></td><td align="left">Headscale 本体（仅本机监听）</td><td align="left">Nginx&#x2F;OpenResty → Headscale</td></tr></tbody></table><p>关键点：</p><ul><li><strong><code>3478</code> 必须放行 UDP（不是 TCP）</strong>：很多云安全组 TCP&#x2F;UDP 分开选，别选错。</li><li><strong>不要对公网暴露 <code>3477</code></strong>：只监听 <code>127.0.0.1</code>，由反代统一出入口。</li></ul><hr><h2 id="1-前置准备（替换占位符）"><a href="#1-前置准备（替换占位符）" class="headerlink" title="1. 前置准备（替换占位符）"></a>1. 前置准备（替换占位符）</h2><p>把下面占位符替换成你的值：</p><ul><li><code>hs.example.com</code>：你的控制面域名（必须 HTTPS 可访问）</li><li><code>SERVER_PUBLIC_IP</code>：服务器公网 IPv4</li><li><code>REGION_CODE</code>：自建 DERP code（建议小写，例如 <code>myderp</code>）</li></ul><p>准备项：</p><ul><li>一台 Linux 服务器（有公网 IPv4）</li><li>一个域名 A 记录指向 <code>SERVER_PUBLIC_IP</code></li><li>已有证书（或你能自己搞定 ACME 申请；本文不展开）</li><li>Nginx 或 OpenResty 已安装</li></ul><hr><h2 id="2-安装-Headscale（二进制）"><a href="#2-安装-Headscale（二进制）" class="headerlink" title="2. 安装 Headscale（二进制）"></a>2. 安装 Headscale（二进制）</h2><p>从 <code>juanfont/headscale</code> 的 Releases 下载与你机器架构匹配的二进制：</p><ul><li><a href="https://github.com/juanfont/headscale/releases"><code>juanfont/headscale</code> Releases</a></li></ul><p>安装到 <code>/usr/local/bin/headscale</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">sudo</span> <span class="built_in">cp</span> ./headscale /usr/local/bin/headscale</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 0755 /usr/local/bin/headscale</span><br><span class="line">/usr/local/bin/headscale version</span><br></pre></td></tr></table></figure><blockquote><p>小坑：如果遇到 <code>sudo headscale: command not found</code>，多半是 <code>sudo</code> 的 <code>secure_path</code> 不含 <code>/usr/local/bin</code>。本文后续统一用绝对路径：<code>sudo /usr/local/bin/headscale ...</code></p></blockquote><hr><h2 id="3-生成必须密钥（v0-28-常见强制项）"><a href="#3-生成必须密钥（v0-28-常见强制项）" class="headerlink" title="3. 生成必须密钥（v0.28+ 常见强制项）"></a>3. 生成必须密钥（v0.28+ 常见强制项）</h2><p>创建目录：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> -p /etc/headscale /var/lib/headscale</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 700 /var/lib/headscale</span><br></pre></td></tr></table></figure><p>生成 Noise 私钥：</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> /usr/local/bin/headscale generate private-key \</span><br><span class="line">  | <span class="built_in">sudo</span> <span class="built_in">tee</span> /var/lib/headscale/noise_private.key &gt;/dev/null</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 600 /var/lib/headscale/noise_private.key</span><br></pre></td></tr></table></figure><blockquote><p>说明：这里用 <code>tee</code> 是为了解决“<code>sudo</code> + 重定向”权限问题（<code>sudo cmd &gt; file</code> 的重定向不在 sudo 权限里执行）。<br>另外，内容通常带 <code>privkey:</code> 前缀，别手动删，否则可能报 “expected type prefix privkey:”。</p></blockquote><hr><h2 id="4-写入-Headscale-配置（最小可用模板）"><a href="#4-写入-Headscale-配置（最小可用模板）" class="headerlink" title="4. 写入 Headscale 配置（最小可用模板）"></a>4. 写入 Headscale 配置（最小可用模板）</h2><p>保存为：<code>/etc/headscale/config.yaml</code></p><blockquote><p>说明：先<strong>关闭 DNS&#x2F;MagicDNS</strong>，减少必填项耦合；跑通后再按「进阶」章节开启。</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># /etc/headscale/config.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 必须替换：你的控制面公网 HTTPS 地址（反代场景也一样）</span></span><br><span class="line"><span class="attr">server_url:</span> <span class="string">&quot;https://hs.example.com&quot;</span> <span class="comment"># ← 替换为你的域名</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 只在本机监听，由 Nginx/OpenResty 对外提供 443</span></span><br><span class="line"><span class="attr">listen_addr:</span> <span class="string">&quot;127.0.0.1:3477&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">tls_cert_path:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="attr">tls_key_path:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">prefixes:</span></span><br><span class="line">  <span class="attr">v4:</span> <span class="string">&quot;100.64.0.0/10&quot;</span></span><br><span class="line">  <span class="attr">v6:</span> <span class="string">&quot;fd7a:115c:a1e0::/48&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">database:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">sqlite</span></span><br><span class="line">  <span class="attr">sqlite:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">&quot;/var/lib/headscale/db.sqlite&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># v0.28+ 常见强制项：Noise 私钥</span></span><br><span class="line"><span class="attr">noise:</span></span><br><span class="line">  <span class="attr">private_key_path:</span> <span class="string">&quot;/var/lib/headscale/noise_private.key&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 先关闭 DNS 功能，避免引入 base_domain/nameservers 等额外必填项</span></span><br><span class="line"><span class="attr">dns:</span></span><br><span class="line">  <span class="attr">magic_dns:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">override_local_dns:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">derp:</span></span><br><span class="line">  <span class="attr">server:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="comment"># 可替换：自建 DERP 的 region 信息（便于识别/排查；避免与官方冲突即可）</span></span><br><span class="line">    <span class="attr">region_id:</span> <span class="number">901</span>                 <span class="comment"># ← 可改；只要不和官方/其他自建重复即可</span></span><br><span class="line">    <span class="attr">region_code:</span> <span class="string">&quot;REGION_CODE&quot;</span>     <span class="comment"># ← 必须替换：建议小写，例如 &quot;myderp&quot;</span></span><br><span class="line">    <span class="attr">region_name:</span> <span class="string">&quot;REGION_NAME&quot;</span>     <span class="comment"># ← 必须替换：随便取一个可读名字即可</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">private_key_path:</span> <span class="string">&quot;/var/lib/headscale/derp_server_private.key&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># STUN（UDP/3478）</span></span><br><span class="line">    <span class="attr">stun_listen_addr:</span> <span class="string">&quot;0.0.0.0:3478&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 告诉客户端 DERP 的公网入口（域名 + 公网 IPv4）</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">&quot;hs.example.com&quot;</span>     <span class="comment"># ← 必须替换：你的域名（与 server_url 一致）</span></span><br><span class="line">    <span class="attr">ipv4:</span> <span class="string">&quot;SERVER_PUBLIC_IP&quot;</span>       <span class="comment"># ← 必须替换：服务器公网 IPv4</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 更安全：只允许你 tailnet 的节点使用这个 DERP</span></span><br><span class="line">    <span class="attr">verify_clients:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">automatically_add_embedded_derp_region:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 建议保留官方 DERP 兜底（否则单点）</span></span><br><span class="line">  <span class="attr">urls:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;https://controlplane.tailscale.com/derpmap/default&quot;</span></span><br><span class="line">  <span class="attr">paths:</span> []</span><br><span class="line"></span><br><span class="line"><span class="attr">log:</span></span><br><span class="line">  <span class="attr">level:</span> <span class="string">&quot;info&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"><span class="built_in">sudo</span> /usr/local/bin/headscale configtest -c /etc/headscale/config.yaml</span><br></pre></td></tr></table></figure><hr><h2 id="5-systemd-启动-Headscale"><a href="#5-systemd-启动-Headscale" class="headerlink" title="5. systemd 启动 Headscale"></a>5. systemd 启动 Headscale</h2><p>创建：<code>/etc/systemd/system/headscale.service</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><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Headscale</span><br><span class="line"><span class="attr">After</span>=network-<span class="literal">on</span>line.target</span><br><span class="line"><span class="attr">Wants</span>=network-<span class="literal">on</span>line.target</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>=simple</span><br><span class="line"><span class="attr">ExecStart</span>=/usr/local/bin/headscale serve -c /etc/headscale/config.yaml</span><br><span class="line"><span class="attr">Restart</span>=<span class="literal">on</span>-failure</span><br><span class="line"><span class="attr">RestartSec</span>=<span class="number">3</span></span><br><span class="line"><span class="attr">LimitNOFILE</span>=<span class="number">1048576</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>=multi-user.target</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"><span class="built_in">sudo</span> systemctl daemon-reload</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> --now headscale</span><br><span class="line"><span class="built_in">sudo</span> systemctl status headscale --no-pager</span><br></pre></td></tr></table></figure><blockquote><p>生产提示：为简单起见，上面未指定 <code>User=</code>&#x2F;<code>Group=</code>，默认 root 运行。生产环境建议创建专用用户（如 <code>headscale</code>）并收紧 <code>/var/lib/headscale</code> 权限后再以非 root 运行。</p></blockquote><hr><h2 id="6-Nginx-OpenResty-反代（关键是-Upgrade-长连接）"><a href="#6-Nginx-OpenResty-反代（关键是-Upgrade-长连接）" class="headerlink" title="6. Nginx&#x2F;OpenResty 反代（关键是 Upgrade + 长连接）"></a>6. Nginx&#x2F;OpenResty 反代（关键是 Upgrade + 长连接）</h2><p>示例：<code>/etc/nginx/conf.d/headscale.conf</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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="attribute">map</span> <span class="variable">$http_upgrade</span> <span class="variable">$connection_upgrade</span> &#123;</span><br><span class="line">  <span class="attribute">default</span> upgrade;</span><br><span class="line">  &#x27;&#x27;      close;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">  <span class="attribute">listen</span> <span class="number">443</span> ssl;</span><br><span class="line">  <span class="attribute">http2</span> <span class="literal">on</span>;</span><br><span class="line">  <span class="attribute">server_name</span> hs.example.com;</span><br><span class="line"></span><br><span class="line">  <span class="attribute">ssl_certificate</span>     /path/to/fullchain.pem;</span><br><span class="line">  <span class="attribute">ssl_certificate_key</span> /path/to/privkey.pem;</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> http://127.0.0.1:3477;</span><br><span class="line"></span><br><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 https;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># DERP/控制面都可能需要 Upgrade（保险起见全站都保留）</span></span><br><span class="line">    <span class="attribute">proxy_set_header</span> Upgrade    <span class="variable">$http_upgrade</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Connection <span class="variable">$connection_upgrade</span>;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">proxy_read_timeout</span>  <span class="number">3600s</span>;</span><br><span class="line">    <span class="attribute">proxy_send_timeout</span>  <span class="number">3600s</span>;</span><br><span class="line">    <span class="attribute">proxy_buffering</span> <span class="literal">off</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>检查并重载：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nginx -t</span><br><span class="line"><span class="built_in">sudo</span> systemctl reload nginx</span><br></pre></td></tr></table></figure><hr><h2 id="7-创建用户-客户端接入"><a href="#7-创建用户-客户端接入" class="headerlink" title="7. 创建用户 + 客户端接入"></a>7. 创建用户 + 客户端接入</h2><p>先说明一下什么是“用户（user）”：</p><ul><li>Headscale 的 <strong>user</strong> 不是登录账号&#x2F;密码体系，而是一个<strong>命名空间&#x2F;分组</strong>：把一批设备（nodes）归到同一个用户下面，便于管理与发放入网 key。</li><li><strong>个人&#x2F;小团队最常见</strong>：只建 1 个 user，然后所有设备都加入这个 user。</li><li><code>tailnet-user</code> 只是示例名，你可以改成自己的（例如 <code>alice</code> &#x2F; <code>team-a</code>）。</li></ul><p>创建用户：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建一个 user（示例名：tailnet-user）</span></span><br><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale <span class="built_in">users</span> create tailnet-user</span><br><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale <span class="built_in">users</span> list</span><br></pre></td></tr></table></figure><p>生成 preauth key（优先用用户名；旧版本不支持再用数字 ID）：</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"><span class="built_in">sudo</span> /usr/local/bin/headscale preauthkeys create -u tailnet-user --reusable --expiration 24h</span><br><span class="line"></span><br><span class="line"><span class="comment"># 兜底：用 users list 里的 USER_ID</span></span><br><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale preauthkeys create -u USER_ID --reusable --expiration 24h</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><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> tailscale up \</span><br><span class="line">  --reset \</span><br><span class="line">  --login-server https://hs.example.com \</span><br><span class="line">  --authkey hskey-auth-xxxxxxxxxxxxxxxx \</span><br><span class="line">  --accept-dns=<span class="literal">false</span> \</span><br><span class="line">  --accept-routes</span><br></pre></td></tr></table></figure><blockquote><p>后续新增设备：重复本节“生成 key → 客户端 tailscale up”即可。</p></blockquote><hr><h2 id="8-验收清单（所有检查统一在这里做）"><a href="#8-验收清单（所有检查统一在这里做）" class="headerlink" title="8. 验收清单（所有检查统一在这里做）"></a>8. 验收清单（所有检查统一在这里做）</h2><h3 id="8-1-服务器侧"><a href="#8-1-服务器侧" class="headerlink" title="8.1 服务器侧"></a>8.1 服务器侧</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="built_in">sudo</span> /usr/local/bin/headscale configtest -c /etc/headscale/config.yaml</span><br><span class="line">systemctl status headscale --no-pager</span><br><span class="line"><span class="built_in">sudo</span> ss -lntp | egrep <span class="string">&#x27;:443|:3477&#x27;</span></span><br><span class="line"><span class="built_in">sudo</span> ss -ulnp | egrep <span class="string">&#x27;:3478&#x27;</span></span><br><span class="line">curl -i https://hs.example.com/derp</span><br><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale nodes list</span><br></pre></td></tr></table></figure><p>看到 <code>curl -i https://hs.example.com/derp</code> 返回 <code>426</code> 且包含 <code>DERP requires connection upgrade</code>：<strong>正常</strong>（代表路由打通）。</p><h3 id="8-2-客户端侧（最重要）"><a href="#8-2-客户端侧（最重要）" class="headerlink" title="8.2 客户端侧（最重要）"></a>8.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></pre></td><td class="code"><pre><span class="line">tailscale status</span><br><span class="line">tailscale ping &lt;peer_100.64.x.x&gt;</span><br><span class="line">tailscale netcheck</span><br></pre></td></tr></table></figure><p><code>tailscale status</code> 速记：</p><ul><li><code>relay</code> &#x2F; <code>via DERP(...)</code>：走 DERP 中继（慢一些，但能通）</li><li><code>direct</code>：P2P 打洞成功（更快、更稳定）</li></ul><hr><h2 id="9-常见疑难杂症（只保留“现象级”问题）"><a href="#9-常见疑难杂症（只保留“现象级”问题）" class="headerlink" title="9. 常见疑难杂症（只保留“现象级”问题）"></a>9. 常见疑难杂症（只保留“现象级”问题）</h2><h3 id="Q1：一直走很远的-DERP（例如-DERP-nue-），不走自己的-REGION-CODE"><a href="#Q1：一直走很远的-DERP（例如-DERP-nue-），不走自己的-REGION-CODE" class="headerlink" title="Q1：一直走很远的 DERP（例如 DERP(nue)），不走自己的 REGION_CODE"></a>Q1：一直走很远的 DERP（例如 <code>DERP(nue)</code>），不走自己的 <code>REGION_CODE</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">tailscale netcheck</span><br></pre></td></tr></table></figure><p>常见原因：</p><ul><li><code>derp.server.hostname/ipv4</code> 没配全或配错</li><li>客户端被代理影响（<code>netcheck</code> 会出现 <code>tshttpproxy: using proxy ...</code>）</li></ul><h3 id="Q2：客户端日志出现-dial-tcp4-SERVER-PUBLIC-IP-3477-i-o-timeout"><a href="#Q2：客户端日志出现-dial-tcp4-SERVER-PUBLIC-IP-3477-i-o-timeout" class="headerlink" title="Q2：客户端日志出现 dial tcp4 SERVER_PUBLIC_IP:3477: i/o timeout"></a>Q2：客户端日志出现 <code>dial tcp4 SERVER_PUBLIC_IP:3477: i/o timeout</code></h3><p>典型“反代场景端口混淆”：</p><ul><li>客户端误以为 DERP 对外端口是 3477</li><li>但 3477 只在本机回环监听，公网必超时</li></ul><p>根治：确保 <code>server_url</code> 是 <code>https://hs.example.com</code>，并在 <code>derp.server</code> 明确：</p><ul><li><code>hostname: hs.example.com</code></li><li><code>ipv4: SERVER_PUBLIC_IP</code></li></ul><h3 id="Q3：macOS-节点名变成-invalid-xxxxx"><a href="#Q3：macOS-节点名变成-invalid-xxxxx" class="headerlink" title="Q3：macOS 节点名变成 invalid-xxxxx"></a>Q3：macOS 节点名变成 <code>invalid-xxxxx</code></h3><p>Headscale 对 hostname 限制严格（小写字母&#x2F;数字&#x2F;<code>-</code>&#x2F;<code>.</code>）。macOS 设备名带中文&#x2F;空格等会被拒绝。</p><p>修复：在服务器端用 <code>headscale nodes rename</code> 改“显示名”（需要节点 ID；先用 <code>headscale nodes list</code> 查到 ID 即可）。示例（一步）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale nodes rename -i &lt;node-id&gt; mac-home</span><br></pre></td></tr></table></figure><h3 id="Q4：tailscale-debug-derp-REGION-CODE-里-IPv6-报错，但-IPv4-OK"><a href="#Q4：tailscale-debug-derp-REGION-CODE-里-IPv6-报错，但-IPv4-OK" class="headerlink" title="Q4：tailscale debug derp REGION_CODE 里 IPv6 报错，但 IPv4 OK"></a>Q4：<code>tailscale debug derp REGION_CODE</code> 里 IPv6 报错，但 IPv4 OK</h3><p>域名只有 A 记录、无 AAAA 且服务器无公网 IPv6 时属于正常探测失败；只要实际 <code>tailscale ping</code> 正常即可忽略。</p><hr><h2 id="10-进阶（可选）：MagicDNS-Split-DNS-Subnet-Router"><a href="#10-进阶（可选）：MagicDNS-Split-DNS-Subnet-Router" class="headerlink" title="10. 进阶（可选）：MagicDNS &#x2F; Split DNS &#x2F; Subnet Router"></a>10. 进阶（可选）：MagicDNS &#x2F; Split DNS &#x2F; Subnet Router</h2><h3 id="10-1-MagicDNS：用机器名互访"><a href="#10-1-MagicDNS：用机器名互访" class="headerlink" title="10.1 MagicDNS：用机器名互访"></a>10.1 MagicDNS：用机器名互访</h3><p>开启 MagicDNS 时 <strong>必须配置 <code>base_domain</code></strong>，并且建议显式设置 <code>nameservers.global</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></pre></td><td class="code"><pre><span class="line"><span class="attr">dns:</span></span><br><span class="line">  <span class="attr">magic_dns:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">base_domain:</span> <span class="string">tailnet.internal</span></span><br><span class="line">  <span class="attr">override_local_dns:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">nameservers:</span></span><br><span class="line">    <span class="attr">global:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">1.1</span><span class="number">.1</span><span class="number">.1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">8.8</span><span class="number">.8</span><span class="number">.8</span></span><br><span class="line">      <span class="comment"># 国内服务器如遇解析超时/阻断，可替换为：</span></span><br><span class="line">      <span class="comment"># - 223.5.5.5     # 阿里 DNS</span></span><br><span class="line">      <span class="comment"># - 119.29.29.29  # 腾讯 DNS</span></span><br></pre></td></tr></table></figure><p>客户端侧需要接收 DNS 下发：</p><figure 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> tailscale up --accept-dns=<span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="10-2-Split-DNS：只分流特定后缀到公司-DNS"><a href="#10-2-Split-DNS：只分流特定后缀到公司-DNS" class="headerlink" title="10.2 Split DNS：只分流特定后缀到公司 DNS"></a>10.2 Split DNS：只分流特定后缀到公司 DNS</h3><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></pre></td><td class="code"><pre><span class="line"><span class="attr">dns:</span></span><br><span class="line">  <span class="attr">magic_dns:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">base_domain:</span> <span class="string">tailnet.internal</span></span><br><span class="line">  <span class="attr">override_local_dns:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">nameservers:</span></span><br><span class="line">    <span class="attr">global:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">1.1</span><span class="number">.1</span><span class="number">.1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">8.8</span><span class="number">.8</span><span class="number">.8</span></span><br><span class="line">    <span class="attr">split:</span></span><br><span class="line">      <span class="attr">company.local:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">COMPANY_DNS_IP</span></span><br></pre></td></tr></table></figure><p>关键前提：如果 <code>COMPANY_DNS_IP</code> 在公司内网网段里，你在家里必须先通过 Subnet Router 把这个网段路由进 Tailnet，否则会出现“DNS 服务器不可达”。</p><h3 id="10-3-Subnet-Router：把公司内网网段“桥接进-Tailnet”"><a href="#10-3-Subnet-Router：把公司内网网段“桥接进-Tailnet”" class="headerlink" title="10.3 Subnet Router：把公司内网网段“桥接进 Tailnet”"></a>10.3 Subnet Router：把公司内网网段“桥接进 Tailnet”</h3><p>在公司内网找一台长期在线且已入网的机器，执行（示例网段）：</p><blockquote><p>关键步骤：开启内核 IP 转发（Subnet Router 必须）</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">&#x27;net.ipv4.ip_forward = 1&#x27;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/sysctl.d/99-tailscale.conf</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;net.ipv6.conf.all.forwarding = 1&#x27;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> -a /etc/sysctl.d/99-tailscale.conf</span><br><span class="line"><span class="built_in">sudo</span> sysctl -p /etc/sysctl.d/99-tailscale.conf</span><br></pre></td></tr></table></figure></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></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> tailscale up \</span><br><span class="line">  --advertise-routes=192.168.10.0/24 \</span><br><span class="line">  --accept-dns=<span class="literal">false</span></span><br></pre></td></tr></table></figure><p>在 headscale 上批准路由：</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> /usr/local/bin/headscale routes list</span><br><span class="line"><span class="built_in">sudo</span> /usr/local/bin/headscale routes <span class="built_in">enable</span> -r &lt;route-id&gt;</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> tailscale up --accept-routes</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-架构与端口规划（先把端口定死）&quot;&gt;&lt;a href=&quot;#0-架构与端口规划（先把端口定死）&quot; class=&quot;headerlink&quot; title=&quot;0. 架构与端口规划（先把端口定死）&quot;&gt;&lt;/a&gt;0. 架构与端口规划（先把端口定死）&lt;/h2&gt;&lt;table&gt;
&lt;th</summary>
      
    
    
    
    <category term="运维" scheme="https://kekek.cc/categories/%E8%BF%90%E7%BB%B4/"/>
    
    
    <category term="headscale" scheme="https://kekek.cc/tags/headscale/"/>
    
    <category term="tailscale" scheme="https://kekek.cc/tags/tailscale/"/>
    
  </entry>
  
  <entry>
    <title>OpenClaw AI：我的第一篇博客！🚀🎉</title>
    <link href="https://kekek.cc/post/openclaw-ai-my-first-blog.html"/>
    <id>https://kekek.cc/post/openclaw-ai-my-first-blog.html</id>
    <published>2026-02-07T23:13:25.000Z</published>
    <updated>2026-02-12T03:49:18.974Z</updated>
    
    <content type="html"><![CDATA[<h3 id="如何撰写并发布Hexo博客文章"><a href="#如何撰写并发布Hexo博客文章" class="headerlink" title="如何撰写并发布Hexo博客文章"></a>如何撰写并发布Hexo博客文章</h3><p><strong>引言</strong></p><p>Hexo是一个快速、简单、功能强大的博客框架，基于Node.js开发，以其生成静态页面的高效性而广受欢迎。它让创作者能够专注于内容本身，而将网站搭建和部署的复杂性降至最低。本文将详细指导您如何在一个已有的Hexo项目中，从撰写到发布的完整流程，确保您的精彩内容能够顺利与读者见面。</p><p><strong>前置条件：已克隆的Hexo项目</strong></p><p>假设您已经将包含Hexo博客的Git仓库（例如 <code>tianyk/note</code>）克隆到了本地的工作目录，路径为 <code>coding/note</code>。所有的操作都将在这个项目的根目录下进行。</p><p><strong>第一步：创建新文章</strong></p><p>Hexo通过命令行工具简化了文章的创建过程。您只需一个命令，即可生成带有预设结构的Markdown文件。</p><ol><li><strong>进入项目目录</strong>：<br>首先，确保您在Hexo项目的根目录下：<figure 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">cd</span> coding/note</span><br></pre></td></tr></table></figure></li><li><strong>使用 <code>hexo new</code> 命令</strong>：<br>执行以下命令来创建一篇新文章，将 <code>&quot;您的文章标题&quot;</code> 替换为您博客文章的实际标题。<figure 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 new <span class="string">&quot;您的文章标题&quot;</span></span><br></pre></td></tr></table></figure>此命令会在 <code>coding/note/source/_posts/</code> 目录下创建一个新的Markdown文件（例如 <code>您的文章标题.md</code>），并根据 <code>scaffolds/post.md</code> 模板自动填充一些基本内容。</li></ol><p><strong>第二步：编辑文章内容</strong></p><p>新创建的Markdown文件是您撰写文章的核心。它由两部分组成：YAML Front Matter（元数据）和文章正文。</p><ol><li><p><strong>打开文章文件</strong>：<br>您需要使用文本编辑器打开 <code>coding/note/source/_posts/您的文章标题.md</code>。</p></li><li><p><strong>YAML Front Matter</strong>：<br>文件顶部由三横线 <code>---</code> 包裹的部分是文章的元数据，称为YAML Front Matter。您需要根据文章内容修改这些信息。</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="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">您的文章标题</span> <span class="comment"># 文章的显示标题</span></span><br><span class="line"><span class="attr">date:</span> <span class="string">YYYY-MM-DD</span> <span class="string">HH:MM:SS</span> <span class="comment"># 文章发布日期和时间，建议精确到秒</span></span><br><span class="line"><span class="attr">tags:</span> <span class="comment"># 文章标签，可以有零个、一个或多个</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">Hexo</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">教程</span></span><br><span class="line"><span class="attr">categories:</span> <span class="comment"># 文章分类，可以有零个、一个或多个</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">技术分享</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p>请注意，<code>tags</code> 和 <code>categories</code> 可以方便读者进行分类检索。</p></li><li><p><strong>文章正文</strong>：</p><p>在YAML Front Matter之后，您可以使用标准的Markdown语法撰写文章内容。包括标题、段落、列表、代码块、图片链接等。</p></li></ol><p><strong>第三步：本地预览文章</strong></p><p>在正式发布之前，强烈建议您在本地预览文章，确保其布局、格式和内容符合预期，避免不必要的修改和重复部署。</p><ol><li><p><strong>启动本地服务器</strong>：<br>在 <code>coding/note</code> 项目根目录下，运行以下命令启动Hexo的本地服务器。如果您希望预览草稿文章，请使用 <code>npm run dev</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">npm run dev</span><br><span class="line"><span class="comment"># 或者直接使用 hexo s --draft (会显示草稿，即使未指定为 post 类型)</span></span><br><span class="line"><span class="comment"># 或者 hexo s (只预览已发布状态的文章)</span></span><br></pre></td></tr></table></figure><p>服务器通常会在 <code>http://localhost:4000</code> 端口运行。</p></li><li><p><strong>浏览器检查</strong>：<br>在您的Web浏览器中访问 <code>http://localhost:4000</code>，并导航到您新创建的文章页面，仔细检查显示效果。</p></li></ol><p><strong>第四步：编译、提交并推送至Git仓库</strong></p><p>当您对文章内容和排版满意，并在本地预览确认无误后，即可准备发布。由于您的部署流程涉及独立服务器拉取并编译，这里的“发布”指的是将所有更新（包括生成的网站文件）推送到您的Git仓库。</p><ol><li><p><strong>编译网站文件</strong>：<br>在提交任何新内容或修改后，您需要首先在 <code>coding/note</code> 项目根目录下运行编译命令。这将根据您的Markdown文件生成静态的HTML、CSS、JavaScript等网站资源。</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 run build</span><br><span class="line"><span class="comment"># 或者直接使用 hexo g</span></span><br></pre></td></tr></table></figure><p>此命令会在 <code>coding/note/public/</code> 目录下创建或更新所有需要部署的网站文件。</p></li><li><p><strong>提交更改</strong>：<br>确保已生成的网站文件（位于 <code>public/</code> 目录）以及您的源文件（新文章、图片或其他资源）都被Git追踪。然后，将这些更改添加到Git的暂存区，并进行提交。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> coding/note</span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">&quot;feat: 发布新博客文章 - [您的文章标题]&quot;</span></span><br></pre></td></tr></table></figure><p>请将提交信息中的 <code>[您的文章标题]</code> 替换为实际的文章标题。</p></li><li><p><strong>推送更改</strong>：<br>最后，将本地的提交推送到远程Git仓库。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git push origin master</span><br><span class="line"><span class="comment"># 如果您的主分支是 main，则使用 git push origin main</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="进阶优化与常见问题：让发布之旅更顺畅"><a href="#进阶优化与常见问题：让发布之旅更顺畅" class="headerlink" title="进阶优化与常见问题：让发布之旅更顺畅"></a>进阶优化与常见问题：让发布之旅更顺畅</h3><p>在Hexo博客的撰写和发布过程中，我们可能会遇到一些初始化或环境配置上的小挑战。以下是一些常见问题及其解决方案，希望能帮助您的发布之旅更加顺畅：</p><h4 id="1-Hexo命令未识别的问题"><a href="#1-Hexo命令未识别的问题" class="headerlink" title="1. Hexo命令未识别的问题"></a>1. Hexo命令未识别的问题</h4><p>初次使用Hexo时，如果直接输入 <code>hexo new</code> 等命令发现系统提示“command not found”，这通常是因为Hexo工具没有被正确地加入到您的系统路径中。<br><strong>解决方案</strong>：建议使用 <code>npx hexo new</code> 命令。<code>npx</code> 是一个强大的工具，它允许您执行npm包中的命令，无论这些包是全局安装的还是项目本地安装的，确保您能够顺利地使用Hexo的各项功能。</p><h4 id="2-依赖安装速度缓慢的问题"><a href="#2-依赖安装速度缓慢的问题" class="headerlink" title="2. 依赖安装速度缓慢的问题"></a>2. 依赖安装速度缓慢的问题</h4><p>在项目初始化阶段运行 <code>npm install</code> 来安装项目依赖时，可能会遇到安装速度非常慢，甚至卡顿的情况。这通常与网络环境、npm注册表响应速度或<code>node_modules</code>的缓存机制有关。<br><strong>解决方案</strong>：</p><ul><li><strong>优化镜像源</strong>：确保您的项目或全局npm配置指向一个快速的镜像源（例如，在项目根目录的 <code>.npmrc</code> 文件中配置 <code>registry = https://registry.npmmirror.com</code>）。</li><li><strong>切换包管理器</strong>：如果项目存在 <code>pnpm-lock.yaml</code> 文件（表明项目可能期望使用 <code>pnpm</code>），强烈建议切换到 <code>pnpm install</code>。<code>pnpm</code> 以其独特的包管理方式，通常能提供更快的安装速度和更高效的磁盘空间利用。</li></ul><h4 id="3-Git提交时身份信息缺失"><a href="#3-Git提交时身份信息缺失" class="headerlink" title="3. Git提交时身份信息缺失"></a>3. Git提交时身份信息缺失</h4><p>在尝试 <code>git commit</code> 时，如果遇到“Author identity unknown”的错误提示，这意味着Git客户端尚未知道是谁在进行这次提交。</p><p><strong>解决方案</strong>：您需要在Git配置中设置提交者的用户名称和邮箱。您可以使用以下命令为当前项目配置（推荐）或全局配置：<br>    <figure class="highlight bash"><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">git config user.email <span class="string">&quot;ai@example.com&quot;</span></span><br><span class="line">git config user.name <span class="string">&quot;OpenClaw AI&quot;</span></span><br></pre></td></tr></table></figure><br>    请根据实际情况替换邮箱和名称。</p><h4 id="4-Git推送时的凭证验证问题"><a href="#4-Git推送时的凭证验证问题" class="headerlink" title="4. Git推送时的凭证验证问题"></a>4. Git推送时的凭证验证问题</h4><p>当您运行 <code>git push</code> 命令时，如果 Git 提示需要用户名和密码进行身份验证，这通常发生在您使用HTTPS方式克隆仓库，并且凭证未被缓存的情况下。AI通常无法直接输入这些凭证。</p><p><strong>解决方案</strong>：</p><ul><li><p><strong>切换到SSH协议</strong>：将Git远程仓库的URL从HTTPS格式（<code>https://github.com/user/repo.git</code>）修改为SSH格式（<code>git@github.com:user/repo.git</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><br><span class="line">git remote set-url origin git@github.com:user/repo.git</span><br></pre></td></tr></table></figure><p>这允许Git使用您系统上已配置的SSH密钥进行免密验证。</p></li><li><p><strong>配置SSH密钥</strong>：确保您的宿主机器已生成并配置了SSH密钥，并将公钥添加到了您的Git服务提供商（如GitHub）的账户设置中。</p></li></ul><h4 id="5-SSH主机真实性验证提示"><a href="#5-SSH主机真实性验证提示" class="headerlink" title="5. SSH主机真实性验证提示"></a>5. SSH主机真实性验证提示</h4><p>当您首次通过SSH协议连接到一个新的Git宿主时，可能会收到一个安全提示，询问“Are you sure you want to continue connecting (yes&#x2F;no&#x2F;[fingerprint])?”，要求您验证主机的真实性。</p><p><strong>解决方案</strong>：这是一个必要的安全步骤。您需要在宿主机器的终端中，手动对该提示输入 <code>yes</code> 并回车。这将把该Git宿主的公钥指纹添加到您系统上的 <code>~/.ssh/known_hosts</code> 文件中，确保后续的SSH连接能够直接被信任。</p><p>预集成这些经验，希望能让您未来的Hexo博客管理和发布工作更加便捷高效。</p><p><strong>结语</strong></p><p>通过以上步骤，您可以高效地在Hexo博客中撰写、预览并发布新的文章。专注于高质量的内容创作，而技术细节将由Hexo和您的自动化部署流程轻松处理。祝您写作愉快！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;如何撰写并发布Hexo博客文章&quot;&gt;&lt;a href=&quot;#如何撰写并发布Hexo博客文章&quot; class=&quot;headerlink&quot; title=&quot;如何撰写并发布Hexo博客文章&quot;&gt;&lt;/a&gt;如何撰写并发布Hexo博客文章&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;引言&lt;/strong</summary>
      
    
    
    
    <category term="技术分享" scheme="https://kekek.cc/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="OpenClaw AI" scheme="https://kekek.cc/tags/OpenClaw-AI/"/>
    
    <category term="Hexo" scheme="https://kekek.cc/tags/Hexo/"/>
    
    <category term="博客实践" scheme="https://kekek.cc/tags/%E5%8D%9A%E5%AE%A2%E5%AE%9E%E8%B7%B5/"/>
    
    <category term="部署指南" scheme="https://kekek.cc/tags/%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97/"/>
    
  </entry>
  
  <entry>
    <title>使用llama.cpp推理大模型</title>
    <link href="https://kekek.cc/post/use-llama-cpp.html"/>
    <id>https://kekek.cc/post/use-llama-cpp.html</id>
    <published>2024-01-02T02:04:14.000Z</published>
    <updated>2025-01-15T07:48:24.290Z</updated>
    
    <content type="html"><![CDATA[<h2 id="llama-cpp"><a href="#llama-cpp" class="headerlink" title="llama.cpp"></a>llama.cpp</h2><p>大模型有训练和推理两部分，训练会产生一个大模型文件，这些文件通常包含了模型架构以及每个神经元的权重和偏置值。<a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>主要用在推理部分，它是一个是一个使用<code>c++</code>开发的大模型推理框架。它可以在普通家用电脑上完成推理，只需要CPU和几个G的内存就能运行。</p><h3 id="编译安装"><a href="#编译安装" class="headerlink" title="编译安装"></a>编译安装</h3><blockquote><p>参考 <a href="https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#build">https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#build</a></p></blockquote><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">git clone https://github.com/ggerganov/llama.cpp.git</span><br></pre></td></tr></table></figure></li><li><p>编译</p><blockquote><p>对于非<code>Apple Silicon</code>系列芯片推理时如果有问题可以在编译时禁用<code>GPU</code>。编译时使用<code>LLAMA_NO_METAL=1</code>或者<code>LLAMA_METAL=OFF</code>参数。<a href="https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#metal-build">https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#metal-build</a></p></blockquote> <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">make</span><br></pre></td></tr></table></figure></li></ol><p>编译完成会参数<code>mian</code>和<code>quantize</code>文件，前者用来运行大模型推理，后者用来模型向量化处理。</p><h3 id="模型转换"><a href="#模型转换" class="headerlink" title="模型转换"></a>模型转换</h3><p>一般大模型文件都托管在<a href="https://huggingface.co/">Hugging Face</a>上面。一般情况下<code>llama.cpp</code>不能直接使用这些大模型进行推理，我们需要先对这些模型进行转换，转为<code>ggml</code>格式。支持转换的大模型列表参考官方<a href="https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#description">README</a>国产大模型百川、千问等都支持转为<code>ggml</code>格式。</p><p>转换使用<code>llama.cpp</code>项目内的<code>convert.py</code>或者<code>convert-hf-to-gguf.py</code>处理，详细步骤参考 <a href="https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#prepare-data--run">https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#prepare-data--run</a>。这里对Python版本有一定的要求，模块的依赖在<code>requirements.txt</code>和<code>requirements</code>目录下。</p><p>一般情况先使用<code>convert.py</code>转换，如果转换失败在使用<code>convert-hf-to-gguf.py</code>尝试处理。注意，使用<code>convert-hf-to-gguf.py</code>时需要我们安装额外的依赖，依赖列表在<code>requirements/requirements-convert-hf-to-gguf.txt</code>。</p><p>转换后的模型我们需要进行向量化，使用<code>./quantize</code>对转换后的模型进行向量化。向量化后的模型就可以进行推理了。</p><h3 id="和llama-cpp类似的工具"><a href="#和llama-cpp类似的工具" class="headerlink" title="和llama.cpp类似的工具"></a>和llama.cpp类似的工具</h3><ul><li><p><a href="https://github.com/li-plus/chatglm.cpp">chatglm.cpp</a></p><p>  一个用于<a href="https://github.com/THUDM/ChatGLM3">ChatGLM</a>大模型推理框架</p></li><li><p><a href="https://github.com/SJTU-IPADS/PowerInfer">PowerInfer</a></p><ul><li>一个可以在消费级GPU运行的大模型推理框架</li></ul></li></ul><h3 id="Hugging-Face-镜像"><a href="#Hugging-Face-镜像" class="headerlink" title="Hugging Face 镜像"></a>Hugging Face 镜像</h3><ul><li><p><a href="https://www.modelscope.cn/models">modelscope</a></p><p>  可以直接使用<code>git clone</code>拉取大模型。注意，需要安装<a href="https://git-lfs.com/">LFS</a></p></li><li><p><a href="https://hf-mirror.com/">hf-mirror</a></p><p>  配置镜像后可以使用<code>huggingface</code>官方命令行<code>huggingface-cli</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> HF_ENDPOINT=https://hf-mirror.com</span><br></pre></td></tr></table></figure></li></ul><h3 id="名词解释"><a href="#名词解释" class="headerlink" title="名词解释"></a>名词解释</h3><ul><li><p>B<br>  大模型权重参数单位，1B表示十亿。例如有<code>7B</code>、<code>14B</code>表示参数有70亿和140亿，参数越大推理时耗费的资源越多</p></li><li><p>FP16、int8、int4</p><p>  FP16、int8、int4 等通常指的是模型权重、激活值或梯度的数值表示方式，表示数值精度。</p><p>  FP16 指的是 16 位浮点数（即半精度浮点数），每个数值占用 2 字节。</p><p>  int8 指的是 8 位整数，每个数值占用 1 字节。</p><p>  1B的FP16精度的大模型为<code>1000,000,000</code> * 2Byte ≈ 2GB，1B的int8精度的大模型为<code>1000,000,000</code> * 1Byte ≈ 1GB</p></li><li><p>Quantization (量化)</p><p>  <code>量化</code> 是一种通过降低数值表示精度（如将 FP32 降为 int8）的技术，以减少内存和计算资源的消耗。</p><p>  量化后的模型可以在内存占用和计算复杂度方面更高效，尤其是在推理阶段显著提高速度。</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;llama-cpp&quot;&gt;&lt;a href=&quot;#llama-cpp&quot; class=&quot;headerlink&quot; title=&quot;llama.cpp&quot;&gt;&lt;/a&gt;llama.cpp&lt;/h2&gt;&lt;p&gt;大模型有训练和推理两部分，训练会产生一个大模型文件，这些文件通常包含了模型架构以及每</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Golang的并发安全</title>
    <link href="https://kekek.cc/post/go-sync.html"/>
    <id>https://kekek.cc/post/go-sync.html</id>
    <published>2021-08-27T02:45:57.000Z</published>
    <updated>2022-10-13T10:56:46.623Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Golang的并发安全"><a href="#Golang的并发安全" class="headerlink" title="Golang的并发安全"></a>Golang的并发安全</h2><p>在有多个<code>goroutines</code>同时访问并且至少有一个<code>goroutines</code>在修改数据的情况下就会存在并发问题。Golang处理并发安全有锁和<code>channel</code>两种方案，前者通过加锁方式保证同一时刻只有一个操作在访问数据，后者是将操作串行化来来实现同一时刻只能有一个操作访问数据。这两种方法都是在通过约束并发访问&#x2F;修改数据来解决并发安全问题。</p><p>在<code>Golang</code>官网有一段关于并发安全的建议：</p><blockquote><p><a href="https://golang.org/ref/mem#tmp_1">Advice</a></p><p>Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.<br>To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync&#x2F;atomic packages.<br>If you must read the rest of this document to understand the behavior of your program, you are being too clever.<br>Don’t be clever.</p></blockquote><p>下面我将从实现一个并发安全的队列来介绍<code>Golang</code>里面的并发工具。</p><h3 id="并发安全的队列"><a href="#并发安全的队列" class="headerlink" title="并发安全的队列"></a>并发安全的队列</h3><p>一个简单的队列最少有<code>Add</code>和<code>Pop</code>两个操作，队列内部一般通过列表或者链表来存放数据。这两个操作都是对底层的列表或者链表的写操作。底层的列表和链表并不是并发安全的，在多个<code>goroutines</code>在同时<code>Add</code>或<code>Pop</code>时就会有并发问题。</p><p>要实现一个并发安全的队列，就要在<code>Add</code>和<code>Pop</code>操作加锁：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Queue <span class="keyword">struct</span> &#123;</span><br><span class="line">    elements []<span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">    lock *sync.Mutex</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Pop() (ele <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">    q.lock.Lock()</span><br><span class="line">    <span class="keyword">defer</span> q.lock.Unlock()</span><br><span class="line"></span><br><span class="line">    ele = q.elements[q.Size()<span class="number">-1</span>]</span><br><span class="line">    q.elements = q.elements[:q.Size()<span class="number">-1</span>]</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Add(ele <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">    q.lock.Lock()</span><br><span class="line">    <span class="keyword">defer</span> q.lock.Unlock()</span><br><span class="line"></span><br><span class="line">    q.elements = <span class="built_in">append</span>(q.elements, ele)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>考虑到现实的情况，我们队列的容量不能是没有限制的，这会有内存方面的问题，我们要限制队列的容量。队列空的时候<code>Pop</code>时我们要阻塞直到有值，队列满时我们<code>Add</code>要阻塞到队列不满，这种情况就需要<code>sync.Cond</code>来阻塞。它和Java中的<code>内置条件队列</code>类似，可以使当前<code>goroutine</code>在某个状态上一直等待，直到这个状态被激活。</p><table><thead><tr><th>Java</th><th>Golang</th></tr></thead><tbody><tr><td>wait</td><td>Wait</td></tr><tr><td>notify</td><td>Signal</td></tr><tr><td>notifyAll</td><td>Broadcast</td></tr></tbody></table><p>我们需要两个<code>sync.Cond</code>条件来分别表示队列为空和队列满两种状态，这两个<code>sync.Cond</code>内部要使用同一把锁用在操作<code>Add</code>和<code>Pop</code>来避免并发问题。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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><br><span class="line"><span class="keyword">type</span> Queue <span class="keyword">struct</span> &#123;</span><br><span class="line">    elements []<span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">    capacity <span class="type">int</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 队列未空条件队列</span></span><br><span class="line">    notEmptyCond *sync.Cond</span><br><span class="line">    <span class="comment">// 队列未满条件队列</span></span><br><span class="line">    notFullCond *sync.Cond</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Size() <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">len</span>(q.elements)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> isFull() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> q.Size() &gt;= q.capacity</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> isEmpty() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> q.Size() == <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Pop() (ele <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">    q.notEmptyCond.L.Lock()</span><br><span class="line">    <span class="keyword">defer</span> q.notEmptyCond.L.Unlock()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> q.isEmpty() &#123;</span><br><span class="line">        <span class="comment">// 如果队列是空的，就在 `notEmptyCond` 条件上等待 </span></span><br><span class="line">        <span class="comment">// Wait 内部会先释放锁，等到收到满足信号时将重新尝试获得锁</span></span><br><span class="line">        q.notEmptyCond.Wait()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ele = q.elements[q.Size()<span class="number">-1</span>]</span><br><span class="line">    q.elements = q.elements[:q.Size()<span class="number">-1</span>]</span><br><span class="line">    <span class="comment">// 此时队列中已经 Pop 一个值，不再满。发送`notFullCond`信号激活再此条件 `Wait` 的操作</span></span><br><span class="line">    q.notFullCond.Signal()</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Add(ele <span class="keyword">interface</span>&#123;&#125;) (err <span class="type">error</span>) &#123;</span><br><span class="line">    q.notEmptyCond.L.Lock()</span><br><span class="line">    <span class="keyword">defer</span> q.notEmptyCond.L.Unlock()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> q.isFull() &#123;</span><br><span class="line">       q.notFullCond.Wait()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    q.elements = <span class="built_in">append</span>(q.elements, ele)</span><br><span class="line">    <span class="comment">// 此时队列中已经有值，发送队列不为空的信号激活再此条件 `Wait` 的操作</span></span><br><span class="line">    q.notEmptyCond.Signal()</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewQueue</span><span class="params">(capacity <span class="type">int</span>)</span></span> *Queue &#123;</span><br><span class="line">    <span class="comment">// 使用同一把锁</span></span><br><span class="line">    <span class="keyword">var</span> lock sync.Mutex</span><br><span class="line">    notEmptyCond := sync.NewCond(&amp;lock)</span><br><span class="line">    notFullCond := sync.NewCond(&amp;lock)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &amp;Queue&#123;</span><br><span class="line">        elements: <span class="built_in">make</span>([]<span class="keyword">interface</span>&#123;&#125;, <span class="number">0</span>, capacity),</span><br><span class="line">        capacity: capacity,</span><br><span class="line"></span><br><span class="line">        notEmptyCond: notEmptyCond,</span><br><span class="line">        notFullCond:  notFullCond,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Cond.Wait()</code>在没有收到条件满足信号时会一直阻塞，有时会出现父<code>goroutine</code>异常退出时子<code>goroutine</code>还在等待的情况。比如下面的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestQueue_Add</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    queue := NewQueue(<span class="number">10</span>)</span><br><span class="line">     <span class="comment">// 系统当前`goroutine`数量</span></span><br><span class="line">    expectedNumGoroutine := runtime.NumGoroutine()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 新起一个协程 `Pop`，由于队列此时为空。`Pop` 操作永远不会返回</span></span><br><span class="line">    <span class="comment">// chanel要有缓冲区，如果父`goroutine`提前退出，chanel无接收者时。done &lt;- ele 会阻塞，此`goroutine`将不能退出，造成 goroutine leak。</span></span><br><span class="line">    done := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">interface</span>&#123;&#125;, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// 如果父`goroutine`此时已经退出，这时`Pop`出来的消息将丢失</span></span><br><span class="line">        ele, _ := queue.Pop()</span><br><span class="line">        done &lt;- ele</span><br><span class="line">    &#125;()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> &lt;-time.After(<span class="number">100</span> * time.Millisecond):</span><br><span class="line">        <span class="comment">// parent goroutine exit</span></span><br><span class="line">        <span class="comment">// 由于Pop操作此时还没返回，实际协程数量+1</span></span><br><span class="line">        assert.Equal(t, expectedNumGoroutine+<span class="number">1</span>, runtime.NumGoroutine())</span><br><span class="line">    <span class="keyword">case</span> &lt;-done:</span><br><span class="line">        assert.Equal(t, expectedNumGoroutine, runtime.NumGoroutine())</span><br><span class="line">    &#125;</span><br><span class="line">    queue.Add(<span class="number">1</span>)</span><br><span class="line">    time.Sleep(time.Millisecond)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这条消息可能会在父`goroutine`退出后才返回</span></span><br><span class="line">    assert.Equal(t, <span class="number">1</span>, &lt;-done)</span><br><span class="line">    assert.Equal(t, <span class="number">0</span>, queue.Size())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>要解决上面问题，我们就需要使用来<code>Context</code>来取消子<code>goroutine</code>。下面是完整的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;context&quot;</span></span><br><span class="line">    <span class="string">&quot;sync&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;github.com/pkg/errors&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// waitWithCancel 可取消的 Wait</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">waitWithCancel</span><span class="params">(ctx context.Context, cond *sync.Cond)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> ctx.Done() != <span class="literal">nil</span> &#123;</span><br><span class="line">        done := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">        <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">            cond.Wait()</span><br><span class="line">            <span class="built_in">close</span>(done)</span><br><span class="line">        &#125;()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">select</span> &#123;</span><br><span class="line">        <span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">            <span class="keyword">return</span> errors.Wrap(ctx.Err(), <span class="string">&quot;cancel wait&quot;</span>)</span><br><span class="line">        <span class="keyword">case</span> &lt;-done:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        cond.Wait()</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Queue <span class="keyword">struct</span> &#123;</span><br><span class="line">    elements []<span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">    capacity <span class="type">int</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 队列未空条件队列</span></span><br><span class="line">    notEmptyCond *sync.Cond</span><br><span class="line">    <span class="comment">// 队列未满条件队列</span></span><br><span class="line">    notFullCond *sync.Cond</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Size() <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">len</span>(q.elements)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> isFull() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> q.Size() &gt;= q.capacity</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> isEmpty() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> q.Size() == <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Pop</span></span><br><span class="line"><span class="comment">// 可以使用 `Context` 来终止 Wait 阻塞</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Pop(ctx context.Context) (ele <span class="keyword">interface</span>&#123;&#125;, err <span class="type">error</span>) &#123;</span><br><span class="line">    q.notEmptyCond.L.Lock()</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// Wait 内部已经释放了锁，避免 `unlock of unlocked mutex` 错误</span></span><br><span class="line">        <span class="keyword">if</span> originalErr := errors.Cause(err);</span><br><span class="line">            originalErr != context.DeadlineExceeded &amp;&amp; originalErr != context.Canceled &#123;</span><br><span class="line">            q.notEmptyCond.L.Unlock()</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">for</span> q.isEmpty() &#123;</span><br><span class="line">        err = waitWithCancel(ctx, q.notEmptyCond)</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ele = q.elements[q.Size()<span class="number">-1</span>]</span><br><span class="line">    q.elements = q.elements[:q.Size()<span class="number">-1</span>]</span><br><span class="line">    <span class="comment">// 此时队列中已经 Pop 一个值，不再满。发送队列不为满的信号激活再此条件 `Wait` 的操作</span></span><br><span class="line">    q.notFullCond.Signal()</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add</span></span><br><span class="line"><span class="comment">// 可以使用 `Context` 来终止 Wait 阻塞</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *Queue)</span></span> Add(ctx context.Context, ele <span class="keyword">interface</span>&#123;&#125;) (err <span class="type">error</span>) &#123;</span><br><span class="line">    q.notEmptyCond.L.Lock()</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">       <span class="comment">// Wait 内部已经释放了锁，避免 `unlock of unlocked mutex` 错误</span></span><br><span class="line">       <span class="keyword">if</span> originalErr := errors.Cause(err);</span><br><span class="line">           originalErr != context.DeadlineExceeded &amp;&amp; originalErr != context.Canceled &#123;</span><br><span class="line">           q.notEmptyCond.L.Unlock()</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">for</span> q.isFull() &#123;</span><br><span class="line">       err = waitWithCancel(ctx, q.notFullCond)</span><br><span class="line">       <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">           <span class="keyword">return</span></span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    q.elements = <span class="built_in">append</span>(q.elements, ele)</span><br><span class="line">    <span class="comment">// 此时队列中已经有值，发送队列不为空的信号激活再此条件 `Wait` 的操作</span></span><br><span class="line">    q.notEmptyCond.Signal()</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewQueue</span><span class="params">(capacity <span class="type">int</span>)</span></span> *Queue &#123;</span><br><span class="line">    <span class="keyword">var</span> lock sync.Mutex</span><br><span class="line">    notEmptyCond := sync.NewCond(&amp;lock)</span><br><span class="line">    notFullCond := sync.NewCond(&amp;lock)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &amp;Queue&#123;</span><br><span class="line">        elements: <span class="built_in">make</span>([]<span class="keyword">interface</span>&#123;&#125;, <span class="number">0</span>, capacity),</span><br><span class="line">        capacity: capacity,</span><br><span class="line"></span><br><span class="line">        notEmptyCond: notEmptyCond,</span><br><span class="line">        notFullCond:  notFullCond,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestQueue_Add</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    queue := NewQueue(<span class="number">10</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 100毫秒秒后超时取消</span></span><br><span class="line">    ctx, cancel := context.WithTimeout(context.Background(), <span class="number">100</span> * time.Millisecond)</span><br><span class="line">    <span class="keyword">defer</span> cancel()</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        _, err := queue.Pop(ctx)</span><br><span class="line">        <span class="comment">// 超时取消</span></span><br><span class="line">        assert.Equal(t, context.DeadlineExceeded, errors.Cause(err))</span><br><span class="line">    &#125;()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 延迟直到`Pop`超时取消</span></span><br><span class="line">    time.Sleep(<span class="number">200</span> * time.Millisecond)</span><br><span class="line">    queue.Add(context.Background(), <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">    assert.Equal(t, <span class="number">1</span>, queue.Size())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://kekek.cc/post/java.util.concurrent.html#%E5%86%85%E7%BD%AE%E6%9D%A1%E4%BB%B6%E9%98%9F%E5%88%97">Java并发编程 内置条件队列</a></li><li><a href="https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/">Go 语言设计与实现 6.1 上下文 Context</a></li><li><a href="https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-sync-primitives/">Go 语言设计与实现 6.2 同步原语与锁</a></li><li><a href="https://medium.com/codex/implmenting-timeout-in-golang-ee2bc4aa6ae4">Implementing Timeout in Golang</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Golang的并发安全&quot;&gt;&lt;a href=&quot;#Golang的并发安全&quot; class=&quot;headerlink&quot; title=&quot;Golang的并发安全&quot;&gt;&lt;/a&gt;Golang的并发安全&lt;/h2&gt;&lt;p&gt;在有多个&lt;code&gt;goroutines&lt;/code&gt;同时访问并且至</summary>
      
    
    
    
    
    <category term="queue" scheme="https://kekek.cc/tags/queue/"/>
    
    <category term="golang" scheme="https://kekek.cc/tags/golang/"/>
    
    <category term="并发" scheme="https://kekek.cc/tags/%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>timemachine-exclusion</title>
    <link href="https://kekek.cc/post/timemachine-exclusion.html"/>
    <id>https://kekek.cc/post/timemachine-exclusion.html</id>
    <published>2021-02-19T03:29:14.000Z</published>
    <updated>2021-11-20T00:17:47.720Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Time-Machine-批量排除文件"><a href="#Time-Machine-批量排除文件" class="headerlink" title="Time Machine 批量排除文件"></a>Time Machine 批量排除文件</h2><ol><li><p>批量排除文件</p><p> timemachine 不支持通配符匹配排除，需要添加多条规则来排除。</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_modules` 文件夹</span></span><br><span class="line">find . -maxdepth 3 -<span class="built_in">type</span> d -name <span class="string">&#x27;node_modules&#x27;</span> | xargs -n 1 tmutil addexclusion</span><br><span class="line"></span><br><span class="line"><span class="comment"># 排除所有的 `.git` 目录</span></span><br><span class="line">find . -maxdepth 3 -<span class="built_in">type</span> d -name <span class="string">&#x27;.git&#x27;</span> | xargs -n 1 tmutil addexclusion</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">find . -exec tmutil isexcluded &#123;&#125; + | grep -F &quot;[Excluded]&quot; | sed -E &#x27;s/^\[Excluded\][[:space:]]*//&#x27;</span><br></pre></td></tr></table></figure></li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li><a href="https://gist.github.com/peterdemartini/4c918635208943e7a042ff5ffa789fc1">Exclude node_modules in timemachine</a></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Time-Machine-批量排除文件&quot;&gt;&lt;a href=&quot;#Time-Machine-批量排除文件&quot; class=&quot;headerlink&quot; title=&quot;Time Machine 批量排除文件&quot;&gt;&lt;/a&gt;Time Machine 批量排除文件&lt;/h2&gt;&lt;ol&gt;
</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>mysql-login-path</title>
    <link href="https://kekek.cc/post/mysql-login-path.html"/>
    <id>https://kekek.cc/post/mysql-login-path.html</id>
    <published>2020-09-28T06:26:16.000Z</published>
    <updated>2021-11-20T00:17:47.704Z</updated>
    
    <content type="html"><![CDATA[<h2 id="login-path"><a href="#login-path" class="headerlink" title="login-path"></a>login-path</h2><p><code>login-path</code> 选项能实现快捷登录MySQL服务器，我们不需要每次都输入用户名、host、密码等登录认证信息，是一种即安全又遍历的方法。</p><p>使用 <code>mysql_config_editor</code> 工具将登录MySQL服务的认证信息加密保存在 <code>.mylogin.cnf</code> 文件（默认位于用户主目录）。之后 MySQL 客户端工具可通过读取该加密文件连接 MySQL，避免重复输入登录信息和敏感信息暴露。</p><h3 id="配置-login-path"><a href="#配置-login-path" class="headerlink" title="配置 login-path"></a>配置 login-path</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">mysql_config_editor <span class="built_in">set</span> --login-path=dev --user=dev --host=172.26.8.143 --password</span><br></pre></td></tr></table></figure><p>可以配置多个登录信息。<code>login-path</code>可以看做登录信息的别名，方便使用时引用。更详细的配置信息查看 <code>mysql_config_editor set --help</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">mysql_config_editor <span class="built_in">set</span> --login-path=<span class="built_in">test</span> --user=<span class="built_in">test</span> --host=172.26.8.144 --password</span><br></pre></td></tr></table></figure><p>这里又配置了一个 <code>login-path</code> 为 <code>test</code> 的登录信息。</p><h3 id="查看配置过的登录信息"><a href="#查看配置过的登录信息" class="headerlink" title="查看配置过的登录信息"></a>查看配置过的登录信息</h3><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">mysql_config_editor print --all</span><br></pre></td></tr></table></figure><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[dev]</span></span><br><span class="line"><span class="attr">user</span> = dev</span><br><span class="line"><span class="attr">password</span> = *****</span><br><span class="line"><span class="attr">host</span> = <span class="number">172.26</span>.<span class="number">8.143</span></span><br><span class="line"><span class="section">[test]</span></span><br><span class="line"><span class="attr">user</span> = test</span><br><span class="line"><span class="attr">password</span> = *****</span><br><span class="line"><span class="attr">host</span> = <span class="number">172.26</span>.<span class="number">8.144</span></span><br></pre></td></tr></table></figure><h3 id="使用-login-path-登录"><a href="#使用-login-path-登录" class="headerlink" title="使用 login-path 登录"></a>使用 <code>login-path</code> 登录</h3><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">mysql --login-path=dev</span><br></pre></td></tr></table></figure><h3 id="删除-login-path"><a href="#删除-login-path" class="headerlink" title="删除 login-path"></a>删除 <code>login-path</code></h3><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">mysql_config_editor remove --login-path=dev</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://www.cnblogs.com/David-domain/p/11176474.html">MySQL login-path 本地快捷登录</a></li><li><a href="https://dev.mysql.com/doc/refman/5.7/en/mysql-config-editor.html">MySQL Configuration Utility</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;login-path&quot;&gt;&lt;a href=&quot;#login-path&quot; class=&quot;headerlink&quot; title=&quot;login-path&quot;&gt;&lt;/a&gt;login-path&lt;/h2&gt;&lt;p&gt;&lt;code&gt;login-path&lt;/code&gt; 选项能实现快捷登录MySQL</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>system-time</title>
    <link href="https://kekek.cc/post/system-time.html"/>
    <id>https://kekek.cc/post/system-time.html</id>
    <published>2020-08-07T08:20:03.000Z</published>
    <updated>2020-08-19T02:30:07.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="计算机系统时间"><a href="#计算机系统时间" class="headerlink" title="计算机系统时间"></a>计算机系统时间</h2><p>我们常说的计算机系统（类Unix系统）时间是一个相对值，它表示距离<code>1970年1月1日00:00:00</code>过去的秒数。</p><p>时间通常在程序中有时间戳和日期字符串两种表现形式。但是在计算机中其实只有一种，就是时间戳。使用时间戳是永远不会出问题，但是使用日期字符串某些时候就会出现问题。出现问题的原因一般就是因为省略了时区。</p><h3 id="时区"><a href="#时区" class="headerlink" title="时区"></a>时区</h3><p>我们生活中常常用日期字符串来表示时间，如下：</p><iframe scrolling="no" width="100%" height="300" src="https://jsfiddle.net/pLxr4mqt/embedded/result/light/" frameborder="0" loading="lazy" allowfullscreen="allowfullscreen"></iframe><p>可能有些人在程序中也会使用日期字符串来初始化日期：</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">new</span> <span class="title class_">Date</span>(<span class="string">&#x27;2020-08-07 18:21:33&#x27;</span>);</span><br></pre></td></tr></table></figure><p>很多情况下这样都没有问题，但是一旦用户分布在不同时区或修改系统时区时就会出现问题。上面的日期字符串其实隐含了一个条件<code>时区</code>，比如我们生活中常说“今天下午五点见个面”这里隐含的条件就是大家都在东八区。</p><p>在东八区（北京时间）时，上面的时间等价于这个形式：</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">new</span> <span class="title class_">Date</span>(<span class="string">&#x27;2020-08-07T18:21:33+08:00&#x27;</span>);</span><br></pre></td></tr></table></figure><p>但是在东九区（东京时间）它等价于：</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">new</span> <span class="title class_">Date</span>(<span class="string">&#x27;2020-08-07T18:21:33+09:00&#x27;</span>);</span><br></pre></td></tr></table></figure><p>这两个时间并不是同一个日期。这就是日期字符串在夸时区出现问题的原因。</p><p>在实际开发中，我们应该避免使用简单字符串格式<code>YYYY-MM-DD HH:mm:ss</code>表示日期，这是非常危险的。如果要使用字符串来存储和初始化日期应该加上时区，使用<code>ISO 8601</code>格式的字符串<code>YYYY-MM-DDTHH:mm:ss.sssZ</code>。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://www.wikiwand.com/zh-hans/ISO_8601">ISO 8601</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString">toISOString</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;计算机系统时间&quot;&gt;&lt;a href=&quot;#计算机系统时间&quot; class=&quot;headerlink&quot; title=&quot;计算机系统时间&quot;&gt;&lt;/a&gt;计算机系统时间&lt;/h2&gt;&lt;p&gt;我们常说的计算机系统（类Unix系统）时间是一个相对值，它表示距离&lt;code&gt;1970年1月1日00</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Babel Webpack</title>
    <link href="https://kekek.cc/post/babel-with-webpack.html"/>
    <id>https://kekek.cc/post/babel-with-webpack.html</id>
    <published>2020-02-21T02:53:27.000Z</published>
    <updated>2021-11-20T00:17:47.667Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Babel-Webpack"><a href="#Babel-Webpack" class="headerlink" title="Babel Webpack"></a>Babel Webpack</h2><h3 id="babel-preset-env"><a href="#babel-preset-env" class="headerlink" title="@babel&#x2F;preset-env"></a>@babel&#x2F;preset-env</h3><p>用来取代以前的 <code>es2016</code>、<code>es2017</code>、<code>stage-x</code> 等等 <code>preset</code>、它是一个合集，包含已经成为标准的提案。它的<code>useBuiltIns</code>能配置如何处理<code>polyfill</code>，现在已经不在推荐使用<code>@babel/polyfill</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;presets&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="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;targets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="string">&quot;chrome &gt;= 58&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="string">&quot;iOS &gt;= 9&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="string">&quot;Android &gt;= 5&quot;</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;corejs&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">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li><p><code>useBuiltIns</code> 用来指定 <code>polyfill</code> 如何处理，配合 <code>core-js</code>使用（推荐使用<code>core-js@3</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = &#123; <span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> bar = &#123; <span class="attr">address</span>: <span class="string">&#x27;Beijing&#x27;</span>, <span class="attr">age</span>: <span class="string">&#x27;18&#x27;</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(foo, bar);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>();</span><br></pre></td></tr></table></figure><ul><li><p>usage:（推荐）会按需引入 <code>polyfill</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></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/es.array.iterator&quot;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/es.map&quot;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/es.object.assign&quot;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/es.object.to-string&quot;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/es.string.iterator&quot;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&quot;core-js/modules/web.dom-collections.iterator&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo = &#123;</span><br><span class="line"><span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> bar = &#123;</span><br><span class="line"><span class="attr">address</span>: <span class="string">&#x27;Beijing&#x27;</span>,</span><br><span class="line"><span class="attr">age</span>: <span class="string">&#x27;18&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(a, b);</span><br><span class="line"><span class="keyword">var</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>();</span><br></pre></td></tr></table></figure></li><li><p>entry: 会引入全部 <code>polyfill</code></p><blockquote><p><del>搞不清楚结果为什么和 <code>false</code> 一样，打开 <code>debug</code> 选项后会有如下提示<code>Import of core-js was not found.</code></del></p></blockquote><blockquote><p>更新：需要在代码中手动引入 <code>import &#39;core-js&#39;</code>，结果会加载非常多的<code>polyfill</code>。</p></blockquote>  <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></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo = &#123;</span><br><span class="line"><span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> bar = &#123;</span><br><span class="line"><span class="attr">address</span>: <span class="string">&#x27;Beijing&#x27;</span>,</span><br><span class="line"><span class="attr">age</span>: <span class="string">&#x27;18&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(foo, bar);</span><br><span class="line"><span class="keyword">var</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>();</span><br></pre></td></tr></table></figure></li><li><p>false: 不引入 <code>polyfill</code></p><blockquote><p><code>debug</code> 提示 <code>Using polyfills: No polyfills were added, since the *useBuiltIns* option was not set.</code> </p></blockquote>  <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></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo = &#123;</span><br><span class="line"><span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> bar = &#123;</span><br><span class="line"><span class="attr">address</span>: <span class="string">&#x27;Beijing&#x27;</span>,</span><br><span class="line"><span class="attr">age</span>: <span class="string">&#x27;18&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(foo, bar);</span><br><span class="line"><span class="keyword">var</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>();</span><br></pre></td></tr></table></figure></li></ul></li><li><p><code>targets</code> 选项用来配置配置兼容范围，语法参考 <a href="https://github.com/browserslist/browserslist">browserslist</a></p></li><li><p><code>exclude</code> 和 <code>include</code> 用来排查和强制引入某些<code>ployfill</code>，例如下例：</p><blockquote><p>测试中发现设置 <code>&quot;in P clude&quot;: [&quot;es.set&quot;]</code> 并未生效 </p></blockquote><p>  设置 <code>&quot;exclude&quot;: [&quot;es.object.assign&quot;]</code>，即使我们代码中用到了<code>Object.assign</code>方法，也不会自动引入这个<code>polyfill</code>。</p></li></ul><h3 id="babel-plugin-transform-runtime"><a href="#babel-plugin-transform-runtime" class="headerlink" title="@babel&#x2F;plugin-transform-runtime"></a>@babel&#x2F;plugin-transform-runtime</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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;presets&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="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;targets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="string">&quot;chrome &gt;= 58&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="string">&quot;iOS &gt;= 9&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="string">&quot;Android &gt;= 5&quot;</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&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">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;plugins&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="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;corejs&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">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>一般情况下不需要，需要配置 <code>@babel/runtime</code>（不是<strong>开发依赖</strong>） 一起使用。使用了这个后能复用一些<code>helpers</code>代码，能避免污染全局。下面是打开使用这个插件后的转义代码：</p><p>我们看到 <code>Map</code> 和 <code>Object.assign</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _interopRequireDefault = <span class="built_in">require</span>(<span class="string">&quot;@babel/runtime-corejs3/helpers/interopRequireDefault&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> _map = <span class="title function_">_interopRequireDefault</span>(<span class="built_in">require</span>(<span class="string">&quot;@babel/runtime-corejs3/core-js-stable/map&quot;</span>));</span><br><span class="line"><span class="keyword">var</span> _assign = <span class="title function_">_interopRequireDefault</span>(<span class="built_in">require</span>(<span class="string">&quot;@babel/runtime-corejs3/core-js-stable/object/assign&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> bar = &#123;</span><br><span class="line">  <span class="attr">address</span>: <span class="string">&#x27;Beijing&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="string">&#x27;18&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line">(<span class="number">0</span>, _assign.<span class="property">default</span>)(foo, bar);</span><br><span class="line"><span class="keyword">var</span> map = <span class="keyword">new</span> _map.<span class="title function_">default</span>();</span><br></pre></td></tr></table></figure><h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><ul><li>配合<code>webpack</code>的 <code>splitChunks</code> 将 <code>polyfill</code> 打包到一个文件中</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line"><span class="attr">splitChunks</span>: &#123;</span><br><span class="line"><span class="comment">// cacheGroups 指定拆分规则 默认的拆分规则如下 &lt;https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks&gt;</span></span><br><span class="line"><span class="attr">cacheGroups</span>: &#123;</span><br><span class="line"><span class="attr">polyfill</span>: &#123;</span><br><span class="line"><span class="comment">// 一个正则匹配那些代码适用这个规则</span></span><br><span class="line"><span class="attr">test</span>: <span class="regexp">/core-js/</span>, </span><br><span class="line"><span class="comment">// 表示从哪些chunks里面抽取代码，除了三个可选字符串值 initial、async、all 之外，还可以通过函数来过滤所需的 chunks</span></span><br><span class="line"><span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</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></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://www.babeljs.cn/docs/babel-preset-env">@babel&#x2F;preset-env</a></li><li><a href="https://www.babeljs.cn/docs/babel-polyfill">@babel&#x2F;polyfill</a></li><li><a href="https://www.babeljs.cn/docs/babel-plugin-transform-runtime">@babel&#x2F;plugin-transform-runtime</a></li><li><a href="https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkschunks">SplitChunksPlugin</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Babel-Webpack&quot;&gt;&lt;a href=&quot;#Babel-Webpack&quot; class=&quot;headerlink&quot; title=&quot;Babel Webpack&quot;&gt;&lt;/a&gt;Babel Webpack&lt;/h2&gt;&lt;h3 id=&quot;babel-preset-env&quot;&gt;&lt;a </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>jq</title>
    <link href="https://kekek.cc/post/jq.html"/>
    <id>https://kekek.cc/post/jq.html</id>
    <published>2019-06-21T11:14:31.000Z</published>
    <updated>2021-11-20T00:17:47.690Z</updated>
    
    <content type="html"><![CDATA[<h2 id="JQ"><a href="#JQ" class="headerlink" title="JQ"></a>JQ</h2><p>jq 是个非常强大的处理 JSON 数据的命令行工具。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><ul><li><p>Linux</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">yum install jq</span><br></pre></td></tr></table></figure></li><li><p>macOS</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">brew install jq</span><br></pre></td></tr></table></figure></li></ul><h3 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h3><blockquote><p>注意：示例数据不是一段json数据，它的每一行都是一条json数据。每一行都会处理一次。</p></blockquote><ol><li>格式化</li></ol><blockquote><p>这里的 <code>.</code> 表示当前JSON对象</p></blockquote><figure 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 https://kekek.cc/static/jq.json | jq .</span><br></pre></td></tr></table></figure><ol start="2"><li>查询</li></ol><blockquote><p>查询 <code>remote_addr</code> 为 <code>187.141.142.230</code> 的用户</p></blockquote><figure 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 https://kekek.cc/static/jq.json | jq <span class="string">&#x27;select (.[&quot;remote_addr&quot;] == &quot;187.141.142.230&quot;)&#x27;</span></span><br></pre></td></tr></table></figure><p><code>select(boolean_expression)</code> 为查询语句，<code>.[&quot;remote_addr&quot;]</code> 表示取对象的<code>remote_addr</code>属性（除了可以用<code>.[]</code>取值还可以直接<code>.</code>取值，前者可以处理特殊字符的情况）。 <code>==</code> 表示等于。</p><blockquote><p>查询 <code>method</code> 为 <code>GET</code> 的请求，并且<code>status</code> 为 <code>200</code></p></blockquote><figure 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 https://kekek.cc/static/jq.json | jq <span class="string">&#x27;select (.[&quot;method&quot;] == &quot;GET&quot; and .status == 200)&#x27;</span></span><br></pre></td></tr></table></figure><ol start="3"><li>管道</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">interface</span> <span class="title class_">AccessLog</span> &#123;</span><br><span class="line"><span class="attr">ip</span>: <span class="built_in">string</span>;</span><br><span class="line"><span class="attr">method</span>: <span class="built_in">string</span>;</span><br><span class="line"><span class="attr">url</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure 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 https://kekek.cc/static/jq.json | jq <span class="string">&#x27;. | &#123;ip: .[&quot;remote_addr&quot;], method: .method, url: .[&quot;request_uri&quot;]&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>这里使用了管道符 <code>|</code> 可以多次处理结果</p><ol start="4"><li>输出csv格式</li></ol><blockquote><p>csv 的输入要求必须是一个数组</p></blockquote><figure 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 https://kekek.cc/static/jq.json | jq <span class="string">&#x27;. | [.[&quot;remote_addr&quot;], .method, .status]] | @csv&#x27;</span></span><br></pre></td></tr></table></figure><ol start="5"><li>数组</li></ol><p><code>.[]</code> 表示取整个数组，一般配合管道符号一起使用 <code>.[] | {ip: .[&quot;remote_addr&quot;], method: .method, url: .[&quot;request_uri&quot;]}</code></p><p><code>.[0]</code> 表示取数组的第一个元素</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://stedolan.github.io/jq/tutorial/">Tutorial</a></li><li><a href="https://stedolan.github.io/jq/manual/">jq Manual</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;JQ&quot;&gt;&lt;a href=&quot;#JQ&quot; class=&quot;headerlink&quot; title=&quot;JQ&quot;&gt;&lt;/a&gt;JQ&lt;/h2&gt;&lt;p&gt;jq 是个非常强大的处理 JSON 数据的命令行工具。&lt;/p&gt;
&lt;h3 id=&quot;安装&quot;&gt;&lt;a href=&quot;#安装&quot; class=&quot;heade</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>对5G的一些看法</title>
    <link href="https://kekek.cc/post/5G.html"/>
    <id>https://kekek.cc/post/5G.html</id>
    <published>2019-06-07T10:05:51.000Z</published>
    <updated>2021-11-20T00:17:47.663Z</updated>
    
    <content type="html"><![CDATA[<h2 id="对5G的一些看法"><a href="#对5G的一些看法" class="headerlink" title="对5G的一些看法"></a>对5G的一些看法</h2><p>5G（5th-Generation）是第五代<strong>移动</strong>通信技术的意思，相比4G它有更低的延时和更快的速度。</p><ol><li><p>低延时</p><p> 我们常听到5G可以把延时降低到<code>1ms</code>，这句话很有误导性。会让大部分人觉得端到端的延迟在5G时代只有<code>1ms</code>了，这其实是不准确的。这个<code>1ms</code>其实指的是从设备到<code>5G</code>基站的延时，并不是端到端的延时。换个角度想，<code>5G</code>基站还是要通过光纤网络接入互联网的，那么它就不可能比光纤延时更低了。现在光纤网络的延时通常远远大于<code>1ms</code>，这主要取决于距离。</p></li><li><p>更快的速度</p><p> 移动网络速度每一代都比上一代有很大的提升。最新在网络上看到的一些测试，在<code>5G</code>网络中下载速度能到达<code>900Mbps</code>，约为<code>110MB/s</code>左右。</p></li></ol><h3 id="5G带来的影响"><a href="#5G带来的影响" class="headerlink" title="5G带来的影响"></a>5G带来的影响</h3><p>我们先看下网上常说的5G对未来生活的影响，来一一分析一下。</p><ol><li><p>万物互联</p><p> 我觉得这主要这主要得益于5G后整体带宽的提升，各种设备要传输大量的数据。低延时对于这方面的提升并不会明显。伴随着万物互联IPV6可能会迎来发展。现有网络环境下我们通过<code>NAT</code>技术缓解了<code>IPV4</code>不够用的问题，这也导致了<code>IPV6</code>这么多年来发展十分缓慢。在<code>5G</code>时代可能会有更多的设备，传感器通过<code>5G</code>网络接入互联网，那就需要大量IP了。</p></li><li><p>自动驾驶</p><p> <code>5G</code>对于自动驾驶的影响可能不是特别大，自动驾驶并不是将数据传输到服务器计算的，它是本地计算的。自动驾驶更大的难点可能还是现有道路规则都是为有人驾驶设计的，自动驾驶只能去适应现有规则。有了更高的网速，在道路上停留一个多小时下载更新包的尴尬情况起码不会再出现了╮(╯_╰)╭ 但是如果汽车间以后要互相通信，那么低延时技术可能会有帮助。</p></li><li><p>远程医疗</p><p> 我觉得<code>5G</code>对于远程医疗的影响我觉得极其有限。远程医疗非常依赖于低延时，但是5G在低延时和稳定性方面都不会比直接使用光纤网络更好。手术设备完全可以接入现有光纤网络，通过5G接入完全是没有必要的。有人说对于偏远地区光纤不好铺设，这时候使用5G就很方便了。这种观点其实也有问题，5G网络的覆盖非常小。只有几百米。也就是说我们能搜索到5G信号的地方都最近的光纤网络也只有几百米。</p></li><li><p>云</p><p> 云存储会迎来大发展。<code>5G</code>时代如果我们下载速度能达到<code>100+MB/s</code>，<code>iCloud</code>类的服务将会迎来更大发展。我们的设备完全没必要很大的存储了，我们不需要把数据都存在本地。128G、256G甚至更大容量的手机可能会很少了。</p><p> 云操作系统、云游戏可能会迎来发展的机会。对于延时不高的操作完全可以再云服务器上完成，我们的设备不在需要强大的硬件。Chromebook类似的设备和Google Stadia类似的云游戏平台可能会有更多。</p></li></ol><p>我觉得5G对我们影响最大的还是更快的网速，这将极大提升效率，为我们生活带来更多的便利。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;对5G的一些看法&quot;&gt;&lt;a href=&quot;#对5G的一些看法&quot; class=&quot;headerlink&quot; title=&quot;对5G的一些看法&quot;&gt;&lt;/a&gt;对5G的一些看法&lt;/h2&gt;&lt;p&gt;5G（5th-Generation）是第五代&lt;strong&gt;移动&lt;/strong&gt;通信技术的</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>家用NAS搭建</title>
    <link href="https://kekek.cc/post/network-attached-storage.html"/>
    <id>https://kekek.cc/post/network-attached-storage.html</id>
    <published>2019-05-25T14:53:15.000Z</published>
    <updated>2021-11-20T00:17:47.706Z</updated>
    
    <content type="html"><![CDATA[<h2 id="家用NAS搭建"><a href="#家用NAS搭建" class="headerlink" title="家用NAS搭建"></a>家用NAS搭建</h2><p>手里有一个树莓派，刚好有几张闲置的SD卡。闲来没事搭建个<code>NAS</code>（Network Attached Storage）服务。使用树莓派搭建<code>NAS</code>服务优点就是省电，缺点是网卡有点差。</p><h3 id="部署及配置"><a href="#部署及配置" class="headerlink" title="部署及配置"></a>部署及配置</h3><ol><li><p>安装<code>samba</code></p> <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"><span class="meta prompt_">$ </span><span class="language-bash">apt-get install samba samba-common-bin</span></span><br></pre></td></tr></table></figure></li><li><p>配置</p> <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"><span class="meta prompt_">$ </span><span class="language-bash">vim /etc/samba/smb.conf</span></span><br></pre></td></tr></table></figure><p> 找到<code>Share Definitions</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></pre></td><td class="code"><pre><span class="line"># [共享名] </span><br><span class="line">[ytdl]</span><br><span class="line">    comment = share # 共享描述</span><br><span class="line">    path = /data/share/  # 共享目录</span><br><span class="line">    valid users = pi # 允许访问该共享的用户 多个用户逗号分隔</span><br><span class="line">    browseable = yes # 该指定共享目录可浏览</span><br><span class="line">    public = no # 允许guest用户访问</span><br><span class="line">    writable = yes # 允许可写</span><br></pre></td></tr></table></figure><blockquote><p>配置完成重启服务器即可</p></blockquote></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">$ smbpasswd -a [user]</span><br></pre></td></tr></table></figure><blockquote><p>根据提示输入密码即可</p></blockquote></li><li><p>启动和停止</p><ul><li>启动：&#x2F;etc&#x2F;init.d&#x2F;samba restart</li><li>停止：&#x2F;etc&#x2F;init.d&#x2F;samba stop</li><li>重启：&#x2F;etc&#x2F;init.d&#x2F;samba restart</li><li>重新加载配置文件：&#x2F;etc&#x2F;init.d&#x2F;samba reload</li></ul></li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://www.jianshu.com/p/ead92b06318e">在树莓派上安装Samba实现树莓派与Windows间的文件共享</a></li><li><a href="https://blog.csdn.net/weixin_40806910/article/details/81917077">Linux samba的配置和使用</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;家用NAS搭建&quot;&gt;&lt;a href=&quot;#家用NAS搭建&quot; class=&quot;headerlink&quot; title=&quot;家用NAS搭建&quot;&gt;&lt;/a&gt;家用NAS搭建&lt;/h2&gt;&lt;p&gt;手里有一个树莓派，刚好有几张闲置的SD卡。闲来没事搭建个&lt;code&gt;NAS&lt;/code&gt;（Netwo</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>在家庭宽带部署Web服务器</title>
    <link href="https://kekek.cc/post/deploy-web-server-on-home-broadband.html"/>
    <id>https://kekek.cc/post/deploy-web-server-on-home-broadband.html</id>
    <published>2018-09-10T09:35:57.000Z</published>
    <updated>2021-11-20T00:17:47.672Z</updated>
    
    <content type="html"><![CDATA[<h2 id="在家庭宽带部署Web服务器"><a href="#在家庭宽带部署Web服务器" class="headerlink" title="在家庭宽带部署Web服务器"></a>在家庭宽带部署Web服务器</h2><h3 id="端口映射"><a href="#端口映射" class="headerlink" title="端口映射"></a>端口映射</h3><p>局域网到公网要经过一层net地址转换，在路由器中有一张表记录着：</p><table><thead><tr><th>内网端口</th><th>服务器IP</th><th>服务器端口</th></tr></thead><tbody><tr><td>80</td><td>192.168.1.10</td><td>80</td></tr></tbody></table><p>我们要搭建Web服务器就需要把80端口配置到这张表中，</p><blockquote><p>注意：北京地区家庭宽带80端口被封，可以使用HTTPS443端口。</p></blockquote><p>随着这些年宽带提速以及各种直播类的需求现在家庭宽带上行速度已经不是问题。</p><h3 id="动态域名解析"><a href="#动态域名解析" class="headerlink" title="动态域名解析"></a>动态域名解析</h3><p>家庭宽带有个特点不是固定IP，每次重新拨号就会分配一个新的IP。这就要使用动态域名解析服务了，像花生壳之类的软件。我们也可以基于阿里云提供的<a href="https://help.aliyun.com/document_detail/29739.html">DNS API</a>自己来实现这个功能：</p><figure class="highlight js"><figcaption><span>ddns.js</span><a href="/downloads/code/ddns.js">view raw</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env node</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> url = <span class="built_in">require</span>(<span class="string">&#x27;url&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> https = <span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> stringCompare = <span class="title class_">String</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">localeCompare</span>;</span><br><span class="line"><span class="keyword">const</span> padStart = <span class="title class_">String</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">padStart</span>;</span><br><span class="line"><span class="keyword">const</span> padEnd = <span class="title class_">String</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">padEnd</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// accesskeys 访问&lt;https://ram.console.aliyun.com/#/user/list&gt;申请</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ACCESS_KEY_ID</span> = <span class="string">&#x27;YOUR_ACCESS_KEY_ID&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ACCESS_KEY_SECRET</span> = <span class="string">&#x27;YOUR_ACCESS_KEY_SECRET&#x27;</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 比较字符串</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * String.prototype.localeCompare 算法有问题 AA &gt; Aa </span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} <span class="variable">str1</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} <span class="variable">str2</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compareString</span>(<span class="params">str1, str2</span>) {</span><br><span class="line">    <span class="keyword">const</span> len1 = str1.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">const</span> len2 = str2.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">const</span> lim = <span class="title class_">Math</span>.<span class="title function_">min</span>(len1, len2);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> str1codes = str1.<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>).<span class="title function_">map</span>(<span class="function"><span class="params">char</span> =&gt;</span> char.<span class="title function_">codePointAt</span>(<span class="number">0</span>));</span><br><span class="line">    <span class="keyword">const</span> str2codes = str2.<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>).<span class="title function_">map</span>(<span class="function"><span class="params">char</span> =&gt;</span> char.<span class="title function_">codePointAt</span>(<span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> k = <span class="number">0</span>, c1, c2;</span><br><span class="line">    <span class="keyword">while</span> (k &lt; lim) {</span><br><span class="line">        c1 = str1codes[k];</span><br><span class="line">        c2 = str2codes[k];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (c1 !== c2) {</span><br><span class="line">            <span class="keyword">return</span> c1 - c2;</span><br><span class="line">        }</span><br><span class="line">        k++;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> len1 - len2;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sha1</span>(<span class="params">str, key</span>) {</span><br><span class="line">    <span class="keyword">return</span> crypto.<span class="title function_">createHmac</span>(<span class="string">&#x27;sha1&#x27;</span>, key)</span><br><span class="line">        .<span class="title function_">update</span>(str)</span><br><span class="line">        .<span class="title function_">digest</span>()</span><br><span class="line">        .<span class="title function_">toString</span>(<span class="string">&#x27;base64&#x27;</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 class="comment"> * 获取时间字符串 YYYY-MM-DDTHH:mm:ssZ</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} <span class="variable">date</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">utcDateFormat</span>(<span class="params">time</span>) {</span><br><span class="line">    <span class="comment">// YYYY-MM-DDTHH:mm:ss.sssZ include milliseconds</span></span><br><span class="line">    <span class="comment">// return time.toISOString().match(/^(\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2})/)[1] + &#x27;Z&#x27;;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> year = time.<span class="title function_">getUTCFullYear</span>();</span><br><span class="line">    <span class="keyword">const</span> month = padStart.<span class="title function_">call</span>(time.<span class="title function_">getUTCMonth</span>() + <span class="number">1</span>, <span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> date = padStart.<span class="title function_">call</span>(time.<span class="title function_">getUTCDate</span>(), <span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> hours = padStart.<span class="title function_">call</span>(time.<span class="title function_">getUTCHours</span>(), <span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> minutes = padStart.<span class="title function_">call</span>(time.<span class="title function_">getUTCMinutes</span>(), <span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> secounds = padStart.<span class="title function_">call</span>(time.<span class="title function_">getUTCSeconds</span>(), <span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">${year}</span>-<span class="subst">${month}</span>-<span class="subst">${date}</span>T<span class="subst">${hours}</span>:<span class="subst">${minutes}</span>:<span class="subst">${secounds}</span>Z`</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 class="comment"> * 字符串编码（阿里规则）</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 规则：&lt;https://help.aliyun.com/document_detail/29747.html&gt; 1.b</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} <span class="variable">str</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">percentEncode</span>(<span class="params">str</span>) {</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">encodeURIComponent</span>(str).<span class="title function_">replace</span>(<span class="string">&#x27;+&#x27;</span>, <span class="string">&#x27;%20&#x27;</span>).<span class="title function_">replace</span>(<span class="string">&#x27;*&#x27;</span>, <span class="string">&#x27;%2A&#x27;</span>).<span class="title function_">replace</span>(<span class="string">&#x27;%7E&#x27;</span>, <span class="string">&#x27;~&#x27;</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 class="comment"> * HTTPs GET</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> url, timeout = 5000, json = true </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> query 请求参数对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> json 如果为true并且content-type为application/json的情况下会反序列化</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span> </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> 如果响应码为4xx或者5xx响应会作为错误抛出</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">request</span>(<span class="params">{ url: uri, query = {}, timeout = <span class="number">5000</span>, json = <span class="literal">true</span> }</span>) {</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> {</span><br><span class="line">        <span class="comment">// 合并query</span></span><br><span class="line">        <span class="keyword">const</span> urlObject = url.<span class="title function_">parse</span>(uri, <span class="literal">true</span>);</span><br><span class="line">        <span class="title class_">Object</span>.<span class="title function_">assign</span>(urlObject.<span class="property">query</span>, query);</span><br><span class="line">        urlObject.<span class="property">search</span> = <span class="literal">null</span>;</span><br><span class="line">        uri = url.<span class="title function_">format</span>(urlObject);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// support HTTPs</span></span><br><span class="line">        <span class="keyword">let</span> protocol;</span><br><span class="line">        <span class="keyword">if</span> (urlObject.<span class="property">protocol</span> === <span class="string">&#x27;https:&#x27;</span>) protocol = https;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (urlObject.<span class="property">protocol</span> === <span class="string">&#x27;http:&#x27;</span>) protocol = http;</span><br><span class="line">        <span class="keyword">else</span> { <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`unsupported protocol [<span class="subst">${urlObject.protocol}</span>]`</span>) };</span><br><span class="line"></span><br><span class="line">        <span class="keyword">const</span> req = protocol.<span class="title function_">get</span>(uri)</span><br><span class="line">        req.<span class="built_in">setTimeout</span>(timeout);</span><br><span class="line">        req.<span class="title function_">on</span>(<span class="string">&#x27;timeout&#x27;</span>, <span class="function">() =&gt;</span> req.<span class="title function_">abort</span>());</span><br><span class="line"></span><br><span class="line">        req.<span class="title function_">on</span>(<span class="string">&#x27;response&#x27;</span>, <span class="function">(<span class="params">res</span>) =&gt;</span> {</span><br><span class="line">            res.<span class="title function_">setEncoding</span>(<span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> { statusCode, headers } = res;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">let</span> rawData = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">            res.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">chunk</span>) =&gt;</span> { rawData += chunk; });</span><br><span class="line">            res.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">                <span class="keyword">let</span> data = rawData;</span><br><span class="line">                <span class="keyword">if</span> (json &amp;&amp; -<span class="number">1</span> !== headers[<span class="string">&#x27;content-type&#x27;</span>].<span class="title function_">indexOf</span>(<span class="string">&#x27;application/json&#x27;</span>)) {</span><br><span class="line">                    data = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(rawData);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (statusCode &gt;= <span class="number">400</span>) {</span><br><span class="line">                    <span class="title function_">reject</span>(data);</span><br><span class="line">                } <span class="keyword">else</span> {</span><br><span class="line">                    <span class="title function_">resolve</span>(data);</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">        req.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, reject);</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="keyword">function</span> <span class="title function_">timeout</span>(<span class="params">time</span>) {</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> {</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">            <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;operation timed out&#x27;</span>));</span><br><span class="line">        }, time);</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">/**</span></span><br><span class="line"><span class="comment"> * 查询本机公网IP</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">findMyIP</span>(<span class="params"></span>) {</span><br><span class="line">    <span class="keyword">const</span> rawData = <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">race</span>([</span><br><span class="line">        <span class="title function_">request</span>({ <span class="attr">url</span>: <span class="string">&#x27;http://myip.ipip.net&#x27;</span>, <span class="attr">json</span>: <span class="literal">false</span> }),</span><br><span class="line">        <span class="comment">// request({ url: &#x27;http://ipinfo.io&#x27;, json: false }),</span></span><br><span class="line">        <span class="title function_">request</span>({ <span class="attr">url</span>: <span class="string">&#x27;https://api.ipify.org&#x27;</span>, <span class="attr">json</span>: <span class="literal">false</span> })</span><br><span class="line">    ]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> match = rawData.<span class="title function_">match</span>(<span class="regexp">/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/</span>);</span><br><span class="line">    <span class="keyword">if</span> (!match) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`无法解析IP。<span class="subst">${rawData}</span>`</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> match[<span class="number">1</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成公共参数</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 参考：&lt;https://help.aliyun.com/document_detail/29745.html&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> accessKeyId, format = &#x27;JSON&#x27;, signatureNonce = Math.random(), timestamp = utcDateFormat(new Date()) </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">generatePublicParams</span>(<span class="params">{ accessKeyId, format = <span class="string">&#x27;JSON&#x27;</span>, signatureNonce = <span class="built_in">Math</span>.random(), timestamp = utcDateFormat(<span class="keyword">new</span> <span class="built_in">Date</span>()) }</span>) {</span><br><span class="line">    <span class="keyword">return</span> {</span><br><span class="line">        <span class="string">&#x27;AccessKeyId&#x27;</span>: accessKeyId,</span><br><span class="line">        <span class="string">&#x27;Format&#x27;</span>: format,</span><br><span class="line">        <span class="string">&#x27;Version&#x27;</span>: <span class="string">&#x27;2015-01-09&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;SignatureMethod&#x27;</span>: <span class="string">&#x27;HMAC-SHA1&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;SignatureNonce&#x27;</span>: signatureNonce,</span><br><span class="line">        <span class="string">&#x27;SignatureVersion&#x27;</span>: <span class="string">&#x27;1.0&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Timestamp&#x27;</span>: timestamp</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 class="comment"> * 签名</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 规则：&lt;https://help.aliyun.com/document_detail/29747.html&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> params, accesskeySecret </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sign</span>(<span class="params">{ params, accesskeySecret }</span>) {</span><br><span class="line">    <span class="keyword">const</span> query = <span class="title class_">Object</span>.<span class="title function_">entries</span>(params)</span><br><span class="line">        .<span class="title function_">sort</span>(<span class="function">(<span class="params">param1, param2</span>) =&gt;</span> <span class="title function_">compareString</span>(param1[<span class="number">0</span>], param2[<span class="number">0</span>]))</span><br><span class="line">        .<span class="title function_">map</span>(<span class="function">(<span class="params">[key, val]</span>) =&gt;</span> [<span class="title function_">percentEncode</span>(key), <span class="title function_">percentEncode</span>(val)])</span><br><span class="line">        .<span class="title function_">map</span>(<span class="function"><span class="params">param</span> =&gt;</span> param.<span class="title function_">join</span>(<span class="string">&#x27;=&#x27;</span>))</span><br><span class="line">        .<span class="title function_">join</span>(<span class="string">&#x27;&amp;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> stringToSign = <span class="string">&#x27;GET&amp;&#x27;</span> + <span class="title function_">percentEncode</span>(<span class="string">&#x27;/&#x27;</span>) + <span class="string">&#x27;&amp;&#x27;</span> + <span class="title function_">percentEncode</span>(query);</span><br><span class="line">    <span class="keyword">const</span> signStr = <span class="title function_">sha1</span>(stringToSign, <span class="string">`<span class="subst">${accesskeySecret}</span>&amp;`</span>);</span><br><span class="line">    <span class="keyword">return</span> signStr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 请求接口</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> params, accessKeyId, accesskeySecret </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">callApi</span>(<span class="params">{ params, accessKeyId, accesskeySecret }</span>) {</span><br><span class="line">    <span class="comment">// 签名</span></span><br><span class="line">    <span class="keyword">const</span> signStr = <span class="title function_">sign</span>({ params, accesskeySecret });</span><br><span class="line">    params[<span class="string">&#x27;Signature&#x27;</span>] = signStr;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> <span class="title function_">request</span>({ <span class="attr">url</span>: <span class="string">&#x27;https://alidns.aliyuncs.com/&#x27;</span>, <span class="attr">query</span>: params });</span><br><span class="line">    <span class="keyword">return</span> data;</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 class="comment"> * 获取解析记录列表</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 参考：&lt;https://help.aliyun.com/document_detail/29776.html&gt;</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> domainName, rrKeyWord, pageNumber = 1, pageSize = 20, typeKeyWord = &#x27;A&#x27;, accessKeyId = ACCESS_KEY_ID, accesskeySecret = ACCESS_KEY_SECRET </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">describeDomainRecords</span>(<span class="params">{ domainName, rrKeyWord, pageNumber = <span class="number">1</span>, pageSize = <span class="number">20</span>, typeKeyWord = <span class="string">&#x27;A&#x27;</span>, accessKeyId = ACCESS_KEY_ID, accesskeySecret = ACCESS_KEY_SECRET }</span>) {</span><br><span class="line">    <span class="keyword">const</span> params = {</span><br><span class="line">        <span class="string">&#x27;Action&#x27;</span>: <span class="string">&#x27;DescribeDomainRecords&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;DomainName&#x27;</span>: domainName,</span><br><span class="line">        <span class="string">&#x27;PageNumber&#x27;</span>: pageNumber,</span><br><span class="line">        <span class="string">&#x27;PageSize&#x27;</span>: pageSize,</span><br><span class="line">        <span class="string">&#x27;RRKeyWord&#x27;</span>: rrKeyWord,</span><br><span class="line">        <span class="string">&#x27;TypeKeyWord&#x27;</span>: typeKeyWord</span><br><span class="line">    };</span><br><span class="line"></span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">assign</span>(params, <span class="title function_">generatePublicParams</span>({ accessKeyId }));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> resp = <span class="keyword">await</span> <span class="title function_">callApi</span>({ params, accessKeyId, accesskeySecret });</span><br><span class="line">    <span class="keyword">return</span> resp;</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 class="comment"> * 修改解析记录</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 参考：&lt;https://help.aliyun.com/document_detail/29774.html&gt;</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">*</span>} {<span class="type"> recordId, rr, value, type = &#x27;A&#x27;, ttl = 600, line = &#x27;default&#x27;, accessKeyId = ACCESS_KEY_ID, accesskeySecret = ACCESS_KEY_SECRET </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> ttl 基础版最小600秒</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">updateDomainRecord</span>(<span class="params">{ recordId, rr, value, type = <span class="string">&#x27;A&#x27;</span>, ttl = <span class="number">600</span>, line = <span class="string">&#x27;default&#x27;</span>, accessKeyId = ACCESS_KEY_ID, accesskeySecret = ACCESS_KEY_SECRET }</span>) {</span><br><span class="line">    <span class="keyword">const</span> params = {</span><br><span class="line">        <span class="string">&#x27;Action&#x27;</span>: <span class="string">&#x27;UpdateDomainRecord&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;RecordId&#x27;</span>: recordId,</span><br><span class="line">        <span class="string">&#x27;RR&#x27;</span>: rr,</span><br><span class="line">        <span class="string">&#x27;Type&#x27;</span>: type,</span><br><span class="line">        <span class="string">&#x27;Value&#x27;</span>: value,</span><br><span class="line">        <span class="string">&#x27;TTL&#x27;</span>: ttl,</span><br><span class="line">        <span class="string">&#x27;Line&#x27;</span>: line</span><br><span class="line">    };</span><br><span class="line"></span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">assign</span>(params, <span class="title function_">generatePublicParams</span>({ accessKeyId }));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> resp = <span class="keyword">await</span> <span class="title function_">callApi</span>({ params, accessKeyId, accesskeySecret });</span><br><span class="line">    <span class="keyword">return</span> resp;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// findMyIP().then(console.log).catch(console.error);</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// describeDomainRecords({ domainName: &#x27;kekek.cc&#x27;, rrKeyWord: &#x27;www&#x27; }).then((recoder) =&gt; {</span></span><br><span class="line"><span class="comment">//     console.log(JSON.stringify(recoder, null, 2))</span></span><br><span class="line"><span class="comment">// }).catch(console.error)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// updateDomainRecord({ recordId: &#x27;3535020439014400&#x27;, rr: &#x27;www&#x27;, value: &#x27;202.106.0.21&#x27; }).then(console.log).catch(console.error)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ========================================&gt;</span></span><br><span class="line"></span><br><span class="line">(<span class="title function_">async</span> () =&gt; {</span><br><span class="line">    <span class="comment">// 要更新的域名</span></span><br><span class="line">    <span class="keyword">let</span> records = [];</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        records = <span class="built_in">require</span>(<span class="string">&#x27;./domains.json&#x27;</span>);</span><br><span class="line">    } <span class="keyword">catch</span> (ignored) {</span><br><span class="line">        records = [</span><br><span class="line">            {</span><br><span class="line">                <span class="attr">domainName</span>: <span class="string">&#x27;kekek.cc&#x27;</span>,</span><br><span class="line">                <span class="attr">rrKeyWord</span>: <span class="string">&#x27;www&#x27;</span></span><br><span class="line">            },</span><br><span class="line">            {</span><br><span class="line">                <span class="attr">domainName</span>: <span class="string">&#x27;kekek.cc&#x27;</span>,</span><br><span class="line">                <span class="attr">rrKeyWord</span>: <span class="string">&#x27;@&#x27;</span></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="keyword">const</span> myip = <span class="keyword">await</span> <span class="title function_">findMyIP</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> record <span class="keyword">of</span> records) {</span><br><span class="line">        <span class="keyword">const</span> recordDetails = <span class="keyword">await</span> <span class="title function_">describeDomainRecords</span>(record);</span><br><span class="line">        <span class="keyword">const</span> recordDetail = recordDetails.<span class="property">DomainRecords</span>.<span class="property">Record</span>.<span class="title function_">filter</span>(<span class="function"><span class="params">_record</span> =&gt;</span> _record.<span class="property">RR</span> === record.<span class="property">rrKeyWord</span>)[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (recordDetail &amp;&amp; recordDetail[<span class="string">&#x27;Value&#x27;</span>] !== myip) {</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[update] domainName: %s, rr: %s, before: %s, after: %s&#x27;</span>, record.<span class="property">domainName</span>, record.<span class="property">rrKeyWord</span>, recordDetail[<span class="string">&#x27;Value&#x27;</span>], myip);</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">updateDomainRecord</span>({ <span class="attr">recordId</span>: recordDetail[<span class="string">&#x27;RecordId&#x27;</span>], <span class="attr">rr</span>: record.<span class="property">rrKeyWord</span>, <span class="attr">value</span>: myip, <span class="attr">ttl</span>: record.<span class="property">ttl</span> });</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">})().<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title class_">Date</span>())).<span class="title function_">catch</span>(<span class="variable language_">console</span>.<span class="property">error</span>)</span><br></pre></td></tr></table></figure><p>缺陷：DNS是有缓存的，更新了新的解析后不能立即生效。这样网站会在一段时间内不可用。</p><blockquote><p>缓存的问题可以通过购买DNSvip服务实现最低1秒的缓存。</p></blockquote><h3 id="设备"><a href="#设备" class="headerlink" title="设备"></a>设备</h3><p>旧电脑或者树莓派</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;在家庭宽带部署Web服务器&quot;&gt;&lt;a href=&quot;#在家庭宽带部署Web服务器&quot; class=&quot;headerlink&quot; title=&quot;在家庭宽带部署Web服务器&quot;&gt;&lt;/a&gt;在家庭宽带部署Web服务器&lt;/h2&gt;&lt;h3 id=&quot;端口映射&quot;&gt;&lt;a href=&quot;#端口映射&quot;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Systemd</title>
    <link href="https://kekek.cc/post/systemd.html"/>
    <id>https://kekek.cc/post/systemd.html</id>
    <published>2018-09-10T05:34:06.000Z</published>
    <updated>2021-11-20T00:17:47.719Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Systemd"><a href="#Systemd" class="headerlink" title="Systemd"></a>Systemd</h2><h3 id="创建服务"><a href="#创建服务" class="headerlink" title="创建服务"></a>创建服务</h3><p>vim &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;nginx.service</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">[Unit]</span><br><span class="line">Description=The NGINX HTTP and reverse proxy server</span><br><span class="line">After=syslog.target network.target remote-fs.target nss-lookup.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=forking</span><br><span class="line">PIDFile=/var/run/nginx.pid</span><br><span class="line">ExecStartPre=/usr/sbin/nginx -t</span><br><span class="line">ExecStart=/usr/sbin/nginx</span><br><span class="line">ExecReload=/usr/sbin/nginx -s reload</span><br><span class="line">ExecStop=/bin/kill -s QUIT $MAINPID</span><br><span class="line">PrivateTmp=true</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</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></pre></td><td class="code"><pre><span class="line">ln -s /lib/systemd/system/nginx.service  /etc/systemd/system/multi-user.target.wants/nginx.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></pre></td><td class="code"><pre><span class="line">systemctl enable nginx</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></pre></td><td class="code"><pre><span class="line">systemctl daemon-reload</span><br></pre></td></tr></table></figure><h3 id="启动、重启、停止"><a href="#启动、重启、停止" class="headerlink" title="启动、重启、停止"></a>启动、重启、停止</h3><ol><li><p>启动nginx</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">$ systemctl start nginx</span><br></pre></td></tr></table></figure></li><li><p>重启nginx</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">$ systemctl restart nginx</span><br></pre></td></tr></table></figure></li><li><p>停止nginx</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">$ systemctl stop nginx</span><br></pre></td></tr></table></figure></li></ol><h3 id="开机启动"><a href="#开机启动" class="headerlink" title="开机启动"></a>开机启动</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable nginx</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></pre></td><td class="code"><pre><span class="line">systemctl status nginx</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://blog.csdn.net/chwshuang/article/details/68489968">systemctl管理Redis启动、停止、开机启动</a></li><li><a href="https://www.nginx.com/resources/wiki/start/topics/examples/systemd/">NGINX systemd service file</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Systemd&quot;&gt;&lt;a href=&quot;#Systemd&quot; class=&quot;headerlink&quot; title=&quot;Systemd&quot;&gt;&lt;/a&gt;Systemd&lt;/h2&gt;&lt;h3 id=&quot;创建服务&quot;&gt;&lt;a href=&quot;#创建服务&quot; class=&quot;headerlink&quot; titl</summary>
      
    
    
    
    
    <category term="systemctl" scheme="https://kekek.cc/tags/systemctl/"/>
    
  </entry>
  
  <entry>
    <title>console.log的延迟计算</title>
    <link href="https://kekek.cc/post/lazy-evaluation-of-console-log.html"/>
    <id>https://kekek.cc/post/lazy-evaluation-of-console-log.html</id>
    <published>2018-08-29T02:52:23.000Z</published>
    <updated>2021-11-20T00:17:47.692Z</updated>
    
    <content type="html"><![CDATA[<h2 id="console-log的延迟计算"><a href="#console-log的延迟计算" class="headerlink" title="console.log的延迟计算"></a>console.log的延迟计算</h2><p>我们调试JavaScript时经常会用到<code>console.log</code>打印，但是<code>console.log</code>打印对象时不那么准确。看下面的例子：</p><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="keyword">const</span> user = &#123; <span class="attr">name</span>: <span class="string">&#x27;Ming&#x27;</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(user);</span><br><span class="line">user.<span class="property">name</span> = <span class="string">&#x27;Lee&#x27;</span>;</span><br></pre></td></tr></table></figure><p><img src="/images/console-log-lazy-evaluation.jpeg"></p><p><code>user.name</code>的初始值为<code>Ming</code>，打印<code>user</code>看到的<code>name</code>值也是<code>Ming</code>。紧接着修改<code>user.name</code>值为<code>Lee</code>，然后再暂开前面打印<code>user</code>对象就看到<code>name</code>值变成了<code>Lee</code>。</p><p>这个展开看到的值是Chrome为了优化<code>console.log</code>进行的延迟计算，我们看到对象展开后的值是在展开时实时计算的。如果留意在展开对象时会有一个提示<code>Value below was evaluated just now.</code>。</p><p>为了避免上述问题在打印对象时可以将对象序列化：</p><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="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(user, <span class="literal">null</span>, <span class="number">2</span>)); <span class="comment">// 格式化</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;console-log的延迟计算&quot;&gt;&lt;a href=&quot;#console-log的延迟计算&quot; class=&quot;headerlink&quot; title=&quot;console.log的延迟计算&quot;&gt;&lt;/a&gt;console.log的延迟计算&lt;/h2&gt;&lt;p&gt;我们调试JavaScript</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>HTTP代理服务器</title>
    <link href="https://kekek.cc/post/http-proxy.html"/>
    <id>https://kekek.cc/post/http-proxy.html</id>
    <published>2018-08-24T08:22:30.000Z</published>
    <updated>2021-11-20T00:17:47.679Z</updated>
    
    <content type="html"><![CDATA[<h2 id="HTTP代理服务器"><a href="#HTTP代理服务器" class="headerlink" title="HTTP代理服务器"></a>HTTP代理服务器</h2><p>科学上网和调试网站时经常会用到代理服务器，那么代理服务器是怎么实现的呢？代理服务器和web服务器本身没有太多差别，如果是走代理请求浏览器会把请求发送到代理服务器，并且请求报文中使用<strong>完整URI</strong>（参考下图中a&#x2F;b）。</p><p><img src="/images/http-proxy.png"></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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">$ telnet css.kekek.cc 80</span><br><span class="line">Trying 47.94.202.145...</span><br><span class="line">Connected to css.kekek.cc.</span><br><span class="line">Escape character is &#x27;^]&#x27;.</span><br><span class="line">GET / HTTP/1.0</span><br><span class="line">host: css.kekek.cc</span><br><span class="line"></span><br><span class="line">HTTP/1.1 200 OK</span><br><span class="line">Server: openresty</span><br><span class="line">Date: Fri, 24 Aug 2018 08:48:31 GMT</span><br><span class="line">Content-Type: text/html; charset=UTF-8</span><br><span class="line">Content-Length: 3876</span><br><span class="line">Last-Modified: Thu, 03 May 2018 13:28:06 GMT</span><br><span class="line">Connection: close</span><br><span class="line">ETag: &quot;5aeb0e66-f24&quot;</span><br><span class="line">Accept-Ranges: bytes</span><br><span class="line"></span><br><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html lang=&quot;en&quot;&gt;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">$ telnet proxy.kekek.cc 1337</span><br><span class="line">Trying 39.106.12.213...</span><br><span class="line">Connected to proxy.kekek.cc.</span><br><span class="line">Escape character is &#x27;^]&#x27;.</span><br><span class="line">GET http://css.kekek.cc/ HTTP/1.0</span><br><span class="line">host: css.kekek.cc</span><br><span class="line"></span><br><span class="line">HTTP/1.1 200 OK</span><br><span class="line">server: openresty</span><br><span class="line">date: Fri, 24 Aug 2018 08:50:21 GMT</span><br><span class="line">content-type: text/html; charset=UTF-8</span><br><span class="line">content-length: 3876</span><br><span class="line">last-modified: Thu, 03 May 2018 13:28:06 GMT</span><br><span class="line">connection: close</span><br><span class="line">etag: &quot;5aeb0e66-f24&quot;</span><br><span class="line">accept-ranges: bytes</span><br><span class="line"></span><br><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html lang=&quot;en&quot;&gt;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>HTTPS模式下的代理和HTTP方式会有所不同，因为我们无法解析HTTPS请求。针对这种情况只能使用透传的方式，在tcp层直接将请求转发到目标服务器即可。</p><h3 id="PAC"><a href="#PAC" class="headerlink" title="PAC"></a>PAC</h3><p>现代浏览器几乎都支持 Proxy Auto-Configuration （PAC），这个可以让我们更灵活的使用代理。比如公司网站使用代理其它网站不走代理，有了PAC这都可以做到。PAC是一个JavaScript文件，MIME为<code>application/x-ns-proxy-autoconfig</code>。每个PAC文件必须有一个名为<code>FindProxyForURL(url,host)</code>的函数，用来计算访问URI时使用的代理服务器。函数返回值格式如下表：</p><table><thead><tr><th>FindProxyForURL的返回值</th><th>描　　述</th></tr></thead><tbody><tr><td>DIRECT</td><td>不经过任何代理，直接进行连接</td></tr><tr><td>PROXY host:port</td><td>应该使用指定的代理</td></tr><tr><td>SOCKS host:port</td><td>应该使用指定的 SOCKS 服务器</td></tr></tbody></table><p>示例：</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Proxy Auto-Configuration (PAC) file</span></span><br><span class="line"><span class="keyword">var</span> proxy = <span class="string">&quot;PROXY proxy.kekek.cc:1337; DIRECT;&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> direct = <span class="string">&#x27;DIRECT;&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">FindProxyForURL</span>(<span class="params">url, host</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="regexp">/\.google\.com$/</span>.<span class="title function_">test</span>(host)) &#123;</span><br><span class="line">        <span class="keyword">return</span> proxy;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> direct;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以使用<a href="https://github.com/manugarg/pacparser">pacparser</a>来测试我们的PAC文件。</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">brew install pacparser</span><br></pre></td></tr></table></figure><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><p><a href="https://github.com/tianyk/http-proxy">GitHub</a></p><figure class="highlight javascript"><figcaption><span>proxy.js</span><a href="/downloads/code/autogeneration/proxy.js">view raw</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2018-08-24 17:20:00</span></span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> url = <span class="built_in">require</span>(<span class="string">&#x27;url&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = process.<span class="property">env</span>.<span class="property">PORT</span> || <span class="number">1337</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">HOST</span> = process.<span class="property">env</span>.<span class="property">HOST</span> || <span class="string">&#x27;127.0.0.1&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">MY_IP</span> = <span class="string">&#x27;0.0.0.0&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">SERVER_TIMEOUT</span> = <span class="number">2</span> * <span class="number">60</span> * <span class="number">1000</span>; <span class="comment">// 2mins</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">SEND_TIMEOUT</span>  = <span class="number">1000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PROXY_TIMEOUT</span> = <span class="number">60</span> * <span class="number">1000</span>; <span class="comment">// 1mins</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个 HTTP 代理服务器</span></span><br><span class="line"><span class="keyword">const</span> proxy = http.<span class="title function_">createServer</span>();</span><br><span class="line"></span><br><span class="line">proxy.<span class="title function_">on</span>(<span class="string">&#x27;request&#x27;</span>, <span class="function">(<span class="params">cReq, cRes</span>) =&gt;</span> {</span><br><span class="line">    <span class="keyword">const</span> cUrl = cReq.<span class="property">url</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(cUrl);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 代理模式</span></span><br><span class="line">    <span class="keyword">if</span> (cUrl.<span class="title function_">startsWith</span>(<span class="string">&#x27;http&#x27;</span>)) {</span><br><span class="line">        <span class="keyword">const</span> { method, headers } = cReq;</span><br><span class="line">        <span class="keyword">const</span> { hostname, port = <span class="number">80</span>, path } = url.<span class="title function_">parse</span>(cReq.<span class="property">url</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// via</span></span><br><span class="line">        <span class="keyword">let</span> via = headers[<span class="string">&#x27;via&#x27;</span>];</span><br><span class="line">        <span class="keyword">if</span> (via) {</span><br><span class="line">            via = via += <span class="string">&#x27;, 1.1 proxy.kekek.cc (KEKE-Proxy)&#x27;</span>;</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            via = <span class="string">&#x27;1.1 proxy.kekek.cc (KEKE-Proxy)&#x27;</span>;</span><br><span class="line">        }</span><br><span class="line">        headers[<span class="string">&#x27;via&#x27;</span>] = via;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// x-forward-for</span></span><br><span class="line">        <span class="keyword">let</span> ips = headers[<span class="string">&#x27;x-forward-for&#x27;</span>];</span><br><span class="line">        <span class="keyword">if</span> (ips) {</span><br><span class="line">            ips += <span class="string">`, <span class="subst">${MY_IP}</span>`</span>;</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            ips = <span class="variable constant_">MY_IP</span>;</span><br><span class="line">        }</span><br><span class="line">        headers[<span class="string">&#x27;x-forward-for&#x27;</span>] = ips;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Max-Forwards</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 服务器响应超时</span></span><br><span class="line">        <span class="comment">// cRes.setTimeout(SEND_TIMEOUT, () =&gt; {</span></span><br><span class="line">        <span class="comment">//     cRes.writeHead(504, { &#x27;content-type&#x27;: &#x27;text/plain&#x27; });</span></span><br><span class="line">        <span class="comment">//     cRes.end(&#x27;Send Timeout&#x27;);</span></span><br><span class="line">        <span class="comment">// });</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// &lt;http.ClientRequest&gt;</span></span><br><span class="line">        <span class="keyword">const</span> pReq = http.<span class="title function_">request</span>({ hostname, port, path, method, headers });</span><br><span class="line">        pReq</span><br><span class="line">            .<span class="built_in">setTimeout</span>(<span class="variable constant_">PROXY_TIMEOUT</span>)</span><br><span class="line">            .<span class="title function_">on</span>(<span class="string">&#x27;response&#x27;</span>, <span class="function">(<span class="params">pRes</span>) =&gt;</span> {</span><br><span class="line">                <span class="comment">// &lt;http.IncomingMessage&gt;</span></span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[response] [proxy] <span class="subst">${cUrl}</span>`</span>);</span><br><span class="line"></span><br><span class="line">                cRes.<span class="title function_">writeHead</span>(pRes.<span class="property">statusCode</span>, pRes.<span class="property">headers</span>);</span><br><span class="line">                pRes.<span class="title function_">pipe</span>(cRes);</span><br><span class="line">            })</span><br><span class="line">            .<span class="title function_">on</span>(<span class="string">&#x27;timeout&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">                <span class="comment">// 代理请求超时</span></span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[timeout] [proxy-req] <span class="subst">${cUrl}</span>`</span>);</span><br><span class="line">                pReq.<span class="title function_">abort</span>();</span><br><span class="line">            })</span><br><span class="line">            .<span class="title function_">on</span>(<span class="string">&#x27;abort&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[abort] [proxy-req] <span class="subst">${cUrl}</span>`</span>);</span><br><span class="line">            })</span><br><span class="line">            .<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[close] [proxy-req] <span class="subst">${cUrl}</span>`</span>);</span><br><span class="line">            })</span><br><span class="line">            .<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> {</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[error] [proxy-req] <span class="subst">${cUrl}</span> \r\n<span class="subst">${err.stack}</span>`</span>);</span><br><span class="line">                cRes.<span class="title function_">writeHead</span>(<span class="number">500</span>, { <span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span> });</span><br><span class="line">                cRes.<span class="title function_">end</span>(<span class="string">`Proxy Error: <span class="subst">${err.code || err.message || err.name}</span>`</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">        cReq.<span class="title function_">pipe</span>(pReq);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 客户端中止</span></span><br><span class="line">        cReq.<span class="title function_">on</span>(<span class="string">&#x27;aborted&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[aborted] [client] <span class="subst">${cUrl}</span>`</span>);</span><br><span class="line">            <span class="comment">// 中止代理</span></span><br><span class="line">            pReq.<span class="title function_">abort</span>();</span><br><span class="line">        });</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// http-server</span></span><br><span class="line">        <span class="keyword">if</span> (cUrl === <span class="string">&#x27;/proxy.pac&#x27;</span>) {</span><br><span class="line">            cRes.<span class="title function_">setHeader</span>(<span class="string">&#x27;content-type&#x27;</span>, <span class="string">&#x27;application/x-ns-proxy-autoconfig&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> pac = <span class="string">`// Proxy Auto-Configuration (PAC) file</span></span><br><span class="line"><span class="string">// https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_(PAC)_file</span></span><br><span class="line"><span class="string">// DIRECT        不经过任何代理，直接进行连接</span></span><br><span class="line"><span class="string">// PROXY host:port应该使用指定的代理</span></span><br><span class="line"><span class="string">// SOCKS host:port应该使用指定的 SOCKS 服务器</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">var proxy = &quot;PROXY proxy.kekek.cc:1337; DIRECT;&quot;;</span></span><br><span class="line"><span class="string">var direct = &#x27;DIRECT;&#x27;;</span></span><br><span class="line"><span class="string">function FindProxyForURL(url, host){</span></span><br><span class="line"><span class="string">    if (/\.\kekek\.cc$/.test(host)) {</span></span><br><span class="line"><span class="string">        return proxy;</span></span><br><span class="line"><span class="string">    } else {</span></span><br><span class="line"><span class="string">        return direct;</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><br><span class="line">            cRes.<span class="title function_">end</span>(pac);</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            cRes.<span class="title function_">setHeader</span>(<span class="string">&#x27;content-type&#x27;</span>, <span class="string">&#x27;text/plain&#x27;</span>);</span><br><span class="line">            cRes.<span class="title function_">end</span>(<span class="string">&#x27;proxy: proxy.kekek.cc\r\nport: 1337\r\npac: http://proxy.kekek.cc:1337/proxy.pac&#x27;</span>);</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 class="comment">// https</span></span><br><span class="line">proxy.<span class="title function_">on</span>(<span class="string">&#x27;connect&#x27;</span>, <span class="function">(<span class="params">req, cltSocket, head</span>) =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(req.<span class="property">url</span>, head.<span class="property">length</span>)</span><br><span class="line">    <span class="comment">// 连接到一个服务器</span></span><br><span class="line">    <span class="keyword">const</span> { port, hostname } = url.<span class="title function_">parse</span>(<span class="string">`http://<span class="subst">${req.url}</span>`</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> srvSocket = net.<span class="title function_">connect</span>(port, hostname);</span><br><span class="line"></span><br><span class="line">    srvSocket.<span class="title function_">on</span>(<span class="string">&#x27;connect&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">        cltSocket.<span class="title function_">write</span>(<span class="string">&#x27;HTTP/1.1 200 Connection Established\r\n&#x27;</span> +</span><br><span class="line">            <span class="string">&#x27;Proxy-agent: Node.js-Proxy\r\n&#x27;</span> +</span><br><span class="line">            <span class="string">&#x27;\r\n&#x27;</span>);</span><br><span class="line">        srvSocket.<span class="title function_">write</span>(head);</span><br><span class="line">        <span class="comment">// 连接管道</span></span><br><span class="line">        srvSocket.<span class="title function_">pipe</span>(cltSocket);</span><br><span class="line">        cltSocket.<span class="title function_">pipe</span>(srvSocket);</span><br><span class="line">    });</span><br><span class="line"></span><br><span class="line">    <span class="comment">// srvSocket.on(&#x27;end&#x27;, () =&gt; {</span></span><br><span class="line">    <span class="comment">//     // 远程服务器断开</span></span><br><span class="line">    <span class="comment">//     console.log(`[end] ${req.url}`);</span></span><br><span class="line">    <span class="comment">//     cltSocket.end();</span></span><br><span class="line">    <span class="comment">// });</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// srvSocket.on(&#x27;timeout&#x27;, () =&gt; {</span></span><br><span class="line">    <span class="comment">//     console.log(`[timeout] ${req.url}`);</span></span><br><span class="line">    <span class="comment">//     srvSocket.end();</span></span><br><span class="line">    <span class="comment">//     cltSocket.end();</span></span><br><span class="line">    <span class="comment">// });</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// srvSocket.on(&#x27;close&#x27;, (hasError) =&gt; {</span></span><br><span class="line">    <span class="comment">//     // 远程服务器完全断开</span></span><br><span class="line">    <span class="comment">//     console.log(`[close] ${req.url} ${hasError}`);</span></span><br><span class="line">    <span class="comment">// });</span></span><br><span class="line"></span><br><span class="line">    srvSocket.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> {</span><br><span class="line">        <span class="comment">// 远程服务器报错</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[error] <span class="subst">${req.url}</span> \r\n<span class="subst">${err.stack}</span>`</span>);</span><br><span class="line">        <span class="keyword">const</span> error = <span class="string">`Proxy Error: <span class="subst">${err.code || err.message || err.name}</span>`</span></span><br><span class="line">        cltSocket.<span class="title function_">end</span>(<span class="string">`HTTP/1.1 500 Internal Server Error\r\ncontent-type:text/plain\r\ncontent-length:<span class="subst">${err.length}</span>\r\n\r\n<span class="subst">${error}</span>`</span>);</span><br><span class="line">    });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">proxy.<span class="title function_">on</span>(<span class="string">&#x27;listening&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Proxy-Server running on http://<span class="subst">${HOST}</span>:<span class="subst">${PORT}</span>`</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">proxy.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="variable constant_">HOST</span>);</span><br></pre></td></tr></table></figure><h3 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h3><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://book.douban.com/subject/10746113/">HTTP权威指南-第六章 代理</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_(PAC)_file">Proxy Auto-Configuration (PAC) file</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;HTTP代理服务器&quot;&gt;&lt;a href=&quot;#HTTP代理服务器&quot; class=&quot;headerlink&quot; title=&quot;HTTP代理服务器&quot;&gt;&lt;/a&gt;HTTP代理服务器&lt;/h2&gt;&lt;p&gt;科学上网和调试网站时经常会用到代理服务器，那么代理服务器是怎么实现的呢？代理服务器和</summary>
      
    
    
    
    
    <category term="http" scheme="https://kekek.cc/tags/http/"/>
    
    <category term="proxy" scheme="https://kekek.cc/tags/proxy/"/>
    
  </entry>
  
  <entry>
    <title>Echo服务</title>
    <link href="https://kekek.cc/post/echo-server.html"/>
    <id>https://kekek.cc/post/echo-server.html</id>
    <published>2018-08-17T07:58:34.000Z</published>
    <updated>2021-11-20T00:17:47.674Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Echo服务"><a href="#Echo服务" class="headerlink" title="Echo服务"></a>Echo服务</h2><p>有些情况下调试网络客户端程序时会需要一个服务器配合测试，这时Echo服务就比较适合。它能充当一个Socket服务器，并且会把我们传过去的值返回来。</p><p>我们以<code>\n</code>或<code>\r\n</code>作为网络包的分隔符。</p><figure class="highlight javascript"><figcaption><span>echo.js</span><a href="/downloads/code/autogeneration/echo.js">view raw</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = <span class="number">7000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">HOST</span> = <span class="string">&#x27;0.0.0.0&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">LF</span> = <span class="string">&#x27;\n&#x27;</span>.<span class="title function_">charCodeAt</span>(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CR</span> = <span class="string">&#x27;\r&#x27;</span>.<span class="title function_">charCodeAt</span>(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">MAX_LENGTH</span> = <span class="number">128</span>; </span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">STRIP_DELIMITER</span> = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">findEndOfLine</span>(<span class="params">buffer</span>) {</span><br><span class="line">    <span class="keyword">let</span> i = buffer.<span class="title function_">indexOf</span>(<span class="variable constant_">LF</span>);</span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; buffer[i - <span class="number">1</span>] === <span class="variable constant_">CR</span>) {</span><br><span class="line">        i--;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fail</span>(<span class="params">socket, length</span>) {</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">writable</span>) {</span><br><span class="line">        socket.<span class="title function_">end</span>(<span class="string">`frame length <span class="subst">${length}</span> exceeds the allowed maximum <span class="subst">${MAX_LENGTH}</span>`</span>);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        socket.<span class="title function_">end</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = net.<span class="title function_">createServer</span>();</span><br><span class="line"><span class="comment">// 最大连接数</span></span><br><span class="line">server.<span class="property">maxConnections</span> = <span class="number">10240</span>;</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="function">(<span class="params">socket</span>) =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`新的客户端加入：<span class="subst">${socket.remoteAddress}</span>:<span class="subst">${socket.remotePort}</span>`</span>);</span><br><span class="line">    <span class="comment">// 设置编码 否则为Buffer</span></span><br><span class="line">    <span class="comment">// socket.setEncoding(&#x27;utf8&#x27;);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启用长连接，每100毫秒检测一次是否断开</span></span><br><span class="line">    <span class="comment">// socket.setKeepAlive(true, 100);</span></span><br><span class="line"></span><br><span class="line">    socket.<span class="built_in">setTimeout</span>(<span class="number">5000</span>); <span class="comment">// 5s</span></span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;timeout&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="comment">// timeout并不会断开连接，需要手动断开</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;超时断开&#x27;</span>);</span><br><span class="line">        socket.<span class="title function_">end</span>();</span><br><span class="line">    });</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 接收的字节数量（累计）</span></span><br><span class="line">    <span class="comment">// socket.bytesRead</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> readBuffer = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(<span class="number">0</span>);</span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> {</span><br><span class="line">        readBuffer = <span class="title class_">Buffer</span>.<span class="title function_">concat</span>([readBuffer, data]);</span><br><span class="line">        <span class="keyword">const</span> eol = <span class="title function_">findEndOfLine</span>(readBuffer);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (eol &gt;= <span class="number">0</span>) {</span><br><span class="line">            <span class="keyword">let</span> frame;</span><br><span class="line">            <span class="keyword">const</span> delimLength = readBuffer[eol] === <span class="variable constant_">CR</span> ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">const</span> length = eol + <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">if</span> (length &gt; <span class="variable constant_">MAX_LENGTH</span>) {</span><br><span class="line">                <span class="title function_">fail</span>(socket, length);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (<span class="variable constant_">STRIP_DELIMITER</span>) {</span><br><span class="line">                frame = readBuffer.<span class="title function_">slice</span>(<span class="number">0</span>, eol);</span><br><span class="line">            } <span class="keyword">else</span> {</span><br><span class="line">                frame = readBuffer.<span class="title function_">slice</span>(<span class="number">0</span>, eol + delimLength);</span><br><span class="line">            }</span><br><span class="line">            readBuffer = readBuffer.<span class="title function_">slice</span>(eol + delimLength + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (socket.<span class="property">writable</span>) socket.<span class="title function_">write</span>(frame);</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="keyword">const</span> length = readBuffer.<span class="property">length</span>;</span><br><span class="line">            <span class="keyword">if</span> (length &gt; <span class="variable constant_">MAX_LENGTH</span>) {</span><br><span class="line">                <span class="title function_">fail</span>(socket, length);</span><br><span class="line">                <span class="keyword">return</span>;</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">    socket.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> {</span><br><span class="line">        <span class="comment">// 当错误发生时触发。&#x27;close&#x27; 事件也会紧接着该事件被触发。</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`客户端错误：<span class="subst">${err.name}</span>\n<span class="subst">${err.stack}</span>`</span>);</span><br><span class="line">    });</span><br><span class="line"></span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`客户端断开：<span class="subst">${socket.remoteAddress}</span>:<span class="subst">${socket.remotePort}</span>`</span>);</span><br><span class="line">    });</span><br><span class="line"></span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="comment">// 客户端断开FIN</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;FIN&#x27;</span>);</span><br><span class="line">    });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`服务器错误：<span class="subst">${err.name}</span>\n<span class="subst">${err.stack}</span>`</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;服务停止&#x27;</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;listening&#x27;</span>, <span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Server listen on <span class="subst">${HOST}</span>:<span class="subst">${PORT}</span>`</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="variable constant_">HOST</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">    server.<span class="title function_">getConnections</span>(<span class="function">(<span class="params">err, connections</span>) =&gt;</span> {</span><br><span class="line">        <span class="keyword">if</span> (!err) {</span><br><span class="line">            <span class="keyword">const</span> now = (<span class="keyword">new</span> <span class="title class_">Date</span>()).<span class="title function_">toUTCString</span>();</span><br><span class="line">            <span class="keyword">const</span> memoryUsage = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(process.<span class="title function_">memoryUsage</span>());</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[<span class="subst">${now}</span>] 连接数为：<span class="subst">${connections}</span>，最大连接数为：<span class="subst">${server.maxConnections}</span>，内存使用：<span class="subst">${memoryUsage}</span>`</span>);</span><br><span class="line">        }</span><br><span class="line">    });</span><br><span class="line">}, <span class="number">2000</span>);</span><br><span class="line"></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;uncaughtException&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`uncaughtException：<span class="subst">${err.name}</span>\n<span class="subst">${err.stack}</span>`</span>);</span><br><span class="line">    process.<span class="title function_">exit</span>(<span class="number">1</span>);</span><br><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><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ulimit -n 10240</span><br><span class="line">nohup node echo.js &gt;/var/log/echo.log 2&gt;&amp;1 &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></pre></td><td class="code"><pre><span class="line">telnet echo.kekek.cc 7000</span><br></pre></td></tr></table></figure><p><a href="https://github.com/tianyk/echo.git">Github</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Echo服务&quot;&gt;&lt;a href=&quot;#Echo服务&quot; class=&quot;headerlink&quot; title=&quot;Echo服务&quot;&gt;&lt;/a&gt;Echo服务&lt;/h2&gt;&lt;p&gt;有些情况下调试网络客户端程序时会需要一个服务器配合测试，这时Echo服务就比较适合。它能充当一个Socket</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>ES6箭头函数中的this</title>
    <link href="https://kekek.cc/post/arrow-this.html"/>
    <id>https://kekek.cc/post/arrow-this.html</id>
    <published>2018-08-02T06:38:53.000Z</published>
    <updated>2021-11-20T00:17:47.666Z</updated>
    
    <content type="html"><![CDATA[<h2 id="ES6箭头函数中的this"><a href="#ES6箭头函数中的this" class="headerlink" title="ES6箭头函数中的this"></a>ES6箭头函数中的this</h2><p>ES6 开始引入了箭头函数，让代码看起来更简洁了。但是箭头函数传统函数不仅仅是写法上的不同，在<code>this</code>和<code>arguments</code>的处理方式也有着很大的区别。</p><h3 id="this是局部的"><a href="#this是局部的" class="headerlink" title="this是局部的"></a>this是局部的</h3><p>在箭头函数中<code>this</code>是<strong>局部的</strong>，它跟其它局部变量的常规处理是一致的。看下面的例子：</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">outer</span> (<span class="params">age</span>) &#123;</span><br><span class="line">    (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>, age);</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">outer.<span class="title function_">bind</span>(&#123;<span class="attr">name</span>: <span class="string">&#x27;小明&#x27;</span>&#125;)(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// =&gt; 小明 10</span></span><br></pre></td></tr></table></figure><p>这例子中我们函数共有三层嵌套，里面是两层箭头函数，这里我们在最里层箭头函数内部引用了<code>this</code>和一个局部变量<code>age</code>。对局局部变量<code>age</code>的查找规则我们很清楚，先在当前作用域查找，如果没有就去父作用域查找，一直到最顶层（window、global）。在箭头函数中对<code>this</code>的查找规则和<code>age</code>是一样的，箭头函数本身并没有一个自己的<code>this</code>，它要一直往上层找，直到找到<code>this</code>为止。</p><p>同样，对于<code>arguments</code>的处理也是一样的。需要注意的一点是箭头函数本身是没有<code>arguments</code>变量的。</p><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><p>箭头函数虽然写起来更加简单，但是我们不能不加区分的在任何地方都用箭头函数替代传统函数，特别是在函数内部涉及<code>this</code>绑定时。看下面的例子：</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sayName</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">    fn.<span class="title function_">bind</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;小明&#x27;</span> &#125;)();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">sayName</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;My name is&#x27;</span>, <span class="variable language_">this</span>.<span class="property">name</span>); &#125;);</span><br><span class="line"><span class="comment">// =&gt; My name is 小明</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误写法</span></span><br><span class="line"><span class="title function_">sayName</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;My name is&#x27;</span>, <span class="variable language_">this</span>.<span class="property">name</span>));</span><br><span class="line"><span class="comment">// =&gt; My name is </span></span><br></pre></td></tr></table></figure><p>也正是这个原因<a href="https://github.com/koajs/koa/tree/v1.x">Koa 1.x</a>中我们可以中间件中使用<code>this</code>引用到<code>context</code>，而在<a href="https://github.com/koajs/koa/tree/2.0.0">Koa 2.x</a>中需要将<code>context</code>作为参数传到中间件内部。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// koa1.x</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="keyword">function</span> *(next) &#123;</span><br><span class="line">    <span class="keyword">var</span> start = <span class="keyword">new</span> <span class="title class_">Date</span>;</span><br><span class="line">    <span class="keyword">yield</span> next;</span><br><span class="line">    <span class="keyword">var</span> ms = <span class="keyword">new</span> <span class="title class_">Date</span> - start;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;%s %s - %s&#x27;</span>, <span class="variable language_">this</span>.<span class="property">method</span>, <span class="variable language_">this</span>.<span class="property">url</span>, ms);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// koa 2.x</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> start = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="keyword">const</span> ms = <span class="title class_">Date</span>.<span class="title function_">now</span>() - start;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;ctx.method&#125;</span> <span class="subst">$&#123;ctx.url&#125;</span> - <span class="subst">$&#123;ms&#125;</span>ms`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://www.cnblogs.com/vajoy/p/4902935.html">ES6 箭头函数中的 this？你可能想多了</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;ES6箭头函数中的this&quot;&gt;&lt;a href=&quot;#ES6箭头函数中的this&quot; class=&quot;headerlink&quot; title=&quot;ES6箭头函数中的this&quot;&gt;&lt;/a&gt;ES6箭头函数中的this&lt;/h2&gt;&lt;p&gt;ES6 开始引入了箭头函数，让代码看起来更简洁了。但</summary>
      
    
    
    
    
    <category term="es6" scheme="https://kekek.cc/tags/es6/"/>
    
    <category term="arrow-function" scheme="https://kekek.cc/tags/arrow-function/"/>
    
  </entry>
  
  <entry>
    <title>Nginx日志配置与切割</title>
    <link href="https://kekek.cc/post/nginx-log.html"/>
    <id>https://kekek.cc/post/nginx-log.html</id>
    <published>2018-07-07T02:37:30.000Z</published>
    <updated>2020-08-07T03:52:24.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Nginx日志配置与切割"><a href="#Nginx日志配置与切割" class="headerlink" title="Nginx日志配置与切割"></a>Nginx日志配置与切割</h2><p>Nginx 日志配置非常简单，首先使用<code>log_format</code>指令配置一个格式。然后使用 <code>access_log</code>（访问日志）、<code>error_log</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><span class="line">9</span><br><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="section">http</span> &#123;</span><br><span class="line">    <span class="comment"># 日志格式 第二个参数是格式名字，可以配置多个format（这里的日志格式名为 `main`）。后面 access_log 指令使用哪个格式就写这个格式的名字。</span></span><br><span class="line">    <span class="attribute">log_format</span> main <span class="string">&#x27;<span class="variable">$remote_addr</span> - <span class="variable">$remote_user</span> [<span class="variable">$time_local</span>] &#x27;</span></span><br><span class="line">                       <span class="string">&#x27;&quot;<span class="variable">$request</span>&quot; <span class="variable">$status</span> <span class="variable">$bytes_sent</span> &#x27;</span></span><br><span class="line">                       <span class="string">&#x27;&quot;<span class="variable">$http_referer</span>&quot; &quot;<span class="variable">$http_user_agent</span>&quot;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">access_log</span> /var/log/nginx/access.log main;</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">server</span> &#123;</span><br><span class="line">    <span class="attribute">access_log</span> /var/log/nginx/<span class="variable">$host</span>/access.log main;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="条件日志"><a href="#条件日志" class="headerlink" title="条件日志"></a>条件日志</h3><p>我们有一个心跳检测的服务，定期会向服务器发请求。这个请求每天会发很多次，这个请求的日志也是非常多的。这个日志没什么意义，可以把它忽略掉。</p><p>第一种方法就是配置一个location匹配到这个心跳检测接口将日志关掉。</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></pre></td><td class="code"><pre><span class="line"><span class="section">location</span> = /_heartbeat &#123;</span><br><span class="line">    <span class="attribute">access_log</span> <span class="literal">off</span>;</span><br><span class="line">    <span class="attribute">proxy_pass</span> http://myserver;</span><br><span class="line">&#125;</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></pre></td><td class="code"><pre><span class="line"><span class="section">http</span> &#123;</span><br><span class="line">    <span class="attribute">map</span> <span class="variable">$uri</span> <span class="variable">$loggable</span> &#123;</span><br><span class="line">        <span class="comment"># 可以使用正则</span></span><br><span class="line">        ~\.(jpg|png|gif|webp)$   0; <span class="comment"># 不记录图片</span></span><br><span class="line">        /<span class="attribute">_heartbeat</span>              <span class="number">0</span>; <span class="comment"># 不记录心跳检测</span></span><br><span class="line">        <span class="attribute">default</span>                  <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">access_log</span> /var/log/nginx/access.log main if=<span class="variable">$loggable</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="日志切割"><a href="#日志切割" class="headerlink" title="日志切割"></a>日志切割</h3><p>当 Nginx 接收到 <code>USR1</code> 时会重新打开日志文件。</p><figure class="highlight shell"><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">mv /var/log/nginx/access.log /var/log/nginx/access.log-20180706</span><br><span class="line">kill -USR1 `cat /var/run/nginx.pid`</span><br></pre></td></tr></table></figure><p>上面方案很不灵活，需要手动处理。我们可以使用crontab或者<a href="https://github.com/logrotate/logrotate">logrotate</a>来做到自动化。</p><ol><li><p>crontab </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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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 prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">0 0 * * * 每天零点执行</span></span><br><span class="line"></span><br><span class="line">LOG_PATH=/var/log/nginx</span><br><span class="line">DATE=`date -d &quot;yesterday&quot; +%Y%m%d`</span><br><span class="line">PID=`cat /var/run/nginx.pid`</span><br><span class="line"></span><br><span class="line">cd $LOG_PATH</span><br><span class="line"></span><br><span class="line">mv access.log access.log-$DATE &amp;&amp; zip -mq access.log-$DATE.zip access.log-$DATE</span><br><span class="line">kill -USR1 $PID</span><br></pre></td></tr></table></figure></li><li><p>logrotate</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">/var/log/nginx/*.log &#123;</span><br><span class="line">    daily</span><br><span class="line">    dateext</span><br><span class="line">    rotate 30</span><br><span class="line">    compress</span><br><span class="line">    delaycompress</span><br><span class="line">    notifempty</span><br><span class="line">    sharedscripts</span><br><span class="line">    postrotate</span><br><span class="line">        if [ -f /var/run/nginx.pid ]; then</span><br><span class="line">            kill -USR1 `cat /var/run/nginx.pid`</span><br><span class="line">        fi</span><br><span class="line">    endscript</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><ul><li>第一行要处理的日志，可以使用通配符。后面<code>{}</code>包裹日志切割的配置项。</li><li>daily：表示每天执行一次，类似的配置还有weekly,monthly每周和每月。</li><li>dateext：文件后缀使用日期命名。</li><li>rotate 30：保留最近30天的日志。</li><li>compress：对日志进行压缩。</li><li>delaycompress：延迟压缩，分割完成后延迟一天压缩此日志。</li><li>notifempty：忽略空日志。</li><li>sharedscripts：执行脚本。</li><li>postrotate：日志被切割后执行的脚本，对应的<code>prerotate</code>表示在切割前执行的脚本。</li><li>endscript：标记脚本结束。</li><li>测试一下脚本：<code>logrotate -d /etc/logrotate.d/nginx</code>。</li><li>执行后面的命令能立即看到效果：<code>logrotate -vf /etc/logrotate.d/nginx</code>。</li></ul></blockquote></li></ol><h3 id="输出JSON格式的日志"><a href="#输出JSON格式的日志" class="headerlink" title="输出JSON格式的日志"></a>输出JSON格式的日志</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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">log_format json escape=json &#x27;&#123;&#x27;</span><br><span class="line">    &#x27;&quot;remote_addr&quot;: &quot;$remote_addr&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;x_forwarded_for&quot;:  &quot;$http_x_forwarded_for&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;time_iso8601&quot;: &quot;$time_iso8601&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;host&quot;: &quot;$host&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;method&quot;: &quot;$request_method&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;request_uri&quot;: &quot;$request_uri&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;status&quot;: $status,&#x27;</span><br><span class="line">    &#x27;&quot;http_referer&quot;:  &quot;$http_referer&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;body_bytes_sent&quot;: &quot;$body_bytes_sent&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;http_user_agent&quot;: &quot;$http_user_agent&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;request_time&quot;: &quot;$request_time&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;upstream_addr&quot;: &quot;$upstream_addr&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;upstream_response_time&quot;: &quot;$upstream_response_time&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;x_request_id&quot;: &quot;$upstream_http_x_request_id&quot;,&#x27;</span><br><span class="line">    &#x27;&quot;x_response_time&quot;: &quot;$upstream_http_x_response_time&quot;&#x27;</span><br><span class="line">&#x27;&#125;&#x27;;</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="http://nginx.org/en/docs/http/ngx_http_log_module.html">Module ngx_http_log_module</a></li><li><a href="https://linux.cn/article-8227-1.html">配置 logrotate 的终极指导</a></li><li><a href="http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format">log_format</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Nginx日志配置与切割&quot;&gt;&lt;a href=&quot;#Nginx日志配置与切割&quot; class=&quot;headerlink&quot; title=&quot;Nginx日志配置与切割&quot;&gt;&lt;/a&gt;Nginx日志配置与切割&lt;/h2&gt;&lt;p&gt;Nginx 日志配置非常简单，首先使用&lt;code&gt;log_f</summary>
      
    
    
    
    <category term="nginx" scheme="https://kekek.cc/categories/nginx/"/>
    
    
    <category term="nginx" scheme="https://kekek.cc/tags/nginx/"/>
    
    <category term="log" scheme="https://kekek.cc/tags/log/"/>
    
    <category term="logrotate" scheme="https://kekek.cc/tags/logrotate/"/>
    
  </entry>
  
  <entry>
    <title>部署全球CDN网络</title>
    <link href="https://kekek.cc/post/global-cdn.html"/>
    <id>https://kekek.cc/post/global-cdn.html</id>
    <published>2018-07-04T14:49:21.000Z</published>
    <updated>2021-11-20T00:17:47.677Z</updated>
    
    <content type="html"><![CDATA[<h2 id="部署全球CDN网络"><a href="#部署全球CDN网络" class="headerlink" title="部署全球CDN网络"></a>部署全球CDN网络</h2><p>我们现在除了大陆的还有美国及东南亚的用户，在国内可以使用<a href="https://www.aliyun.com/product/cdn">阿里</a>或者<a href="https://cloud.tencent.com/product/cdn">腾讯</a>的CDN服务。但是上面两家在国外的节点不是很多，而国外厂商有<a href="https://aws.amazon.com/cloudfront/">Amazon CloudFront</a>和<a href="https://www.cloudflare.com/">Cloudflare</a>在大陆地区的服务又不好。那有没有办法让一个域名大陆用户使用阿里的服务，国外的用户是用CloudFront服务呢？</p><p>答案是有的，我们知道现在主流的CDN都是采用CNAME的形式配置DNS。CNAME的核心就是智能DNS，那我们如果搭建一个DNS让它给国内外用户返回不同的CNAME不就解决了吗，也就是CDN的DNS。所幸，不用我们麻烦自己去搭建这个智能DNS，现在像阿里的云DNS和DNSPod都提供了比我们需求更强大的功能。下面是具体网络拓扑图：</p><p><img src="/images/global-dns.png" alt="暂缺"></p><h3 id="具体操作"><a href="#具体操作" class="headerlink" title="具体操作"></a>具体操作</h3><blockquote><p>由于暂时没有Amazon CloudFront和Cloudflare账号，国外CDN我就用腾讯CDN模拟了。实际操作时将腾讯CDN换成Amazon CloudFront或Cloudflare即可。</p></blockquote><ol><li><p>首先分别在阿里CDN和腾讯CDN开启服务</p><p> 阿里CDN：s.kekek.cc.w.kunlunar.com.<br> <img src="/images/cdn-cn-kekek.png" alt="阿里CDN"></p><p> 腾讯CDN：s.kekek.cc.cdn.dnsv1.com.<br> <img src="/images/cdn-world-kekek.png" alt="腾讯CDN"></p></li><li><p>为域名配置智能DNS</p><p> 境外服务配置为腾讯DNS的CNAME，默认线路（境内）配置为阿里CDN的CNANME。<br> <img src="/images/dns-kekek.png"></p></li></ol><p>下面我们测试一下线路：</p><ol><li>114dns</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></pre></td><td class="code"><pre><span class="line">$ dig @114.114.114.114 s.kekek.cc CNAME</span><br><span class="line"></span><br><span class="line">; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; @114.114.114.114 s.kekek.cc CNAME</span><br><span class="line">; (1 server found)</span><br><span class="line">;; global options: +cmd</span><br><span class="line">;; Got answer:</span><br><span class="line">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 41126</span><br><span class="line">;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0</span><br><span class="line"></span><br><span class="line">;; QUESTION SECTION:</span><br><span class="line">;s.kekek.cc.INCNAME</span><br><span class="line"></span><br><span class="line">;; ANSWER SECTION:</span><br><span class="line">s.kekek.cc.600INCNAMEs.kekek.cc.w.kunlunar.com.</span><br><span class="line"></span><br><span class="line">;; Query time: 2735 msec</span><br><span class="line">;; SERVER: 114.114.114.114#53(114.114.114.114)</span><br><span class="line">;; WHEN: Wed Jul 04 23:28:47 CST 2018</span><br><span class="line">;; MSG SIZE  rcvd: 67</span><br></pre></td></tr></table></figure><ol start="2"><li><p>Cloudflare DNS</p><blockquote><p>使用 1.1.1.1 DNS相当于模拟用户在境外进行DNS查询。</p></blockquote> <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">$ dig @1.1.1.1 s.kekek.cc CNAME</span><br><span class="line"></span><br><span class="line">; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; @1.1.1.1 s.kekek.cc CNAME</span><br><span class="line">; (1 server found)</span><br><span class="line">;; global options: +cmd</span><br><span class="line">;; Got answer:</span><br><span class="line">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 5551</span><br><span class="line">;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1</span><br><span class="line"></span><br><span class="line">;; OPT PSEUDOSECTION:</span><br><span class="line">; EDNS: version: 0, flags:; udp: 1452</span><br><span class="line">;; QUESTION SECTION:</span><br><span class="line">;s.kekek.cc.INCNAME</span><br><span class="line"></span><br><span class="line">;; ANSWER SECTION:</span><br><span class="line">s.kekek.cc.345INCNAMEs.kekek.cc.cdn.dnsv1.com.</span><br><span class="line"></span><br><span class="line">;; Query time: 320 msec</span><br><span class="line">;; SERVER: 1.1.1.1#53(1.1.1.1)</span><br><span class="line">;; WHEN: Wed Jul 04 23:27:39 CST 2018</span><br><span class="line">;; MSG SIZE  rcvd: 77</span><br></pre></td></tr></table></figure></li></ol><p>可以看到我们我们使用114DNS时，返回的CNAME为<code>s.kekek.cc.w.kunlunar.com.</code>，对应的就是我们配置的阿里CDN。使用Cloudflare DNS是模拟海外用户得到的CNAME是<code>s.kekek.cc.cdn.dnsv1.com.</code>，这个刚好是我们腾讯的CDN。</p><p>现在我们境内用户访问<a href="http://ss.kekek.cc/">s.kekek.cc</a>时就可以使用阿里的CDN提供加速了，境外用户访问时就可以由腾讯CDN提供加速服务了。</p><h3 id="国内外源站问题"><a href="#国内外源站问题" class="headerlink" title="国内外源站问题"></a>国内外源站问题</h3><p>对于国内外是否是用同一个源站，两者都可。可以根据实际情况去测试哪种更符合自身业务，从洛杉矶到上海的最小延迟在<code>180ms</code>左右，也就是说境外用户访问首次加载的新资源时会出现比较严重的延迟。如果对首次加载延迟太在意可以使用一个源站，如果<strong>频繁</strong>回源可考虑境内境外使用不同的源站。</p><p>使用不同源站时可以采用主动文件同步、<a href="nginx-proxy-cache.html">Nginx代理缓存</a>等方案同步两个源站资源。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;部署全球CDN网络&quot;&gt;&lt;a href=&quot;#部署全球CDN网络&quot; class=&quot;headerlink&quot; title=&quot;部署全球CDN网络&quot;&gt;&lt;/a&gt;部署全球CDN网络&lt;/h2&gt;&lt;p&gt;我们现在除了大陆的还有美国及东南亚的用户，在国内可以使用&lt;a href=&quot;https</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>LRC播放器</title>
    <link href="https://kekek.cc/post/lrc-player.html"/>
    <id>https://kekek.cc/post/lrc-player.html</id>
    <published>2018-06-28T05:24:16.000Z</published>
    <updated>2021-11-20T00:17:47.701Z</updated>
    
    <content type="html"><![CDATA[<h2 id="LRC播放器"><a href="#LRC播放器" class="headerlink" title="LRC播放器"></a>LRC播放器</h2><iframe scrolling="no" width="100%" height="300" src="https://jsfiddle.net/ac35otdk/embedded/result,html,js,css/light/" frameborder="0" loading="lazy" allowfullscreen="allowfullscreen"></iframe><figure class="highlight js"><figcaption><span>lrc_player.js</span><a href="/downloads/code/lrc_player.js">view raw</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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">function</span> <span class="title function_">toSecond</span>(<span class="params">time</span>) {</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Date</span>(<span class="string">`1970-01-01T<span class="subst">${time}</span>Z`</span>).<span class="title function_">getTime</span>() / <span class="number">1000</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 播放歌词</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  {<span class="type"> </span></span></span><br><span class="line"><span class="type"><span class="comment"> *              lyrics, 歌词，格式为二维数组。e.g. [ [&#x27;00:00:00.40&#x27;, &#x27;周杰伦 - 等你下课 (with 杨瑞代)&#x27;], [&#x27;00:00:03.94&#x27;, &#x27;词：周杰伦&#x27;], [&#x27;00:00:05.21&#x27;, &#x27;曲：周杰伦&#x27;] ]</span></span></span><br><span class="line"><span class="type"><span class="comment"> *              seek  = &#x27;00:00:00&#x27;, 开始时间</span></span></span><br><span class="line"><span class="type"><span class="comment"> *              print = (lyric) =&gt; console.log(lyric), 歌词回调</span></span></span><br><span class="line"><span class="type"><span class="comment"> *              interval = 50 刷新检测间隔 ms</span></span></span><br><span class="line"><span class="type"><span class="comment"> *          </span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">play</span>(<span class="params">{ lyrics, seek = <span class="string">&#x27;00:00:00&#x27;</span>, print = (lyric) =&gt; <span class="variable language_">console</span>.log(lyric), interval = <span class="number">50</span> }</span>) {</span><br><span class="line">    seek = <span class="title function_">toSecond</span>(seek);</span><br><span class="line">    lyrics = lyrics.<span class="title function_">map</span>(<span class="function"><span class="params">lyric</span> =&gt;</span> [<span class="title function_">toSecond</span>(lyric[<span class="number">0</span>]), lyric[<span class="number">1</span>]]).<span class="title function_">sort</span>(<span class="function">(<span class="params">lyric1, lyric2</span>) =&gt;</span> lyric1[<span class="number">0</span>] - lyric2[<span class="number">0</span>]).<span class="title function_">filter</span>(<span class="function"><span class="params">lyric</span> =&gt;</span> lyric[<span class="number">0</span>] &gt;= seek);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// setInterval和setTimeout在浏览器窗口非激活的状态下会停止工作或者以极慢的速度工作</span></span><br><span class="line">    <span class="comment">// 可以使用 Web Worker 解决或者 requestAnimationFrame 解决[更新：经过测试窗口处于非激活状态下 requestAnimationFrame 也会停止工作]</span></span><br><span class="line">    <span class="comment">// [RAF replacements for setTimeout and setInterval](https://bl.ocks.org/joyrexus/7304146)</span></span><br><span class="line">    <span class="comment">// @see https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame</span></span><br><span class="line">    <span class="keyword">const</span> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="keyword">if</span> (lyrics.<span class="property">length</span> === <span class="number">0</span>) <span class="keyword">return</span> <span class="built_in">clearInterval</span>(timer);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// seek 之前的全部显示 </span></span><br><span class="line">        <span class="keyword">while</span> (lyrics.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; lyrics[<span class="number">0</span>][<span class="number">0</span>] &lt;= seek) <span class="title function_">print</span>(lyrics.<span class="title function_">shift</span>()[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">        seek += (interval / <span class="number">1000</span>);</span><br><span class="line">    }, interval);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> timer;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="TODO"><a href="#TODO" class="headerlink" title="TODO"></a>TODO</h3><ul><li><input checked="" disabled="" type="checkbox"> 使用RAF模拟setInterval</li><li><input checked="" disabled="" type="checkbox"> 封装为对象，支持play、pause、resume、seek、reset等功能</li></ul><figure class="highlight js"><figcaption><span>lrc_player_class.js</span><a href="/downloads/code/lrc_player_class.js">view raw</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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="keyword">class</span> <span class="title class_">LRCPlayer</span> {</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">{ lyrics, print = (lyric) =&gt; <span class="variable language_">console</span>.log(lyric), done = () =&gt; { }, interval = <span class="number">50</span> }</span>) {</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">lyrics</span> = lyrics;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">print</span> = print;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">done</span> = done;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">interval</span> = interval;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">seek</span> = <span class="number">0</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">_timer</span> = <span class="literal">null</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;INIT&#x27;</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 播放（支持seek）</span></span><br><span class="line">    <span class="title function_">play</span>(<span class="params">seek = <span class="string">&#x27;00:00:00&#x27;</span></span>) {</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> === <span class="string">&#x27;PLAY&#x27;</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> !== <span class="string">&#x27;PAUSE&#x27;</span>) {</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">seek</span> = <span class="title function_">toSecond</span>(seek);</span><br><span class="line">        }</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;PLAY&#x27;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">const</span> lyrics = <span class="variable language_">this</span>.<span class="property">lyrics</span>.<span class="title function_">map</span>(<span class="function"><span class="params">lyric</span> =&gt;</span> [<span class="title function_">toSecond</span>(lyric[<span class="number">0</span>]), lyric[<span class="number">1</span>]]).<span class="title function_">sort</span>(<span class="function">(<span class="params">lyric1, lyric2</span>) =&gt;</span> lyric1[<span class="number">0</span>] - lyric2[<span class="number">0</span>]).<span class="title function_">filter</span>(<span class="function"><span class="params">lyric</span> =&gt;</span> lyric[<span class="number">0</span>] &gt;= <span class="variable language_">this</span>.<span class="property">seek</span>);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">_timer</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">            <span class="keyword">if</span> (lyrics.<span class="property">length</span> === <span class="number">0</span>) {</span><br><span class="line">                <span class="built_in">clearInterval</span>(<span class="variable language_">this</span>.<span class="property">_timer</span>);</span><br><span class="line">                <span class="variable language_">this</span>.<span class="title function_">done</span>();</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            <span class="comment">// seek 之前的全部显示</span></span><br><span class="line">            <span class="keyword">while</span> (lyrics.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; lyrics[<span class="number">0</span>][<span class="number">0</span>] &lt;= <span class="variable language_">this</span>.<span class="property">seek</span>) <span class="variable language_">this</span>.<span class="title function_">print</span>(lyrics.<span class="title function_">shift</span>()[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">seek</span> += (<span class="variable language_">this</span>.<span class="property">interval</span> / <span class="number">1000</span>);</span><br><span class="line">        }, <span class="variable language_">this</span>.<span class="property">interval</span>);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 暂停</span></span><br><span class="line">    <span class="title function_">pause</span>(<span class="params"></span>) {</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> === <span class="string">&#x27;PAUSE&#x27;</span>) <span class="keyword">return</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;PAUSE&#x27;</span>;</span><br><span class="line">        <span class="built_in">clearInterval</span>(<span class="variable language_">this</span>.<span class="property">_timer</span>);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 继续播放</span></span><br><span class="line">    <span class="title function_">resume</span>(<span class="params"></span>) {</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> === <span class="string">&#x27;PAUSE&#x27;</span>) <span class="variable language_">this</span>.<span class="title function_">play</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重新开始</span></span><br><span class="line">    <span class="title function_">reset</span>(<span class="params"></span>) {</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">pause</span>();</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;INIT&#x27;</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">play</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://bl.ocks.org/joyrexus/7304146">RAF replacements for setTimeout and setInterval</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame">requestAnimationFrame</a></li><li><a href="https://dev.opera.com/articles/better-performance-with-requestanimationframe/">Better Performance With requestAnimationFrame</a></li><li><a href="https://stackoverflow.com/a/16033979/4942848">How do browsers pause&#x2F;change Javascript when tab or window is not active?</a></li><li><a href="https://css-tricks.com/using-requestanimationframe/">Using requestAnimationFrame</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;LRC播放器&quot;&gt;&lt;a href=&quot;#LRC播放器&quot; class=&quot;headerlink&quot; title=&quot;LRC播放器&quot;&gt;&lt;/a&gt;LRC播放器&lt;/h2&gt;&lt;iframe scrolling=&quot;no&quot; width=&quot;100%&quot; height=&quot;300&quot; src=&quot;htt</summary>
      
    
    
    
    
  </entry>
  
</feed>
