<?xml version="1.0" encoding="utf-8"?>
<search>
  <entry>
    <title>前后端分离下如何防止 CSRF 攻击</title>
    <url>/2020/10/anti-csrf-on-separation-of-frontend-and-backend/</url>
    <content><![CDATA[<h2 id="什么是-CSRF-攻击"><a href="#什么是-CSRF-攻击" class="headerlink" title="什么是 CSRF 攻击"></a>什么是 CSRF 攻击</h2><p>CSRF 的全名是 Cross Site Request Forgery，翻译成中文就是跨站点请求伪造  </p>
<p>CSRF 利用的是网站对用户网页浏览器的信任，简单地说，是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作（如发邮件，发消息，甚至财产操作如转账和购买商品）。<br>由于浏览器曾经认证过，所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞：<strong>简单的身份验证只能保证请求发自某个用户的浏览器，却不能保证请求本身是用户自愿发出的</strong></p>
<span id="more"></span>

<p>例子：</p>
<p>假如一家银行用以运行转账操作的 URL 地址如下：<code>https://bank.example.com/withdraw?account=AccoutName&amp;amount=1000&amp;for=PayeeName</code></p>
<p>那么，一个恶意攻击者可以在另一个网站上放置如下代码：<code>&lt;img src=&quot;https://bank.example.com/withdraw?account=Alice&amp;amount=1000&amp;for=Badman&quot; /&gt;</code></p>
<p>如果有账户名为 Alice 的用户访问了恶意站点，而她之前刚访问过银行不久，登录信息尚未过期，那么她就会损失 1000 资金</p>
<h2 id="常见的防御手段"><a href="#常见的防御手段" class="headerlink" title="常见的防御手段"></a>常见的防御手段</h2><h3 id="验证码"><a href="#验证码" class="headerlink" title="验证码"></a>验证码</h3><p><code>CSRF</code> 攻击往往是在用户不知情的情况下伪造了请求，而验证码强制用户必须进行交互</p>
<ul>
<li><code>措施</code>：给敏感操作添加验证码校验</li>
<li><code>局限</code>：影响用户体验</li>
</ul>
<h3 id="检查-Referer-字段"><a href="#检查-Referer-字段" class="headerlink" title="检查 Referer 字段"></a>检查 Referer 字段</h3><p>在处理敏感请求时，通常发出请求的网址应该与接受请求的网址属于同一域名</p>
<ul>
<li><code>措施</code>：所以我们可以通过校验 <code>HTTP headers</code> 中的 <code>Referer</code> 字段是否是属于本网站，如果不是则可能遭受 <code>CSRF</code> 攻击  </li>
<li><code>局限</code>：这种办法简单易行，工作量低，但是依赖于浏览器的发送正确的 <code>Referer</code> 字段，无法保证浏览器没有安全漏洞影响 <code>Referer</code> 的发送</li>
</ul>
<h3 id="不使用-GET-请求做敏感操作"><a href="#不使用-GET-请求做敏感操作" class="headerlink" title="不使用 GET 请求做敏感操作"></a>不使用 GET 请求做敏感操作</h3><p>在上面的例子中，我们可以知道，Alice 打开恶意站点后，浏览器通过 <code>img</code> 标签向银行转账网址发送了 <code>GET</code> 请求从而实现了 <code>CSRF</code> 攻击。</p>
<ul>
<li><code>措施</code>：所以，避免 <code>CSRF</code> 攻击最基础的一条是 <code>永远不使用 GET 请求做敏感操作</code></li>
<li><code>局限</code>：这种方法可以避免 <code>img</code>、<code>script</code>、<code>iframe</code> 等带 <code>src</code> 属性的标签发起 <code>CSRF</code> 攻击。但是无法防止攻击者构建 <code>POST</code> 请求等发起 <code>CSRF</code> 攻击</li>
</ul>
<h3 id="使用-Anti-CSRF-Token"><a href="#使用-Anti-CSRF-Token" class="headerlink" title="使用 Anti-CSRF-Token"></a>使用 Anti-CSRF-Token</h3><p><code>CSRF</code> 为什么能够攻击成功？其本质原因是<strong>重要操作的所有参数都是可以被攻击者猜测到的</strong>。攻击者只有预测出 URL 的所有参数与参数值，才能成功地构造一个伪造的请求；反之，攻击者将无法攻击成功。  </p>
<ul>
<li><p><code>措施</code>：所以我们可以生成一个<code>足够随机</code>的参数 <code>Token</code> 放入该次请求，保证该次请求无法被攻击者成功伪造。</p>
<p><strong>核心思路</strong>：使用一个由服务器派发的 <code>Token</code>，在前端进行状态修改时，也同时提交这个 <code>Token</code>（往往会放在 <code>html form</code> 的 <code>input</code> 中，或者 <code>ajax header</code> 中），这时候服务端验证该 <code>Token</code> 是否是之前所生成的，以此来判断这个请求是否被允许  </p>
</li>
<li><p><code>局限</code>：</p>
<ol>
<li>依赖于 <code>Token</code> 足够随机</li>
<li>无论是 <code>Token</code> 放在哪里，只要 <code>JS</code> 能读取到，都会面临 <code>XSS</code> 风险</li>
</ol>
</li>
</ul>
<p>在常见的 Web 后端框架比如 <code>Rails</code>、<code>Django</code> 中都自带实现了 <code>csrf_token</code> 功能，不同的是</p>
<ul>
<li>Rails 使用 <code>meta</code> 标签存放 <code>csrf_token</code>，通过 <code>HTTP headers</code> 发送</li>
<li>Django 使用隐藏的 <code>input</code> 标签存放 <code>csrf_token</code>，通过 <code>POST body</code> 发送</li>
</ul>
<h2 id="前后端分离带来的问题"><a href="#前后端分离带来的问题" class="headerlink" title="前后端分离带来的问题"></a>前后端分离带来的问题</h2><p>在现在大前端时代，前端后分离是个再常见不过的情况，我们该如何在前后端分离的情况下防止 <code>CSRF</code> 攻击呢？</p>
<p>首先，我们分析下上面所提到的几种手段在前后端分离下发生了什么变化：</p>
<ul>
<li>验证码：<ul>
<li><code>form</code> 表单完全由前端生成，后端是无法感知的，即使前端使用某种方式在 <code>form</code> 中放入了验证码，但是后端也无法验证</li>
</ul>
</li>
<li>检查 Referer 字段：无变化</li>
<li>不使用 GET 请求做敏感操作：无变化</li>
<li>使用 Anti-CSRF-Token：<ul>
<li><code>form</code> 表单完全由前端生成，后端是无法感知的，即使前端使用某种方式在 <code>form</code> 中放入了 <code>Token</code>，但是后端也无法验证</li>
</ul>
</li>
</ul>
<p>所以根本上是前后端分离之后，后端无法控制 <code>form</code> 生成时机，而前端则必须通过某种方式获取到由后端生成的 验证码 或者 <code>Token</code> 才能保证后端能够校验请求</p>
<ul>
<li><code>措施</code>：<ol>
<li>后端引入安全模块，可能是写在 <code>WAF</code> 里，也有可能是 <code>Security Sidecar</code> 或者自定义的 <code>API Gateway</code></li>
<li>前端请求安全模块，由安全模块生成 <code>csrf_token</code> 并返回，在提交时验证请求</li>
</ol>
</li>
<li><code>局限</code>：依然无法避免 <code>XSS</code> 攻击</li>
</ul>
<h2 id="XSRF"><a href="#XSRF" class="headerlink" title="XSRF"></a>XSRF</h2><p><code>CSRF</code> 的 <code>Token</code> 仅仅用于对抗 <code>CSRF</code> 攻击，当网站还同时存在 <code>XSS</code> 漏洞时，这个方案就会变得无效。<br>因为 <code>XSS</code> 可以模拟客户端浏览器执行任意操作，在 <code>XSS</code> 攻击下，攻击者完全可以请求页面后，读出页面内容里的 <code>Token</code> 值，然后再构造出一个合法的请求。这个过程就被称之为 <code>XSRF</code></p>
<p><code>XSS</code> 带来的问题，应该使用 <code>XSS</code> 的防御方案予以解决，否则 <code>CSRF</code> 的 <code>Token</code> 防御就是空中楼阁。安全防御的体系是相辅相成、缺一不可的</p>
<h2 id="迷思"><a href="#迷思" class="headerlink" title="迷思"></a>迷思</h2><ul>
<li><p>Q：以下攻击能成功吗？</p>
<ol>
<li>攻击者构建了一个恶意网站</li>
<li>恶意网站先通过请求上述所说的安全模块获取 <code>csrf_token</code></li>
<li>恶意网站构造隐藏的 <code>form</code> 表单并对参数进行伪造</li>
<li>利用 JS 自动提交（恶意网站是攻击者所有，所以绕过了 <code>XSS</code> 防御策略） <code>form</code> 并携带 <code>csrf_token</code> 给目标网站</li>
</ol>
<p>A：不会，因为浏览器的 <a href="https://zh.wikipedia.org/wiki/%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5">同源策略</a> 不允许恶意网站获取安全模块所返回的结果（<code>csrf_token</code>），所以攻击在第 2 步时就会失败</p>
</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://item.jd.com/11483966.html">白帽子讲Web安全</a></p>
<p><a href="https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0">跨站请求伪造</a></p>
<p><a href="https://blog.csdn.net/XiaoBeiTu/article/details/102268639">CSRF - 前后端分离后带来的新问题</a></p>
]]></content>
      <categories>
        <category>安全</category>
      </categories>
      <tags>
        <tag>think</tag>
        <tag>csrf</tag>
      </tags>
  </entry>
  <entry>
    <title>构建自己的 rss 平台</title>
    <url>/2023/04/build-own-rss-platform/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>因为需要在微博关注一些财经博主的更新，但又不想登录微博，遂研究了如何构建自己的 rss 平台，汇总感兴趣的信息。并使用 telegram 作为 rss 阅读器。</p>
<span id="more"></span>

<h3 id="Install"><a href="#Install" class="headerlink" title="Install"></a>Install</h3><ol>
<li><p>安装 <a href="https://docs.rsshub.app/install/#docker-compose-bu-shu">RSSHub</a> 作为 rss 平台</p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">mkdir rsshub
cd rsshub
wget https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;DIYgod&#x2F;RSSHub&#x2F;master&#x2F;docker-compose.yml
docker volume create redis-data
docker compose up -d<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p> 更新</p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">docker compose down &amp;&amp; docker compose pull &amp;&amp; docker compose up -d<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>安装 <a href="https://github.com/Rongronggg9/RSS-to-Telegram-Bot/blob/dev/docs/deployment-guide.md">RSS-to-Telegram-Bot</a> 以便从 telegram 上订阅 rss</p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">mkdir rsstt
cd rsstt
wget https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;Rongronggg9&#x2F;RSS-to-Telegram-Bot&#x2F;dev&#x2F;docker-compose.yml.sample -O docker-compose.yml
vi docker-compose.yml  # fill in env variables
docker compose up -d<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p> 更新</p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">docker compose down &amp;&amp; docker compose pull &amp;&amp; docker compose up -d<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ol>
<h3 id="Setup"><a href="#Setup" class="headerlink" title="Setup"></a>Setup</h3><p>有三种使用方式</p>
<ol>
<li>直接使用 Bot 订阅所有 rss</li>
<li>将 Bot 添加到频道中，然后在频道中订阅 rss<ol>
<li>创建一个公开频道，并输入 《频道名》，比如 <code>test_rss_hub</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2023/04/upgit_20230412_1681283739.png" alt="upgit_20230412_1681283739.png"></li>
<li>将 Bot 添加到频道中</li>
<li>私聊 Bot 发送 <code>/user_info @频道名</code>，比如 <code>/user_info @test_rss_hub</code></li>
<li>出现如下选项，点击 <code>将用户状态设置为“用户”</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2023/04/upgit_20230412_1681283864.png" alt="upgit_20230412_1681283864.png"></li>
</ol>
</li>
<li>将 Bot 添加到群组中，然后在群组中订阅 rss<ol>
<li>步骤与添加至频道类似</li>
</ol>
</li>
</ol>
<p>Tips: 必须先将频道/群组设置为 public，这样才能设置 Bot 发送到指定频道/群组。如果需要私有频道/群组，只需要在设置完成将频道/群组转为 private 即可。</p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>rss</tag>
        <tag>自建</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 charles 调试</title>
    <url>/2020/10/charles/</url>
    <content><![CDATA[<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p><a href="https://www.charlesproxy.com/download/">点击去官网下载</a></p>
<h2 id="激活"><a href="#激活" class="headerlink" title="激活"></a>激活</h2><pre class="line-numbers language-none"><code class="language-none">Registered Name: https:&#x2F;&#x2F;zhile.io
License Key: 48891cf209c6d32bf4<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<span id="more"></span>

<h2 id="开启-http-调试"><a href="#开启-http-调试" class="headerlink" title="开启 http 调试"></a>开启 http 调试</h2><ol>
<li>点击菜单栏 <code>Proxy -&gt; Proxy Settings</code></li>
<li>填写 <code>Port</code></li>
<li>勾选 <code>Enable transparent HTTP proxying</code></li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009171421.png" alt="20201009171421"></p>
<h2 id="开启抓取-macOS-请求"><a href="#开启抓取-macOS-请求" class="headerlink" title="开启抓取 macOS 请求"></a>开启抓取 macOS 请求</h2><ol>
<li>点击菜单栏 <code>Proxy -&gt; Proxy Settings</code></li>
<li>点击 <code>macOS</code></li>
<li>勾选 <code>Enable macOS proxy</code></li>
<li>勾选 <code>Use HTTP proxy</code></li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009171951.png" alt="20201009171951"></p>
<h2 id="开启抓取-Android-请求"><a href="#开启抓取-Android-请求" class="headerlink" title="开启抓取 Android 请求"></a>开启抓取 Android 请求</h2><p>手机连接局域网下 wifi，与 charles 必须为同一网络下。</p>
<ol>
<li><p>自动开启代理</p>
<ol>
<li><p>在已连接的 wifi 上点击更多，进入配置代理页。</p>
</li>
<li><p>勾选自动，在输入框URL中输入：</p>
 <pre class="line-numbers language-uri" data-language="uri"><code class="language-uri"><span class="token scheme">Https<span class="token scheme-delimiter">:</span></span><span class="token authority"><span class="token authority-delimiter">//</span><span class="token host">chls.pro</span></span><span class="token path"><span class="token path-separator">/</span>10.10.11.235:6666.pac</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
</ol>
</li>
<li><p>手动设置代理</p>
<ol>
<li>在已连接的 wifi 上点击更多，进入配置代理页。</li>
<li>勾选手动，输入 charles 的 ip 与端口（ip 为 macOS 主机 ip，端口为<a href="/2020/10/09/charles/#%E5%BC%80%E5%90%AF-http-%E8%B0%83%E8%AF%95">上面</a>填写的 http 端口）</li>
</ol>
</li>
</ol>
<h2 id="设置-https"><a href="#设置-https" class="headerlink" title="设置 https"></a>设置 https</h2><ol>
<li>点击 <code>Proxy -&gt; SSL Proxying Settings</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009173147.png" alt="20201009173147"></li>
<li>勾选 <code>Enable SSL Proxying</code></li>
<li>点击 <code>Add</code>，Host 填写 <code>*</code>，Port 填写 <code>443</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009173256.png" alt="20201009173256"></li>
</ol>
<h3 id="macOS-安装证书"><a href="#macOS-安装证书" class="headerlink" title="macOS 安装证书"></a>macOS 安装证书</h3><ol>
<li>点击菜单栏 <code>Help -&gt; SSL Proxying -&gt; Install Charles Root Certificate</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009172253.png" alt="20201009172253"></li>
<li>找到 <code>Charles Proxy.....</code> 并点击</li>
<li>点击 <code>Trust -&gt; When using this certificate</code>，并勾选 <code>Always Trust</code><br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201009172635.png" alt="20201009172635"></li>
</ol>
<h3 id="Android-安装证书"><a href="#Android-安装证书" class="headerlink" title="Android 安装证书"></a>Android 安装证书</h3><p>Android7 以后，系统不再信任用户级的证书，只信任系统级的证书，所以要抓包就需要把我们的 charles 证书安装至 Android 的系统目录中</p>
<h4 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h4><ol>
<li>一台已 root 的手机</li>
<li>Openssl</li>
</ol>
<h4 id="证书生成"><a href="#证书生成" class="headerlink" title="证书生成"></a>证书生成</h4><ol>
<li><p>将 <code>Filddler</code> 或者其他抓包程序的证书导出，一般为 <code>xxx.cer</code> 或者 <code>xxx.pem</code></p>
</li>
<li><p>使用 <code>openssl</code> 的 <code>x509</code> 指令进行 <code>cer</code> 证书转 <code>pem</code> 证书 和 用 <code>md5</code> 方式显示 <code>pem</code> 证书的 <code>hash</code> 值</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 1. 证书转换，已经是 pem 格式的证书不需要执行这一步</span>
openssl x509 <span class="token parameter variable">-inform</span> DER <span class="token parameter variable">-in</span> xxx.cer <span class="token parameter variable">-out</span> cacert.pem

<span class="token comment"># 2. 进行 MD5 的 hash 显示</span>

<span class="token comment"># openssl 版本在 1.0 以上的版本的执行这一句</span>
openssl x509 <span class="token parameter variable">-inform</span> PEM <span class="token parameter variable">-subject_hash_old</span> <span class="token parameter variable">-in</span> cacert.pem

<span class="token comment"># openssl 版本在 1.0 以下的版本的执行这一句</span>
openssl x509 <span class="token parameter variable">-inform</span> PEM <span class="token parameter variable">-subject_hash</span> <span class="token parameter variable">-in</span> cacert.pem<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p> 将第二条指令输出的类似 <code>347bacb5</code> 的值进行复制<br> <code>tips</code>：查看 <code>openssl</code> 版本的指令 <code>openssl version</code></p>
</li>
<li><p>将 pem 证书重命名<br> 使用上面复制的值（类似于 <code>347bacb5</code>）对 pem 证书进行重命名</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">mv</span> cacert.pem 347bacb5.0<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>将新证书放入手机系统证书目录(<code>/system/etc/security/cacerts</code>)  </p>
<p> 需要拷贝至此目录必须拥有 root 权限</p>
</li>
<li><p>重启 Android 设备以生效<br> 拷贝证书至 <code>/system/etc/security/cacerts</code> 之后，重启手机就可以使证书生效了</p>
</li>
</ol>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://www.jianshu.com/p/9f4ebde9c518">charles 安装配置 for Mac</a></p>
<p><a href="https://blog.csdn.net/djzhao627/article/details/102812783">给 Android7 及以上的手机安装系统级证书，实现 Fiddler 或者其他程序的 HTTPS 的抓包</a></p>
]]></content>
      <categories>
        <category>测试</category>
      </categories>
  </entry>
  <entry>
    <title>chrome 为什么不会自动过期 expires 为 session 的 cookie</title>
    <url>/2020/10/chrome-cookie-do-not-delete/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>今天对自己的项目做前后端联调时，发现在 mac 的 chrome 下（未测试 windows）没有自动过期 expires 为 session 的 cookie，对此感到一些疑惑</p>
<span id="more"></span>

<h2 id="尝试"><a href="#尝试" class="headerlink" title="尝试"></a>尝试</h2><p>在网上搜索的时候，发现了 <a href="https://segmentfault.com/q/1010000011981273">这篇文章</a>，遂按照网友的解决方案，尝试了如下行为：</p>
<ol>
<li>点击作左上角 <code>x</code>，结果：<strong>无效</strong></li>
<li><code>Command + Q</code> 退出，结果：<strong>无效</strong></li>
<li>右键 <code>Chrome</code> 图标退出，结果：<strong>无效</strong></li>
</ol>
<p>很难受，网友提供的方法都无效</p>
<h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><ol>
<li>点击 chrome <code>设置</code></li>
<li><code>默认浏览器</code> -&gt; <code>启动时</code> -&gt; 取消勾选 <code>继续浏览上次打开的网页</code>，勾选其他两个选项</li>
</ol>
<h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>为了增强用户体验，chrome 会在 <code>继续浏览上次打开的网页</code> 设置下，默认不删除会话 cookie</p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>chrome</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 Cloudflare Tunnels 进行内网穿透</title>
    <url>/2023/01/cloudflare-tunnels/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><a href="https://www.cloudflare.com/products/tunnel/">Cloudflare Tunnel</a> 是一款隧道软件，可以快速安全地加密应用程序到任何类型基础设施的流量，让您能够隐藏你的web 服务器IP 地址，阻止直接攻击，从而专注于提供出色的应用程序。  </p>
<p>相比于 <a href="https://github.com/fatedier/frp">frp</a> 等做内网穿透，Cloudflare Tunnels 的优势是不需要一台额外的公网服务器转发，并且可以享受到 Cloudflare CDN 带来的便利。</p>
<span id="more"></span>

<p>使用 frp 做内网穿透的流程如下：</p>
<pre class="mermaid">
flowchart LR
  server[Frp Server]
  clients[Frp Clients]

User --&gt;|http&#x2F;https| server --&gt;|http&#x2F;https| clients
</pre>

<p>使用 Cloudflare Tunnels 做内网穿透的流程如下：</p>
<pre class="mermaid">
flowchart LR
  tunnels[Cloudflare CDN]
  clients[Original Clients]

User --&gt;|http&#x2F;https| tunnels --&gt;|Cloudflare Tunnels|clients
</pre>

<h3 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h3><p>需要有一个由 Cloudflare 管理的域名</p>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><ol>
<li><p>安装 <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/">cloudflared</a></p>
<p>debian: </p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">apt install cloudflared<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>或者下载最新版</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">wget -q https:&#x2F;&#x2F;github.com&#x2F;cloudflare&#x2F;cloudflared&#x2F;releases&#x2F;latest&#x2F;download&#x2F;cloudflared-linux-amd64.deb -O cloudflared.deb &amp;&amp; dpkg -i cloudflared.deb &amp;&amp; rm cloudflared.deb<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>登录 cloudflared</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">cloudflared tunnel login<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>随后会生成一个 <code>~/.cloudflared/cert.pem</code> 文件用于后续授权</p>
</li>
<li><p>创建一个隧道 tunnel</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">cloudflared tunnel create &lt;名字&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>随后会在 <code>~/.cloudflared/</code> 目录下出现一个 json 文件 <code>[Tunnel-UUID].json</code>，里面保存着运行这条隧道所需要的授权信息。</p>
</li>
<li><p>创建一个配置文件，<code>~/.cloudflared/tunnels/名字.yaml</code>，并添加</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># 反代的源站地址</span>
<span class="token key atrule">url</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">8080</span>
<span class="token key atrule">tunnel</span><span class="token punctuation">:</span> &lt;Tunnel<span class="token punctuation">-</span>UUID<span class="token punctuation">></span>
<span class="token key atrule">credentials-file</span><span class="token punctuation">:</span> /root/.cloudflared/&lt;Tunnel<span class="token punctuation">-</span>UUID<span class="token punctuation">></span>.json<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>如果在上一步没有保存 UUID 信息，可以通过 <code>cloudflared tunnel info</code> 获取</p>
</li>
<li><p>配置路由</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">cloudflared tunnel route dns [名字或者 Tunnel-UUID] [想要绑定到的域名或其二级域名]<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>启动 <code>cloudflared</code></p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">cloudflared tunnel --config ~&#x2F;.cloudflared&#x2F;tunnels&#x2F;名字.yaml run<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ol>
<p>之后就可以通过在第 5 步 设置的域名进行访问</p>
<h4 id="范例"><a href="#范例" class="headerlink" title="范例"></a>范例</h4><p>例如，创建一条隧道从公网访问家里的 clash dashboard(地址为 192.168.1.1:9090)，设置隧道名字为 clash，并将其绑定到 clash.example.com：</p>
<ol>
<li><p><code>cloudflared tunnel login</code> 授权</p>
</li>
<li><p><code>cloudflared tunnel create clash</code>，假设输出 Tunnel-UUID 为 <code>c91c37a3-3efd-47fb-af0d-e5676ac122b9</code></p>
</li>
<li><p>将下面配置添加到 <code>~/.cloudflared/tunnels/clash.yaml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">url</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//192.168.1.1<span class="token punctuation">:</span><span class="token number">9090</span>
<span class="token key atrule">tunnel</span><span class="token punctuation">:</span> c91c37a3<span class="token punctuation">-</span>3efd<span class="token punctuation">-</span>47fb<span class="token punctuation">-</span>af0d<span class="token punctuation">-</span>e5676ac122b9
<span class="token key atrule">credentials-file</span><span class="token punctuation">:</span> /root/.cloudflared/c91c37a3<span class="token punctuation">-</span>3efd<span class="token punctuation">-</span>47fb<span class="token punctuation">-</span>af0d<span class="token punctuation">-</span>e5676ac122b9.json<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><code>cloudflared tunnel route dns clash clash.example.com</code> 创建路由</p>
</li>
<li><p><code>cloudflared tunnel --config ~/.cloudflared/tunnels/clash.yaml run</code> 启动隧道</p>
</li>
</ol>
<p>此时，访问 <code>clash.example.com</code> 等同于内网访问 <code>192.168.1.1:9090</code></p>
<h3 id="配置-systemd-service-自动启动"><a href="#配置-systemd-service-自动启动" class="headerlink" title="配置 systemd service 自动启动"></a>配置 systemd service 自动启动</h3><ol>
<li><p>在 <code>/etc/systemd/system</code> 目录下创建 service 配置文件 <code>EXAMPLE.service</code></p>
 <pre class="line-numbers language-systemd" data-language="systemd"><code class="language-systemd"><span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">Unit</span><span class="token punctuation">]</span></span>
<span class="token key attr-name">Description</span><span class="token punctuation">=</span><span class="token value attr-value">Cloudflare Tunnel</span>
<span class="token key attr-name">After</span><span class="token punctuation">=</span><span class="token value attr-value">network.target</span>
<span class="token key attr-name">StartLimitIntervalSec</span><span class="token punctuation">=</span><span class="token value attr-value">0</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">Service</span><span class="token punctuation">]</span></span>
<span class="token key attr-name">Type</span><span class="token punctuation">=</span><span class="token value attr-value">simple</span>
<span class="token key attr-name">Restart</span><span class="token punctuation">=</span><span class="token value attr-value">always</span>
<span class="token key attr-name">RestartSec</span><span class="token punctuation">=</span><span class="token value attr-value">1</span>
<span class="token key attr-name">User</span><span class="token punctuation">=</span><span class="token value attr-value">root</span>
<span class="token key attr-name">ExecStart</span><span class="token punctuation">=</span><span class="token value attr-value">cloudflared tunnel --config /root/.cloudflared/tunnels/名字.yaml run</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">Install</span><span class="token punctuation">]</span></span>
<span class="token key attr-name">WantedBy</span><span class="token punctuation">=</span><span class="token value attr-value">multi-user.target</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>启动服务</p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">systemctl enable EXAMPLE
systemctl start EXAMPLE<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

</li>
</ol>
]]></content>
      <categories>
        <category>网络</category>
      </categories>
      <tags>
        <tag>cloudflare</tag>
        <tag>内网穿透</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 Cloudflare Workers 反代 gist</title>
    <url>/2020/12/cloudflare-workers/</url>
    <content><![CDATA[<p>我经常用 GitHub 的 gist 服务来保存一些比较优秀的代码片段、配置等等，但是苦于 gist 在国内遭受了 DNS 污染，访问太不便利，所以一直在寻求一个类似 jsdelivr 加速 GitHub Repo 的方式，能够避免修改 host 直接访问 gist</p>
<span id="more"></span>

<h2 id="可行的方式"><a href="#可行的方式" class="headerlink" title="可行的方式"></a>可行的方式</h2><ol>
<li><a href="https://raw.githack.com/#no-uptime-guarantee">raw.githack.com</a></li>
<li>反代加速</li>
</ol>
<p>首先说说第一种，raw.githack.com 确实可以加速 gist，而且可以加速 repo。<br>使用方式很简单</p>
<ul>
<li>将 gist.githubusercontent.com 替换为 gistcdn.githack.com<br>例：<a href="http://gistcdn.githack.com/jiz4oh/189ed96ca1607b3ddf07bd64bcb459cd/raw/trojan.json">http://gistcdn.githack.com/jiz4oh/189ed96ca1607b3ddf07bd64bcb459cd/raw/trojan.json</a></li>
<li>将 raw.githubusercontent.com 替换为 rawcdn.githack.com<br>例：<a href="https://rawcdn.githack.com/jiz4oh/vim/master/vimrc">https://rawcdn.githack.com/jiz4oh/vim/master/vimrc</a></li>
</ul>
<p>但是这种方式有一点弊端，就是不方便发表永链，当首次访问时，githack 会将内容缓存在 cloudflare 长达一年。当链接内容变化时，不会及时刷新，只适合发布永久内容，或者分版本发布。</p>
<p>第二种方式就是我经过一段时间摸索并决定采用的方式</p>
<h2 id="Cloudflare-Workers"><a href="#Cloudflare-Workers" class="headerlink" title="Cloudflare Workers"></a>Cloudflare Workers</h2><p>Workers 的工作原理就是最近几年火热的 serverless。网站管理员不再一定需要一个服务器，只需要将对应的 Function 托管在 Workers 上，当用户访问网站时就会执行对应的 Function，值得一提的是 Cloudflare Workers 本质上只支持 JS（<a href="https://developers.cloudflare.com/workers/platform/languages">其他语言通过编译成 js 来执行</a>）。</p>
<h3 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h3><ol>
<li>cloudflare 的帐号</li>
<li>域名，并且托管到 cloudflare</li>
</ol>
<h3 id="创建-worker"><a href="#创建-worker" class="headerlink" title="创建 worker"></a>创建 worker</h3><ol>
<li><p>登录帐号到下图界面</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227143715.png" alt="20201227143715"></p>
</li>
<li><p>然后进入 Workers</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227143837.png" alt="20201227143837"></p>
</li>
<li><p>点击创建 worker</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227144011.png" alt="20201227144011"></p>
</li>
<li><p>这时候会进入如下界面</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227144908.png" alt="20201227144908"></p>
</li>
</ol>
<p>反代加速 gist 代码如下：</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 需要反代的地址</span>
<span class="token keyword">const</span> upstream <span class="token operator">=</span> <span class="token string">'gist.github.com'</span>
<span class="token comment">// 反代地址的子路径</span>
<span class="token keyword">const</span> upstreamPath <span class="token operator">=</span> <span class="token string">'/'</span>
<span class="token comment">// 反代网站的移动端域名</span>
<span class="token keyword">const</span> upstreamMobile <span class="token operator">=</span> <span class="token string">'gist.github.com'</span>

<span class="token comment">// 是否使用 https</span>
<span class="token keyword">const</span> useHttps <span class="token operator">=</span> <span class="token boolean">true</span>

<span class="token comment">// 禁止使用该 worker 的国家代码</span>
<span class="token keyword">const</span> blockedRegion <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'KP'</span><span class="token punctuation">,</span> <span class="token string">'SY'</span><span class="token punctuation">,</span> <span class="token string">'PK'</span><span class="token punctuation">,</span> <span class="token string">'CU'</span><span class="token punctuation">]</span>

<span class="token comment">// 禁止使用该 worker 的 ip 地址</span>
<span class="token keyword">const</span> blockedIp <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'0.0.0.0'</span><span class="token punctuation">,</span> <span class="token string">'127.0.0.1'</span><span class="token punctuation">]</span>

<span class="token comment">// 是否关闭缓存</span>
<span class="token keyword">const</span> disableCache <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token comment">// 替换条件</span>
<span class="token keyword">const</span> contentTypes <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">'text/plain'</span><span class="token punctuation">,</span>
  <span class="token string">'text/html'</span>
<span class="token punctuation">]</span>
<span class="token comment">// 反代网站中其他需要被替换的地址</span>
<span class="token keyword">const</span> replaceDict <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
  <span class="token string-property property">'$upstream'</span><span class="token operator">:</span> <span class="token string">'$workerDomain'</span><span class="token punctuation">,</span>
<span class="token punctuation">&#125;</span>

<span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'fetch'</span><span class="token punctuation">,</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
  event<span class="token punctuation">.</span><span class="token function">respondWith</span><span class="token punctuation">(</span><span class="token function">handleRequest</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>request<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>

<span class="token comment">/**
 * Respond to the request
 * @param &#123;Request&#125; request
 */</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleRequest</span><span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">const</span> region <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'cf-ipcountry'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> ip <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'cf-connecting-ip'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>blockedRegion<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>region<span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token string">'Access denied: WorkersProxy is not available in your region yet.'</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">403</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>blockedIp<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>ip<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token string">'Access denied: Your IP address is blocked by WorkersProxy.'</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">403</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">const</span> upstreamDomain <span class="token operator">=</span> <span class="token function">isMobile</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'user-agent'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">?</span> upstreamMobile <span class="token operator">:</span> upstream<span class="token punctuation">;</span>

  <span class="token comment">// 构建上游请求地址</span>
  <span class="token keyword">let</span> url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> workerDomain <span class="token operator">=</span> url<span class="token punctuation">.</span>host<span class="token punctuation">;</span>
  
  url<span class="token punctuation">.</span>protocol <span class="token operator">=</span> useHttps <span class="token operator">?</span> <span class="token string">'https:'</span> <span class="token operator">:</span> <span class="token string">'http'</span><span class="token punctuation">;</span>
  url<span class="token punctuation">.</span>pathname <span class="token operator">=</span> url<span class="token punctuation">.</span>pathname <span class="token operator">===</span> <span class="token string">'/'</span> <span class="token operator">?</span> upstreamPath <span class="token operator">:</span> upstreamPath <span class="token operator">+</span> url<span class="token punctuation">.</span>pathname<span class="token punctuation">;</span>
  url<span class="token punctuation">.</span>host <span class="token operator">=</span> upstreamDomain<span class="token punctuation">;</span>

  <span class="token comment">// 构建上游请求头</span>
  <span class="token keyword">const</span> newRequestHeaders <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Headers</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>headers<span class="token punctuation">)</span><span class="token punctuation">;</span>
  newRequestHeaders<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'Host'</span><span class="token punctuation">,</span> upstreamDomain<span class="token punctuation">)</span><span class="token punctuation">;</span>
  newRequestHeaders<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'Referer'</span><span class="token punctuation">,</span> url<span class="token punctuation">.</span>protocol <span class="token operator">+</span> <span class="token string">'//'</span> <span class="token operator">+</span> workerDomain<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 获取上游响应</span>
  <span class="token keyword">const</span> originalResponse <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">.</span>href<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
    <span class="token literal-property property">method</span><span class="token operator">:</span> request<span class="token punctuation">.</span>method<span class="token punctuation">,</span>
    <span class="token literal-property property">headers</span><span class="token operator">:</span> newRequestHeaders
  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> connectionUpgrade <span class="token operator">=</span> newRequestHeaders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"Upgrade"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>connectionUpgrade <span class="token operator">&amp;&amp;</span> connectionUpgrade<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">"websocket"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> originalResponse<span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">let</span> originalResponseClone <span class="token operator">=</span> originalResponse<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 构建响应头</span>
  <span class="token keyword">let</span> responseHeaders <span class="token operator">=</span> originalResponseClone<span class="token punctuation">.</span>headers<span class="token punctuation">;</span>
  <span class="token keyword">let</span> newResponseHeaders <span class="token operator">=</span> <span class="token function">buildResponseHeaders</span><span class="token punctuation">(</span>responseHeaders<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>newResponseHeaders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"x-pjax-url"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    newResponseHeaders<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"x-pjax-url"</span><span class="token punctuation">,</span> responseHeaders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"x-pjax-url"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"//"</span> <span class="token operator">+</span> upstreamDomain<span class="token punctuation">,</span> <span class="token string">"//"</span> <span class="token operator">+</span> workerDomain<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>

  <span class="token comment">// 构建响应体</span>
  <span class="token keyword">let</span> originalText<span class="token punctuation">;</span>
  <span class="token keyword">const</span> contentType <span class="token operator">=</span> newResponseHeaders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'content-type'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>contentType <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> types <span class="token operator">=</span> contentType<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">,</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">';'</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>types<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'charset=utf-8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token keyword">of</span> contentTypes<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>types<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
          originalText <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">replaceResponseText</span><span class="token punctuation">(</span>originalResponseClone<span class="token punctuation">,</span> upstreamDomain<span class="token punctuation">,</span> workerDomain<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token keyword">break</span>
        <span class="token punctuation">&#125;</span>
      <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
    originalText <span class="token operator">=</span> originalResponseClone<span class="token punctuation">.</span>body
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span>originalText<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
    <span class="token literal-property property">status</span><span class="token operator">:</span> originalResponseClone<span class="token punctuation">.</span>status<span class="token punctuation">,</span>
    <span class="token literal-property property">headers</span><span class="token operator">:</span> newResponseHeaders
  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">function</span> <span class="token function">isMobile</span><span class="token punctuation">(</span><span class="token parameter">userAgent</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  userAgent <span class="token operator">=</span> userAgent <span class="token operator">||</span> <span class="token string">''</span>
  <span class="token keyword">let</span> agents <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"Android"</span><span class="token punctuation">,</span> <span class="token string">"iPhone"</span><span class="token punctuation">,</span> <span class="token string">"SymbianOS"</span><span class="token punctuation">,</span> <span class="token string">"Windows Phone"</span><span class="token punctuation">,</span> <span class="token string">"iPad"</span><span class="token punctuation">,</span> <span class="token string">"iPod"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> v <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> v <span class="token operator">&lt;</span> agents<span class="token punctuation">.</span>length<span class="token punctuation">;</span> v<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>agents<span class="token punctuation">[</span>v<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
      <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
  <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">function</span> <span class="token function">buildResponseHeaders</span><span class="token punctuation">(</span><span class="token parameter">originalHeaders</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Headers</span><span class="token punctuation">(</span>originalHeaders<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>disableCache<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    result<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'Cache-Control'</span><span class="token punctuation">,</span> <span class="token string">'no-store'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>
  result<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'access-control-allow-origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  result<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'access-control-allow-credentials'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  result<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token string">'content-security-policy'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  result<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token string">'content-security-policy-report-only'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  result<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token string">'clear-site-data'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> result
<span class="token punctuation">&#125;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">replaceResponseText</span><span class="token punctuation">(</span><span class="token parameter">response<span class="token punctuation">,</span> upstreamDomain<span class="token punctuation">,</span> workerDomain</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> placeholders <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
    <span class="token string-property property">"$upstream"</span><span class="token operator">:</span> upstreamDomain<span class="token punctuation">,</span>
    <span class="token string-property property">"$workerDomain"</span><span class="token operator">:</span> workerDomain
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> origin <span class="token keyword">in</span> replaceDict<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">let</span> target <span class="token operator">=</span> replaceDict<span class="token punctuation">[</span>origin<span class="token punctuation">]</span>

    origin <span class="token operator">=</span> placeholders<span class="token punctuation">[</span>origin<span class="token punctuation">]</span> <span class="token operator">||</span> origin
    target <span class="token operator">=</span> placeholders<span class="token punctuation">[</span>target<span class="token punctuation">]</span> <span class="token operator">||</span> target

    <span class="token keyword">const</span> re <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RegExp</span><span class="token punctuation">(</span>origin<span class="token punctuation">,</span> <span class="token string">'g'</span><span class="token punctuation">)</span>
    text <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>re<span class="token punctuation">,</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">return</span> text<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>然后点击部署，就可以通过 <code>[project].[subdomain].workers.dev</code> 绕墙访问 gist 了，理论上是可以反代所有网站的，如果有需求的话，各位请自行修改代码~</p>
<p>注：</p>
<ol>
<li>project 是创建的 worker 的名称</li>
<li>subdomain 是注册 workers 是输入的名字</li>
</ol>
<h3 id="自定义域名（可选）"><a href="#自定义域名（可选）" class="headerlink" title="自定义域名（可选）"></a>自定义域名（可选）</h3><p>经过上面的步骤，我们已经可以使用类似于 <code>test.baidu.workers.dev</code> 这样的域名使用触发 worker 了。但是如果需要使用自定义域名代替上述域名的话，还需要额外设置</p>
<p>ps：刚开始我以为直接在 DNS 中 CNAME 到 workers.dev 就行，但是实际操作之后发现是不可以的。</p>
<p>例如我们需要配置一个 test.jiz4oh.com 域名来使用 worker</p>
<ol>
<li><p>配置 DNS 解析</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227180432.png" alt="20201227180432"></p>
</li>
<li><p>设置 worker 路由</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227180558.png" alt="20201227180558"></p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201227180837.png" alt="20201227180837"></p>
</li>
<li><p>等待数分钟，此时就可以访问 <code>test.jiz4oh.com</code> 来使用 worker 了</p>
</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>cloudflare</tag>
        <tag>freebie</tag>
      </tags>
  </entry>
  <entry>
    <title>Github+PicGo+Jsdelivr 创建个人图床</title>
    <url>/2020/09/github-picgo-jsdelivr/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>图床相信经常写东西而且文章图文并茂的朋友多多少少都接触过</p>
<p>公共图床服务要不就是备案麻烦，要不就是免费不稳定</p>
<p>本文就是关于如何利用 Github+Jsdelivr 创建免费、大容量、高速的个人图床，并且利用 vscode+PicGo 实现图片自动上传</p>
<span id="more"></span>

<h2 id="创建-GitHub-仓库"><a href="#创建-GitHub-仓库" class="headerlink" title="创建 GitHub 仓库"></a>创建 GitHub 仓库</h2><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925204037.png" alt="20200925204037"></p>
<p>仓库名随意</p>
<h3 id="生成-Token"><a href="#生成-Token" class="headerlink" title="生成 Token"></a>生成 Token</h3><ol>
<li>点击个人设置<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180107.png" alt="20200926180107"></li>
<li>点击开发者设置<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180238.png" alt="20200926180238"></li>
<li>点击个人令牌<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180349.png" alt="20200926180349"></li>
<li>创建令牌<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180428.png" alt="20200926180428"></li>
<li>保存命令<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180540.png" alt="20200926180540"></li>
</ol>
<h2 id="配置-PicGo"><a href="#配置-PicGo" class="headerlink" title="配置 PicGo"></a>配置 PicGo</h2><h3 id="vocode-安装-PicGo"><a href="#vocode-安装-PicGo" class="headerlink" title="vocode 安装 PicGo"></a>vocode 安装 PicGo</h3><p>扩展搜索 <code>picgo</code></p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180638.png" alt="20200926180638"></p>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>打开 vscode 设置</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926180821.png" alt="20200926180821"></p>
<p>点开 <code>扩展</code> =&gt; <code>PicGo</code></p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926181037.png" alt="20200926181037"></p>
<p>具体配置：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926181331.png" alt="20200926181331"></p>
<ol>
<li>选择 github</li>
<li>一般选择 master，如果<strong>不需要</strong>发布到其他分支<strong>不要</strong>更改</li>
<li>存入 vscode 的链接地址<ul>
<li>使用 jsdelivr：<code>https://cdn.jsdelivr.net/gh/红框5处内容</code>  </li>
<li>使用 rawgithubcontent：<code>https://raw.githubusercontent.com/红框5处内容/红框2处内容</code></li>
</ul>
</li>
<li>存入 GitHub 仓库的子文件夹，可为空</li>
<li>仓库名，格式为 <code>用户名/仓库名</code></li>
<li>之前生成的 Github Token</li>
</ol>
<h2 id="使用中的问题"><a href="#使用中的问题" class="headerlink" title="使用中的问题"></a>使用中的问题</h2><p>描述：jsdelivr 无法加速，显示 <code>Package size exceeded the configured limit of 50 MB</code>  </p>
<p>问题原因：jsdelivr 对每一个 release 限制加速 50MB</p>
<p>解决方案：</p>
<ol>
<li>进入仓库</li>
<li>创建 release<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926182953.png" alt="20200926182953"></li>
<li>创建 master 版本<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20200926183104.png" alt="20200926183104"></li>
<li>修改 PicGo 设置 Custom Url<br> 在最后面添加 <code>@master</code> 指定版本号</li>
<li>修改被拒绝显示的链接，在仓库名后面添加 <code>@master</code>。之前 jsdelivr 加速成功的链接不受影响</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>vscode</tag>
        <tag>github</tag>
        <tag>白嫖</tag>
      </tags>
  </entry>
  <entry>
    <title>Hexo 博客搭建过程实录</title>
    <url>/2020/09/hello-hexo/</url>
    <content><![CDATA[<h2 id="创建本地博客-blog"><a href="#创建本地博客-blog" class="headerlink" title="创建本地博客 blog"></a>创建本地博客 blog</h2><h3 id="安装-Hexo-脚手架"><a href="#安装-Hexo-脚手架" class="headerlink" title="安装 Hexo 脚手架"></a>安装 Hexo 脚手架</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-g</span> hexo-cli<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>安装之后可以使用 hexo 对博客命令操作</p>
<ol>
<li>创建 blog：<code>hexo init 文件夹</code></li>
<li>创建文章：<code>hexo new 文章名</code></li>
<li>生成静态文件：<code>hexo generate</code> 简写：<code>hexo g</code></li>
<li>清除已生成缓存：<code>hexo clean</code></li>
<li>启动本地服务：<code>hexo server</code> 简写：<code>hexo s</code><br> 默认监听 4000 端口</li>
<li>部署：<code>hexo deploy</code> 简写：<code>hexo d</code></li>
<li>生成静态文件并部署：<code>hexo g -d</code></li>
</ol>
<span id="more"></span>

<h3 id="创建博客"><a href="#创建博客" class="headerlink" title="创建博客"></a>创建博客</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">hexo init blog
<span class="token builtin class-name">cd</span> blog
hexo g
hexo s<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>访问 <a href="http://localhost:4000/">http://localhost:4000</a> 查看本地博客</p>
<h2 id="配置-blog-部署-Github-Pages"><a href="#配置-blog-部署-Github-Pages" class="headerlink" title="配置 blog 部署 Github Pages"></a>配置 blog 部署 Github Pages</h2><h3 id="创建-github-io-仓库"><a href="#创建-github-io-仓库" class="headerlink" title="创建 github.io 仓库"></a>创建 github.io 仓库</h3><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925204037.png" alt="20200925204037"></p>
<p>输入仓库名，仓库名以用户名开头，以 <code>github.io</code> 结尾  </p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925204246.png" alt="20200925204246"></p>
<h3 id="修改部署文件"><a href="#修改部署文件" class="headerlink" title="修改部署文件"></a>修改部署文件</h3><ol>
<li><p>创建成功后，打开刚刚使用 <code>hexo init blog</code> 命令生成的 blog 文件夹</p>
</li>
<li><p>修改 <code>_config.yml</code> 文件<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925203757.png" alt="20200925203757"><br>repo 填入刚刚创建的 GitHub 仓库地址，建议填写 ssh 仓库地址，https 地址也可</p>
</li>
<li><p>部署到 Github Pages</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 清理缓存</span>
hexo clean
<span class="token comment"># 部署</span>
hexo g <span class="token parameter variable">-d</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

</li>
</ol>
<h2 id="GitHub-Actions-自动部署"><a href="#GitHub-Actions-自动部署" class="headerlink" title="GitHub Actions 自动部署"></a>GitHub Actions 自动部署</h2><h3 id="创建-blog-仓库"><a href="#创建-blog-仓库" class="headerlink" title="创建 blog 仓库"></a>创建 blog 仓库</h3><p>创建另外一个 GitHub 私有仓库 blog，用来存放 hexo 项目并触发部署</p>
<h3 id="创建-ssh-密钥"><a href="#创建-ssh-密钥" class="headerlink" title="创建 ssh 密钥"></a>创建 ssh 密钥</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">ssh-keygen <span class="token parameter variable">-f</span> .ssh/github-deploy-key<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>.ssh 文件夹下会有 github-deploy-key 和 github-deploy-key.pub 两个文件。</p>
<h3 id="配置部署密钥"><a href="#配置部署密钥" class="headerlink" title="配置部署密钥"></a>配置部署密钥</h3><ol>
<li>打开 blog 仓库设置，Settings -&gt; Secrets -&gt; Add a new secret<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925210741.png" alt="20200925210741"><ul>
<li>在 Name 输入框填写 HEXO_DEPLOY_KEY。</li>
<li>在 Value 输入框填写 github-deploy-key 文件内容。</li>
</ul>
</li>
<li>打开 用户名.github.io 仓库设置，<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200925211312.png" alt="20200925211312"><ul>
<li>在 Title 输入框填写 HEXO_DEPLOY_PUB。</li>
<li>在 Key 输入框填写 github-deploy-key.pub 文件内容。</li>
<li>勾选 Allow write access 选项。</li>
</ul>
</li>
</ol>
<h3 id="编写-GitHub-Actions"><a href="#编写-GitHub-Actions" class="headerlink" title="编写 GitHub Actions"></a>编写 GitHub Actions</h3><p>在 hexo 博客文件夹下创建 .github/workflows/deploy.yml 文件，目录结构如下：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">blog
└── .github
    └── workflows
        └── deploy.yml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>编辑 deploy.yml 文件：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Deploy GitHub Pages

<span class="token key atrule">on</span><span class="token punctuation">:</span>
  <span class="token key atrule">push</span><span class="token punctuation">:</span>
    <span class="token key atrule">branches</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> master

<span class="token comment"># 修改 GitHub 用户名及邮箱才能正确部署</span>
<span class="token key atrule">env</span><span class="token punctuation">:</span>
  <span class="token key atrule">GIT_USER</span><span class="token punctuation">:</span> jiz4oh
  <span class="token key atrule">GIT_EMAIL</span><span class="token punctuation">:</span> jiz4oh@gmail.com

<span class="token key atrule">jobs</span><span class="token punctuation">:</span>
  <span class="token key atrule">build-and-deploy</span><span class="token punctuation">:</span>
    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest
    <span class="token key atrule">strategy</span><span class="token punctuation">:</span>
      <span class="token key atrule">matrix</span><span class="token punctuation">:</span>
        <span class="token key atrule">node-version</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> 10.x <span class="token punctuation">]</span>

    <span class="token key atrule">steps</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">persist-credentials</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Use Node.js $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>node@v1
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">node-version</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Configuration environment
        <span class="token key atrule">env</span><span class="token punctuation">:</span>
          <span class="token key atrule">HEXO_DEPLOY_PRI</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span>secrets.HEXO_DEPLOY_KEY<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
          mkdir -p ~/.ssh/
          echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan github.com >> ~/.ssh/known_hosts
          git config --global user.name $GIT_USER
          git config --global user.email $GIT_EMAIL</span>

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install dependencies
        <span class="token key atrule">run</span><span class="token punctuation">:</span> npm i

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install hexo<span class="token punctuation">-</span>cli
        <span class="token key atrule">run</span><span class="token punctuation">:</span> npm i <span class="token punctuation">-</span>g hexo<span class="token punctuation">-</span>cli

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Clean
        <span class="token key atrule">run</span><span class="token punctuation">:</span> hexo clean

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build &amp; Deploy
        <span class="token key atrule">run</span><span class="token punctuation">:</span> hexo g <span class="token punctuation">-</span>d<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>在 hexo 博客文件夹下，执行</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 配置 git 账户</span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> user.name 用户名
<span class="token function">git</span> config <span class="token parameter variable">--global</span> user.email 邮箱

<span class="token function">git</span> remote <span class="token function">add</span> origin BLOG仓库地址
<span class="token function">git</span> push --set-upstream origin master<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
  </entry>
  <entry>
    <title>使用 Valine 开启 Hexo 评论系统</title>
    <url>/2020/11/hexo-next-valine/</url>
    <content><![CDATA[<h2 id="关于评论系统"><a href="#关于评论系统" class="headerlink" title="关于评论系统"></a>关于评论系统</h2><p>博客搭建好之后，由于当时已经晚上12点了，并没有一鼓作气的开启评论系统，就一直拖到了现在。。。</p>
<p>今天终于想起来开启评论系统，在网上搜索了一下 hexo 的评论系统，有以下几个：</p>
<ul>
<li><a href="https://github.com/imsun/gitment">gitment</a>：基于 GitHub Issue 作为评论存储仓库，但是一直长时间无人维护</li>
<li><a href="https://github.com/gitalk/gitalk">gitalk</a>：同 <code>gitment</code> 使用 Github Issue，且页面比 <code>gitment</code> 更精美</li>
<li><a href="http://disqus.com/">disqus</a>：界面好看，支持 Facebook、twitter、Google+ 等账号登陆，但是 需要科学上网</li>
<li><a href="https://valine.js.org/">valine</a>：基于 LeanCloud 的无后端评论系统，支持匿名评论</li>
<li><a href="https://www.livere.com/">来必力</a>：韩国的评论系统，功能强大，支持多社交帐号登录，链接不太稳定，不支持匿名评论</li>
</ul>
<p>对于我来讲，我更偏向于使用 <code>gitalk</code> 作为评论系统，省心~但是 <code>gitalk</code> 类的评论系统被反馈有<a href="https://v2ex.com/t/535608">安全风险</a>，虽然是 GitHub 的锅，但是会对访客（还不是我。。。）的账户安全带来很大的不确定性，所以最终我还是选择了支持匿名评论的 Valine</p>
<span id="more"></span>

<h2 id="注册-LeanCloud"><a href="#注册-LeanCloud" class="headerlink" title="注册 LeanCloud"></a>注册 LeanCloud</h2><p>Valine 是基于 LeanCloud 进行使用的，所以我们需要在 LeanCloud 进行注册</p>
<p>因为现在 LeanCloud 国内版要求备案域名，所以使用<a href="https://leancloud.app/">国际版</a></p>
<h2 id="配置-LeanCloud"><a href="#配置-LeanCloud" class="headerlink" title="配置 LeanCloud"></a>配置 LeanCloud</h2><ul>
<li>创建应用：<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121111644.png" alt="20201121111644"></li>
<li>切换到存储页面：<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121111935.png" alt="20201121111935"></li>
<li>【结构化数据】-【创建 class】<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121112126.png" alt="20201121112126"></li>
<li>修改 ACL 的 write 权限，设置成下图效果<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121112221.png" alt="20201121112221"></li>
<li>切换到【设置】</li>
<li>【安全中心】-【Web 安全域名】，输入博客会用到的域名，如 <code>http://jiz4oh.com</code>、<code>https://jiz4oh.com</code><br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121112458.png" alt="20201121112458"></li>
<li>【应用 Keys】-【AppID】<br>【应用 Keys】-【AppKey】<br>这两个数据在下一步中使用（可点击后面的按钮快速复制）<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201121112803.png" alt="20201121112803"></li>
</ul>
<h2 id="配置-next"><a href="#配置-next" class="headerlink" title="配置 next"></a>配置 next</h2><ul>
<li><p>打开 next 的配置文件，如我是 <code>_confg.next.yml</code>，将下列代码复制进去，注意缩进  </p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">valine</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">appId</span><span class="token punctuation">:</span> <span class="token comment"># 拷贝上一步的 ID</span>
  <span class="token key atrule">appKey</span><span class="token punctuation">:</span> <span class="token comment"># 拷贝上一步的 Key</span>
  <span class="token key atrule">placeholder</span><span class="token punctuation">:</span> <span class="token comment"># Comment box placeholder</span>
  <span class="token key atrule">avatar</span><span class="token punctuation">:</span> mm <span class="token comment"># Gravatar style</span>
  <span class="token key atrule">meta</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>nick<span class="token punctuation">,</span> mail<span class="token punctuation">,</span> link<span class="token punctuation">]</span> <span class="token comment"># Custom comment header</span>
  <span class="token key atrule">pageSize</span><span class="token punctuation">:</span> <span class="token number">10</span> <span class="token comment"># Pagination size</span>
  <span class="token key atrule">lang</span><span class="token punctuation">:</span> <span class="token comment"># Language, available values: en, zh-cn</span>
  <span class="token key atrule">visitor</span><span class="token punctuation">:</span> <span class="token boolean important">false</span> <span class="token comment"># Article reading statistic</span>
  <span class="token key atrule">comment_count</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token comment"># If false, comment count will only be displayed in post page, not in home page</span>
  <span class="token key atrule">recordIP</span><span class="token punctuation">:</span> <span class="token boolean important">false</span> <span class="token comment"># Whether to record the commenter IP</span>
  <span class="token key atrule">serverURLs</span><span class="token punctuation">:</span> <span class="token comment"># When the custom domain name is enabled, fill it in here (it will be detected automatically by default, no need to fill in)</span>
  <span class="token key atrule">enableQQ</span><span class="token punctuation">:</span> <span class="token boolean important">false</span> <span class="token comment"># Whether to enable the Nickname box to automatically get QQ Nickname and QQ Avatar</span>
  <span class="token key atrule">requiredFields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token comment"># Set required fields: [nick] | [nick, mail]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
</ul>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
  </entry>
  <entry>
    <title>Hexo 博客使用 Next 主题及美化</title>
    <url>/2020/09/hexo-next/</url>
    <content><![CDATA[<h2 id="安装-Next"><a href="#安装-Next" class="headerlink" title="安装 Next"></a>安装 Next</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> hexo-theme-next<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="设置使用-Next"><a href="#设置使用-Next" class="headerlink" title="设置使用 Next"></a>设置使用 Next</h3><ol>
<li><p>打开博客文件夹</p>
</li>
<li><p>打开 <code>_config.yml</code> 文件</p>
 <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">theme</span><span class="token punctuation">:</span> next<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ol>
<span id="more"></span>

<h3 id="删除默认主题（可选）"><a href="#删除默认主题（可选）" class="headerlink" title="删除默认主题（可选）"></a>删除默认主题（可选）</h3><p>删除 <code>themes</code> 文件夹</p>
<h2 id="配置-Next"><a href="#配置-Next" class="headerlink" title="配置 Next"></a>配置 Next</h2><p>本配置文件基于 hexo-theme-next@^8.0.0  </p>
<p>未经特殊注明，均为修改主题配置文件 <code>_config.next.yml</code></p>
<h3 id="创建配置文件"><a href="#创建配置文件" class="headerlink" title="创建配置文件"></a>创建配置文件</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">touch</span> _config.next.yml<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="设置主题为-Gemini"><a href="#设置主题为-Gemini" class="headerlink" title="设置主题为 Gemini"></a>设置主题为 Gemini</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">scheme</span><span class="token punctuation">:</span> Gemini<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="返回顶部显示在侧边栏"><a href="#返回顶部显示在侧边栏" class="headerlink" title="返回顶部显示在侧边栏"></a>返回顶部显示在侧边栏</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">back2top</span><span class="token punctuation">:</span>
  <span class="token key atrule">sidebar</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="显示阅读进度条"><a href="#显示阅读进度条" class="headerlink" title="显示阅读进度条"></a>显示阅读进度条</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">reading_progress</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token comment"># 显示在底部</span>
  <span class="token key atrule">position</span><span class="token punctuation">:</span> bottom<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="显示加载进度条"><a href="#显示加载进度条" class="headerlink" title="显示加载进度条"></a>显示加载进度条</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">pace</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="侧边栏菜单展开所有"><a href="#侧边栏菜单展开所有" class="headerlink" title="侧边栏菜单展开所有"></a>侧边栏菜单展开所有</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">toc</span><span class="token punctuation">:</span>
  <span class="token key atrule">expand_all</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="启用-local-search"><a href="#启用-local-search" class="headerlink" title="启用 local search"></a>启用 local search</h3><p>安装外部依赖：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> hexo-generator-searchdb<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>编辑 <code>_config.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">search</span><span class="token punctuation">:</span>
  <span class="token key atrule">path</span><span class="token punctuation">:</span> search.xml
  <span class="token key atrule">field</span><span class="token punctuation">:</span> post
  <span class="token key atrule">format</span><span class="token punctuation">:</span> html
  <span class="token key atrule">limit</span><span class="token punctuation">:</span> <span class="token number">10000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>编辑 <code>_config.next.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># Local search</span>
<span class="token key atrule">local_search</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h3 id="设置代码高亮"><a href="#设置代码高亮" class="headerlink" title="设置代码高亮"></a>设置代码高亮</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">codeblock</span><span class="token punctuation">:</span>
  <span class="token key atrule">copy_button</span><span class="token punctuation">:</span>
    <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">prism</span><span class="token punctuation">:</span>
    <span class="token key atrule">light</span><span class="token punctuation">:</span> prism<span class="token punctuation">-</span>tomorrow
    <span class="token key atrule">dark</span><span class="token punctuation">:</span> prism<span class="token punctuation">-</span>tomorrow<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="添加版权声明"><a href="#添加版权声明" class="headerlink" title="添加版权声明"></a>添加版权声明</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">creative_commons</span><span class="token punctuation">:</span>
  <span class="token key atrule">license</span><span class="token punctuation">:</span> by<span class="token punctuation">-</span>nc<span class="token punctuation">-</span>sa
  <span class="token key atrule">sidebar</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">post</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="修改建站时间"><a href="#修改建站时间" class="headerlink" title="修改建站时间"></a>修改建站时间</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">footer</span><span class="token punctuation">:</span>
  <span class="token key atrule">since</span><span class="token punctuation">:</span> <span class="token number">2020</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="开启彩色缎带"><a href="#开启彩色缎带" class="headerlink" title="开启彩色缎带"></a>开启彩色缎带</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">canvas_ribbon</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="开启文章字数统计"><a href="#开启文章字数统计" class="headerlink" title="开启文章字数统计"></a>开启文章字数统计</h3><p>安装外部依赖：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> hexo-word-counter<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>编辑 <code>_config.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">symbols_count_time</span><span class="token punctuation">:</span>
  <span class="token key atrule">symbols</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">time</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">total_symbols</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">total_time</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">exclude_codeblock</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
  <span class="token comment"># 平均字长，默认为 4，中文为 2</span>
  <span class="token key atrule">awl</span><span class="token punctuation">:</span> <span class="token number">2</span>
  <span class="token key atrule">wpm</span><span class="token punctuation">:</span> <span class="token number">275</span>
  <span class="token key atrule">suffix</span><span class="token punctuation">:</span> <span class="token string">"mins."</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="开启文章阅读量统计"><a href="#开启文章阅读量统计" class="headerlink" title="开启文章阅读量统计"></a>开启文章阅读量统计</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">busuanzi_count</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="添加-GitHub-Banner"><a href="#添加-GitHub-Banner" class="headerlink" title="添加 GitHub Banner"></a>添加 GitHub Banner</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">github_banner</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> enable
  <span class="token key atrule">permalink</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//github.com/jiz4oh
  <span class="token key atrule">title</span><span class="token punctuation">:</span> Follow me on GitHub<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="删除不必要文件"><a href="#删除不必要文件" class="headerlink" title="删除不必要文件"></a>删除不必要文件</h3><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">minify</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="添加标签页面"><a href="#添加标签页面" class="headerlink" title="添加标签页面"></a>添加标签页面</h3><p>命令行执行：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">hexo new page tags<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>修改 <code>source/tags/index.md</code></p>
<pre class="line-numbers language-markdown" data-language="markdown"><code class="language-markdown"><span class="token front-matter-block"><span class="token punctuation">---</span>
<span class="token front-matter yaml language-yaml"><span class="token key atrule">title</span><span class="token punctuation">:</span> tags
<span class="token key atrule">date</span><span class="token punctuation">:</span> <span class="token datetime number">2020-09-26 12:51:57</span>
<span class="token comment"># 添加下面两行</span>
<span class="token key atrule">type</span><span class="token punctuation">:</span> tags
<span class="token key atrule">comments</span><span class="token punctuation">:</span> <span class="token boolean important">false</span></span>
<span class="token punctuation">---</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>编辑 <code>_config.next.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">menu</span><span class="token punctuation">:</span>
  <span class="token key atrule">tags</span><span class="token punctuation">:</span> /tags/ <span class="token punctuation">|</span><span class="token punctuation">|</span> fa fa<span class="token punctuation">-</span>tags<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="添加分类页面"><a href="#添加分类页面" class="headerlink" title="添加分类页面"></a>添加分类页面</h3><p>命令行执行：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">hexo new page categories<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>修改 <code>source/tags/categories.md</code></p>
<pre class="line-numbers language-markdown" data-language="markdown"><code class="language-markdown"><span class="token front-matter-block"><span class="token punctuation">---</span>
<span class="token front-matter yaml language-yaml"><span class="token key atrule">title</span><span class="token punctuation">:</span> categories
<span class="token key atrule">date</span><span class="token punctuation">:</span> <span class="token datetime number">2020-09-26 13:07:13</span>
<span class="token comment"># 添加下面两行</span>
<span class="token key atrule">type</span><span class="token punctuation">:</span> categories
<span class="token key atrule">comments</span><span class="token punctuation">:</span> <span class="token boolean important">false</span></span>
<span class="token punctuation">---</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>编辑 <code>_config.next.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">menu</span><span class="token punctuation">:</span>
  <span class="token key atrule">categories</span><span class="token punctuation">:</span> /categories/ <span class="token punctuation">|</span><span class="token punctuation">|</span> fa fa<span class="token punctuation">-</span>categories<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="设置字体"><a href="#设置字体" class="headerlink" title="设置字体"></a>设置字体</h3><h4 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h4><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">font</span><span class="token punctuation">:</span>
  <span class="token key atrule">enable</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">global</span><span class="token punctuation">:</span>
    <span class="token key atrule">external</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">family</span><span class="token punctuation">:</span> Arial
  <span class="token comment"># 以下设置只能修改 `` 注释的 md 代码块，不能修改 ```code block``` 字体大小</span>
  <span class="token comment"># ```code block``` 字体大小修改见下</span>
  <span class="token comment"># codes:</span>
  <span class="token comment">#   external: true</span>
  <span class="token comment">#   family: Arial</span>
  <span class="token comment">#   size: 0.8</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="大小"><a href="#大小" class="headerlink" title="大小"></a>大小</h4><p>编辑 <code>_config.next.yml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">custom_file_path</span><span class="token punctuation">:</span>
  <span class="token key atrule">style</span><span class="token punctuation">:</span> source/_data/styles.styl<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>新建 <code>source/_data/styles.styl</code>：</p>
<pre class="line-numbers language-stylus" data-language="stylus"><code class="language-stylus"><span class="token selector"><span class="token comment">// 修改文章字体大小，强制保持 1em</span>
div.post-body <span class="token punctuation">&#123;</span></span>
  <span class="token property-declaration"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">em</span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span>

<span class="token selector"><span class="token comment">// 修改 code block 中的字体，需要写入以下样式</span>
code[class*=language-] <span class="token punctuation">&#123;</span></span>
  <span class="token property-declaration"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">14</span><span class="token unit">px</span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h2><p>博客主题美化参考了诸多前辈的文章，感谢各位大神的无私分享~</p>
<h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="http://jeffyang.top/Hexo/Hexo%E4%B8%BB%E9%A2%98Next%E7%BE%8E%E5%8C%96/">hexo的next主题个性化教程:打造炫酷网站</a></p>
<p><a href="https://theme-next.iissnan.com/theme-settings.html">Next 原作者 iissnan 大神教程，v6 版本以上部分配置不适用</a></p>
<p><a href="https://theme-next.js.org/docs/getting-started/">Next 社区教程</a></p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
  </entry>
  <entry>
    <title>CentOS 7 安装 ehforwarderbot V1 来收发微信</title>
    <url>/2020/09/how-to-install-ehforwarderbot-v1/</url>
    <content><![CDATA[<h2 id="ehforwarderbot-介绍"><a href="#ehforwarderbot-介绍" class="headerlink" title="ehforwarderbot 介绍"></a>ehforwarderbot 介绍</h2><p>ehforwarderbot 是由 blueset 开源在 <a href="https://github.com/blueset/ehForwarderBot">github</a> 的一款专用于转发微信/QQ/Faceboot Message 到 Telegram 的机器人</p>
<p>ehforwarderbot 支持 多微信，多QQ 汇集到一起集中处理消息，特别适合：</p>
<ol>
<li>Telegram 重度用户</li>
<li>需要对多个微信、qq 消息进行处理的用户</li>
</ol>
<span id="more"></span>

<p>最新版的 ehforwarderbot 已更新到 v2.0.0，本教程仅适用于 v1 版本，且 v2 版本与 v1 版本的数据库结构大改，无法从 v1 无缝迁移到 v2 版本。请谨慎安装 v1 版本</p>
<h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ol>
<li>消息云同步，⽂字，语⾳，图⽚，视频，发送的链接，⽂件都可以保存在 tg 云端</li>
<li>消息⼏乎⽆延迟，对⽐ Gcmformojo，tg 发消息很快，没有卡顿，就像你正常聊 tg ⼀样，也没有消息发送失<br>败的情况（除⾮你⽹络没连上）</li>
<li>耗电，明显优于微信毒瘤。tg ⾃带 gcm，如果你需要，可以不留 tg 后台，由 gcm 拉起通知</li>
<li>⽆需挂梯，以往 Gcmformojo 有的地区需要挂飞机才能收发，⽽ tg ⾃带⼀个代理功能，可以通过代理收发微<br>信</li>
<li>公众号信息也能推送，⽽且 TG ⾃带应⽤内浏览器，也能⽅便的查看公众号推送的⽂章</li>
</ol>
<h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><p>ehforwarderbot 依赖于 web 版微信，所以 web 版微信没有的功能，它也不可能有，例如：</p>
<ol>
<li>无法收发红包</li>
<li>无法查看别人分享的历史记录</li>
<li>某些表情无法正常显示</li>
</ol>
<h2 id="安装-docker-CE"><a href="#安装-docker-CE" class="headerlink" title="安装 docker CE"></a>安装 docker CE</h2><h3 id="卸载旧版本docker"><a href="#卸载旧版本docker" class="headerlink" title="卸载旧版本docker"></a>卸载旧版本docker</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">sudo</span> yum remove <span class="token function">docker</span> <span class="token punctuation">\</span>
                  docker-client <span class="token punctuation">\</span>
                  docker-client-latest <span class="token punctuation">\</span>
                  docker-common <span class="token punctuation">\</span>
                  docker-latest <span class="token punctuation">\</span>
                  docker-latest-logrotate <span class="token punctuation">\</span>
                  docker-logrotate <span class="token punctuation">\</span>
                  docker-engine<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">sudo</span> yum update
<span class="token function">sudo</span> yum <span class="token function">install</span> <span class="token parameter variable">-y</span> yum-utils <span class="token punctuation">\</span>
  device-mapper-persistent-data <span class="token punctuation">\</span>
  lvm2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="添加-Docker-稳定版本的-yum-软件源"><a href="#添加-Docker-稳定版本的-yum-软件源" class="headerlink" title="添加 Docker 稳定版本的 yum 软件源"></a>添加 Docker 稳定版本的 yum 软件源</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">sudo</span> yum-config-manager <span class="token punctuation">\</span>
    --add-repo <span class="token punctuation">\</span>
    https://download.docker.com/linux/centos/docker-ce.repo<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h3 id="安装-docker"><a href="#安装-docker" class="headerlink" title="安装 docker"></a>安装 docker</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">sudo</span> yum update
<span class="token function">sudo</span> yum <span class="token function">install</span> docker-ce<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>docker开机自启：<br><code>sudo systemctl enable docker</code><br>启动docker服务：<br><code>sudo systemctl start docker</code></p>
<h2 id="安装efb"><a href="#安装efb" class="headerlink" title="安装efb"></a>安装efb</h2><h3 id="创建配置文件"><a href="#创建配置文件" class="headerlink" title="创建配置文件"></a>创建配置文件</h3><h3 id="获取-Telegram-Bot-Token"><a href="#获取-Telegram-Bot-Token" class="headerlink" title="获取 Telegram Bot Token"></a>获取 Telegram Bot Token</h3><ol>
<li>在Telegram关注@BotFather</li>
<li>再到对话框依次输入：<code>/start</code> =&gt; <code>/newbot</code></li>
<li>然后会要你给机器人命名(如：TestBot)</li>
<li>命名完成会得到 <strong>Token</strong>。</li>
</ol>
<h3 id="获取自己的-Userid"><a href="#获取自己的-Userid" class="headerlink" title="获取自己的 Userid"></a>获取自己的 Userid</h3><ol>
<li>先和你的机器人聊天，随便发一句话。</li>
<li>在浏览器输入 <a href="https://api.telegram.org/botxx:xx/getUpdates(%E5%85%B6%E4%B8%ADxx:xx%E4%B8%BAToken)">https://api.telegram.org/botxx:xx/getUpdates(其中xx:xx为Token)</a></li>
<li>然后 chat 后面的 id 即为你的 <strong>Userid</strong>。</li>
</ol>
<h3 id="创建-config"><a href="#创建-config" class="headerlink" title="创建 config"></a>创建 config</h3><p>创建 <code>config.py</code> 文件</p>
<pre class="line-numbers language-python" data-language="python"><code class="language-python">master_channel <span class="token operator">=</span> <span class="token string">'plugins.eh_telegram_master'</span><span class="token punctuation">,</span> <span class="token string">'TelegramChannel'</span>
slave_channels <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token string">'plugins.eh_wechat_slave'</span><span class="token punctuation">,</span> <span class="token string">'WeChatChannel'</span><span class="token punctuation">)</span><span class="token punctuation">]</span>

eh_telegram_master <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
    <span class="token string">"token"</span><span class="token punctuation">:</span> <span class="token string">"机器人的 TOKEN"</span><span class="token punctuation">,</span>
    <span class="token string">"admins"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>你自己的 Userid<span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">"bing_speech_api"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"3243f6a8885a308d313198a2e037073"</span><span class="token punctuation">,</span> <span class="token string">"2b7e151628ae082b7e151628ae08"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">"baidu_speech_api"</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span>
        <span class="token string">"app_id"</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        <span class="token string">"api_key"</span><span class="token punctuation">:</span> <span class="token string">"3243f6a8885a308d313198a2e037073"</span><span class="token punctuation">,</span>
        <span class="token string">"secret_key"</span><span class="token punctuation">:</span> <span class="token string">"2b7e151628ae082b7e151628ae08"</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="创建-tgdata-db"><a href="#创建-tgdata-db" class="headerlink" title="创建 tgdata.db"></a>创建 tgdata.db</h3><p>创建 <code>tgdata.db</code> 文件，该文件可以为空</p>
<h3 id="启动-ehforwarderbot"><a href="#启动-ehforwarderbot" class="headerlink" title="启动 ehforwarderbot"></a>启动 ehforwarderbot</h3><p>将 <code>config.py</code> 和 <code>tgdata.db</code> 放在 <code>/root</code> 目录下</p>
<p>运行：  </p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">-d</span> <span class="token parameter variable">--restart</span><span class="token operator">=</span>always <span class="token parameter variable">--name</span><span class="token operator">=</span>efb <span class="token punctuation">\</span>
    <span class="token parameter variable">-v</span> /root/config.py:/opt/ehForwarderBot/config.py <span class="token punctuation">\</span>
    <span class="token parameter variable">-v</span> /root/tgdata.db:/opt/ehForwarderBot/plugins/eh_telegram_master/tgdata.db <span class="token punctuation">\</span>
    royx/docker-efb<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="登录-efb"><a href="#登录-efb" class="headerlink" title="登录 efb"></a>登录 efb</h3><p><code>docker logs efb</code></p>
<p>此时屏幕上会出现二维码，使用手机扫码登录即可</p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>efb</tag>
        <tag>ehforwarderbot</tag>
        <tag>telegram</tag>
        <tag>wechat</tag>
      </tags>
  </entry>
  <entry>
    <title>CentOS 7 安装 efb v2 来收发微信</title>
    <url>/2020/09/how-to-install-ehforwarderbot-v2/</url>
    <content><![CDATA[<p><a href="/2020/09/how-to-install-ehforwarderbot-v1/">上一篇文章已经介绍了如何安装 v1</a>，这篇文章来介绍如何安装 v2 版本的 ehforwarderbot</p>
<span id="more"></span>

<h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">yum <span class="token function">install</span> <span class="token parameter variable">-y</span> gcc file-devel libwebp-tools <span class="token function">git</span> <span class="token function">screen</span>

<span class="token comment"># 安装 python3.6 和 pip3</span>
<span class="token function">wget</span> https://www.moerats.com/usr/shell/Python3/CentOS_Python3.6.sh <span class="token operator">&amp;&amp;</span> <span class="token function">sh</span> CentOS_Python3.6.sh
yum <span class="token function">install</span> python3-pip python3-dev python3-setuptools

<span class="token comment"># 下载 ffmpeg</span>
<span class="token function">wget</span> https://www.moerats.com/usr/down/ffmpeg/ffmpeg-git-<span class="token variable"><span class="token variable">$(</span>getconf LONG_BIT<span class="token variable">)</span></span>bit-static.tar.xz <span class="token operator">&amp;&amp;</span> <span class="token function">tar</span> xvf ffmpeg-git-*-static.tar.xz
<span class="token function">mv</span> ffmpeg-git-*/ffmpeg ffmpeg-git-*/ffprobe /usr/bin/
<span class="token function">rm</span> <span class="token parameter variable">-rf</span> ffmpeg-git-*<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="安装-efb"><a href="#安装-efb" class="headerlink" title="安装 efb"></a>安装 efb</h2><h3 id="安装-efb（ehforwarderbot）"><a href="#安装-efb（ehforwarderbot）" class="headerlink" title="安装 efb（ehforwarderbot）"></a>安装 efb（ehforwarderbot）</h3><p>二选一：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 安装稳定版</span>
pip3 <span class="token function">install</span> ehforwarderbot

<span class="token comment"># 安装开发版</span>
pip3 <span class="token function">install</span> git+https://github.com/blueset/ehforwarderbot.git<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="安装-ETB（efb-telegram-master）和-EWS（efb-wechat-slave）"><a href="#安装-ETB（efb-telegram-master）和-EWS（efb-wechat-slave）" class="headerlink" title="安装 ETB（efb-telegram-master）和 EWS（efb-wechat-slave）"></a>安装 ETB（efb-telegram-master）和 EWS（efb-wechat-slave）</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 安装TG和微信模块</span>
pip3 <span class="token function">install</span> efb-telegram-master efb-wechat-slave<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h2 id="配置-efb"><a href="#配置-efb" class="headerlink" title="配置 efb"></a>配置 efb</h2><h3 id="配置-efb（ehforwarderbot）"><a href="#配置-efb（ehforwarderbot）" class="headerlink" title="配置 efb（ehforwarderbot）"></a>配置 efb（ehforwarderbot）</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">mkdir</span> <span class="token parameter variable">-p</span> ~/.ehforwarderbot/profiles/default
<span class="token function">vi</span> ~/.ehforwarderbot/profiles/default/config.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>保存下列代码到 <code>config.yaml</code>：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">master_channel</span><span class="token punctuation">:</span> <span class="token string">"blueset.telegram"</span>
<span class="token key atrule">slave_channels</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> <span class="token string">"blueset.wechat"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>这只是登录一个微信号，如果你要同时登录多个微信号，那么配置文件就需要改为：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># 比如我要同时登录并收发3个微信号</span>
<span class="token key atrule">master_channel</span><span class="token punctuation">:</span> blueset.telegram
<span class="token key atrule">slave_channels</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> blueset.wechat
<span class="token punctuation">-</span> blueset.wechat<span class="token comment">#moe123</span>
<span class="token punctuation">-</span> blueset.wechat<span class="token comment">#rats321</span>
<span class="token comment"># #号后面指定id，只能是字母、数字、下划线</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="配置-ETB"><a href="#配置-ETB" class="headerlink" title="配置 ETB"></a>配置 ETB</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 同样的也建在 default 文件夹，如果你上面更改了 default 文件夹，那这里也要更改</span>
<span class="token function">mkdir</span> ~/.ehforwarderbot/profiles/default/blueset.telegram
<span class="token function">vi</span> ~/.ehforwarderbot/profiles/default/blueset.telegram/config.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>保存下列代码到 <code>blueset.telegram/config.yaml</code>：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">token</span><span class="token punctuation">:</span> <span class="token string">"机器人的 TOKEN"</span>
<span class="token key atrule">admins</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> 你自己的 Userid
<span class="token key atrule">flags</span><span class="token punctuation">:</span>
    <span class="token comment"># 关闭自动语言设置，使用 systemd 启动时默认中文</span>
    <span class="token key atrule">auto_locale</span><span class="token punctuation">:</span> <span class="token boolean important">false</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="配置-EWS"><a href="#配置-EWS" class="headerlink" title="配置 EWS"></a>配置 EWS</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 同样的也建在 default 文件夹，如果你上面更改了 default 文件夹，那这里也要更改</span>
<span class="token function">mkdir</span> ~/.ehforwarderbot/profiles/default/blueset.wechat
<span class="token function">vi</span> ~/.ehforwarderbot/profiles/default/blueset.wechat/config.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>保存下列代码到 <code>blueset.wechat/config.yaml</code>：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">flags</span><span class="token punctuation">:</span>
    <span class="token comment"># tg 端编辑消息时以撤回并重新发送的方式发送到微信</span>
    <span class="token key atrule">delete_on_edit</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token comment"># 每当请求会话列表时，强制刷新会话列表</span>
    <span class="token key atrule">refresh_friends</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token comment"># 使用 iTerm2 图像协议 显示二维码。本功能只适用于 iTerm2 用户</span>
    <span class="token key atrule">imgcat_qr</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token comment"># 在收到第三方合作应用分享给微信的链接时，其附带的预览图将缩略图上传到 sm.ms</span>
    <span class="token key atrule">app_shared_link_mode</span><span class="token punctuation">:</span> upload<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="设置守护进程"><a href="#设置守护进程" class="headerlink" title="设置守护进程"></a>设置守护进程</h2><h3 id="创建-efb-服务"><a href="#创建-efb-服务" class="headerlink" title="创建 efb 服务"></a>创建 efb 服务</h3><p><code>vi /etc/systemd/system/efb.service</code></p>
<p>保存以下配置到 <code>efb.service</code>：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token punctuation">[</span>Unit<span class="token punctuation">]</span>
<span class="token assign-left variable">Description</span><span class="token operator">=</span>EH Forwarder Bot instance
<span class="token assign-left variable">After</span><span class="token operator">=</span>network.target
<span class="token assign-left variable">Wants</span><span class="token operator">=</span>network.target
<span class="token assign-left variable">Documentation</span><span class="token operator">=</span>https://github.com/blueset/ehForwarderBot

<span class="token punctuation">[</span>Service<span class="token punctuation">]</span>
<span class="token assign-left variable">Type</span><span class="token operator">=</span>simple
<span class="token assign-left variable">Environment</span><span class="token operator">=</span><span class="token string">'LANG=zh_CN.UTF-8'</span> <span class="token string">'PYTHONIOENCODING=utf_8'</span> <span class="token string">'EFB_DATA_PATH=/root/.ehforwarderbot'</span>
<span class="token assign-left variable">ExecStart</span><span class="token operator">=</span>/usr/local/bin/ehforwarderbot <span class="token parameter variable">--verbose</span>
<span class="token assign-left variable">Restart</span><span class="token operator">=</span>on-abort
<span class="token assign-left variable">KillSignal</span><span class="token operator">=</span>SIGINT

<span class="token punctuation">[</span>Install<span class="token punctuation">]</span>
<span class="token assign-left variable">WantedBy</span><span class="token operator">=</span>multi-user.target<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="使用-systemctl-管理-efb"><a href="#使用-systemctl-管理-efb" class="headerlink" title="使用 systemctl 管理 efb"></a>使用 systemctl 管理 efb</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 开机启动</span>
<span class="token function">sudo</span> systemctl <span class="token builtin class-name">enable</span> efb

<span class="token comment"># 启动</span>
<span class="token function">sudo</span> systemctl start efb

<span class="token comment"># 关闭</span>
<span class="token function">sudo</span> systemctl stop efb

<span class="token comment"># 重启</span>
<span class="token function">sudo</span> systemctl restart efb

<span class="token comment"># 查看运行详情</span>
<span class="token function">sudo</span> systemctl status efb

<span class="token comment"># 查看更加详细的运行详情</span>
<span class="token function">sudo</span> systemctl status efb <span class="token parameter variable">-l</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="登录-efb"><a href="#登录-efb" class="headerlink" title="登录 efb"></a>登录 efb</h2><ul>
<li>如果不使用 systemctl 管理，则执行 <code>ehforwarderbot --verbose</code> 进行启动  </li>
<li>如果使用 systemctl 进行管理<ol>
<li><code>sudo systemctl start efb</code></li>
<li><code>sudo systemctl status efb -l</code> 然后复制二维码连接到浏览器，扫码登陆</li>
</ol>
</li>
</ul>
<h2 id="安装中遇到的错误"><a href="#安装中遇到的错误" class="headerlink" title="安装中遇到的错误"></a>安装中遇到的错误</h2><h3 id="缺少-cairo-依赖"><a href="#缺少-cairo-依赖" class="headerlink" title="缺少 cairo 依赖"></a>缺少 cairo 依赖</h3><p>描述：</p>
  <pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">raise</span> OSError<span class="token punctuation">(</span>error_message<span class="token punctuation">)</span> <span class="token comment"># pragma: no cover</span>
OSError<span class="token punctuation">:</span> no library called <span class="token string">"cairo"</span> was found<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>解决方案：</p>
  <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">yum <span class="token function">install</span> <span class="token parameter variable">-y</span> cairo-devel libtiff* <span class="token operator">&amp;&amp;</span> pip3 <span class="token function">install</span> cairosvg cairocffi<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>描述：</p>
  <pre class="line-numbers language-python" data-language="python"><code class="language-python">Command <span class="token string">"python setup.py egg_info"</span> failed <span class="token keyword">with</span> error code <span class="token number">1</span> <span class="token keyword">in</span> <span class="token operator">/</span>tmp<span class="token operator">/</span>pip<span class="token operator">-</span>build<span class="token operator">-</span>g3q2qdl8<span class="token operator">/</span>cairocffi<span class="token operator">/</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>原因：pip 版本过低</p>
<p>解决方案：</p>
<p>  <code>pip3 install --upgrade pip</code></p>
<h3 id="python-安装失败"><a href="#python-安装失败" class="headerlink" title="python 安装失败"></a>python 安装失败</h3><p>描述：</p>
  <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">configure: error: <span class="token keyword">in</span> <span class="token variable"><span class="token variable">`</span>/usr/local/src/Python-3.6.4':
configure: error: no acceptable C compiler found <span class="token keyword">in</span> <span class="token environment constant">$PATH</span>
See <span class="token variable">`</span></span>config.log' <span class="token keyword">for</span> <span class="token function">more</span> details<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>解决方案：<br>  <code>yum install gcc -y</code></p>
<p>描述：</p>
  <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">zipimport.ZipImportError: can't decompress data<span class="token punctuation">;</span> zlib not available
make: *** <span class="token punctuation">[</span>install<span class="token punctuation">]</span> Error <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>解决方案：<br>  <code>yum install zlib* -y</code></p>
<h3 id="pip-安装依赖失败"><a href="#pip-安装依赖失败" class="headerlink" title="pip 安装依赖失败"></a>pip 安装依赖失败</h3><p>描述：<br>  <code>install --record /tmp/pip-f5erwx9h-record/install-record.txt --single-version-externally-managed --compile&quot; failed with error code 1 in /tmp/pip-build-34xxo8de/cairocffi/</code>  </p>
<p>解决方案：  </p>
  <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pip <span class="token function">install</span> <span class="token parameter variable">-U</span> setuptools
pip <span class="token function">install</span> <span class="token parameter variable">-U</span> wheel<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h2 id="如何迁移"><a href="#如何迁移" class="headerlink" title="如何迁移"></a>如何迁移</h2><ol>
<li>保留 <code>/root/.ehforwarderbot/profiles/default/blueset.telegram</code> 下的 <code>config.yaml</code> 和 <code>tgdata.db</code> 文件</li>
<li>保留 <code>/root/.ehforwarderbot/profiles/default/blueset.wechat</code> 下的 <code>wxpy_puid.pkl</code> 文件（这个文件存着对应微信好友的 UID，与 tgdata 的 chatassoc 表相对应）</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>efb</tag>
        <tag>ehforwarderbot</tag>
        <tag>telegram</tag>
        <tag>wechat</tag>
      </tags>
  </entry>
  <entry>
    <title>Rime 输入法指北</title>
    <url>/2020/10/how-to-use-rime/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>出于对国产输入法软件的不信任，再加上国产输入法广告太多等因素的影响，促使我想要尽快找到一个开源、快捷、不自动上传云端的输入法。</p>
<p>在网上搜索时，Rime 收到了一致好评，让我萌生了非常大的兴趣，故特意找到了 Rime 的相关资料并整理。</p>
<p>本文基于 <code>Rime v1.5.3</code> 版本进行整理，其他版本可能不适用。</p>
<span id="more"></span>

<h2 id="关于-Rime"><a href="#关于-Rime" class="headerlink" title="关于 Rime"></a>关于 Rime</h2><h3 id="什么是-Rime"><a href="#什么是-Rime" class="headerlink" title="什么是 Rime"></a>什么是 Rime</h3><p>Rime 全名是「中州韵输入法引擎」，Rime 不是一种输入法。是从各种常见键盘输入法中提炼出来的抽象的输入算法框架，由 <a href="https://github.com/lotem">佛振</a> 大佬开发。<br>Rime 通过各种输入方案来实现对几乎所有中文的支持，包括但不限于【拼音】、【注音】、【双拼】、【五笔】、【仓颉】，并且可以简单的进行【简繁】的切换</p>
<h3 id="什么是输入方案"><a href="#什么是输入方案" class="headerlink" title="什么是输入方案"></a>什么是输入方案</h3><p>Rime 由于本质只是一个框架，本身是不知道如何输入的。<br>如果我们想要使用 Rime，就得定义一些关于如何输入的设置来告诉 Rime，这些设置就是输入方案。</p>
<h3 id="Rime-的版本"><a href="#Rime-的版本" class="headerlink" title="Rime 的版本"></a>Rime 的版本</h3><p>Rime 是一个天生跨平台的框架，在每一个平台都有对应的发行版本：</p>
<ol>
<li>Linux：<ul>
<li>官方：<code>ibus-rime</code>（中州韵）</li>
<li>第三方：<code>fcitx-rime</code></li>
</ul>
</li>
<li>windowns：<ul>
<li>官方：<code>Weasel</code>（小狼毫）</li>
<li>第三方：<code>PRIME</code></li>
</ul>
</li>
<li>macOS：<ul>
<li>官方：<code>Squirrel</code>（鼠须管）</li>
<li>第三方：<code>XIME</code></li>
</ul>
</li>
<li>Android：<ul>
<li>第三方：<code>Trime</code>（同文）</li>
</ul>
</li>
<li>iOS：<ul>
<li>第三方：<code>iRime</code></li>
</ul>
</li>
</ol>
<p>上面是 Rime 在各平台对应的软件，Rime 默认提供了两个拼音输入方案「朙月拼音」和「地球拼音」，两者都可以输入准确的繁体和简体，而且「地球拼音」还支持声调输入。<br>Rime 还支持了许多种方言拼音，如吴语、粤语，甚至中古汉语。</p>
<h3 id="Rime-的文件架构"><a href="#Rime-的文件架构" class="headerlink" title="Rime 的文件架构"></a>Rime 的文件架构</h3><p>Rime 所有的配置文件、输入方案及词典文件，均是普通的文本文档，均要求 UTF8 编码。<br>其中配置文件、输入方案要求使用 <a href="https://translate.google.com/translate?hl=zh-CN&sl=en&u=https://yaml.org/&prev=search&pto=aue">yaml</a> 格式的文件。词典文件使用普通的 txt 文档即可。</p>
<p>Rime 中将文件分为 <code>共享文件夹</code> 和 <code>用户文件夹</code>。</p>
<ul>
<li><code>共享文件夹</code> 中存放的是 Rime 发行版的默认文件，一般是不允许进行修改的<ul>
<li>【中州韵 <code>ibus-rime</code>】 <code>/usr/share/rime-data/</code></li>
<li>【小狼毫 <code>Weasel</code>】 <code>安装目录\data</code></li>
<li>【鼠须管 <code>Squirrel</code>】 <code>/Library/Input Methods/Squirrel.app/Contents/SharedSupport/</code></li>
</ul>
</li>
<li><code>用户文件夹</code> 的存放位置<ul>
<li>【中州韵 <code>ibus-rime</code>】 <code>~/.config/ibus/rime/</code></li>
<li>【小狼毫 <code>Weasel</code>】 <code>%APPDATA%\Rime</code>，也可以通过“开始菜单＼小狼毫输入法＼用户文件夹”打开。</li>
<li>【鼠须管 <code>Squirrel</code>】 <code>~/Library/Rime/</code>，也可以通过“系统输入法菜单／鼠须管／用户设置”打开。</li>
</ul>
</li>
</ul>
<h4 id="用户文件夹"><a href="#用户文件夹" class="headerlink" title="用户文件夹"></a>用户文件夹</h4><p>刚开始安装后，在 <code>用户文件夹</code> 中是没有文件的，当我们运行 Rime 后会出现以下文件/文件夹：</p>
<ul>
<li><code>build/*</code>：这个文件夹下存放的是我们每次部署之后生成的静态文件<ul>
<li><code>&lt;输入方案名&gt;.prism.bin</code>：Rime 棱镜，拼写运算规则</li>
<li><code>&lt;词典名&gt;.table.bin</code>：Rime 固态词典，按音节编码检索词条的索引</li>
<li><code>&lt;词典名&gt;.reverse.bin</code>：Rime 反查词典，按词条检索编码的索引</li>
</ul>
</li>
<li><code>&lt;词典名&gt;.userdb/*</code>：这个文件夹是我们的词典文件夹，当我们进行了输入之后，会将输入的内容存入，以便进行词语调频</li>
<li><code>installation.yaml</code>：这个文件是关于我们本机 Rime 的安装时间、版本信息等</li>
<li><code>user.yaml</code>：这个文件是关于 Rime 的一些设置选项，比如我们上次选择的输入方案</li>
</ul>
<p>Rime 的配置文件都需要放于 <code>用户文件夹</code> 下，分为三类：</p>
<ul>
<li><code>default.yaml</code>：全局配置，存放跨输入方案的通用配置</li>
<li><code>&lt;输入方案名&gt;.schema.yaml</code>：存放某一个输入方案的配置<br>如：<code>double_pinyin_flypy.schema.yaml</code></li>
<li><code>&lt;发布版名&gt;.yaml</code>：存放某个发行版的独有配置<br>如：<code>symbols.yaml</code></li>
</ul>
<p>如果有其他额外文件，也需要放入 <code>用户文件夹</code> 下，才能被配置文件正确使用</p>
<h2 id="自定义-Rime-的全局配置"><a href="#自定义-Rime-的全局配置" class="headerlink" title="自定义 Rime 的全局配置"></a>自定义 Rime 的全局配置</h2><p><a href="https://gist.github.com/jiz4oh/7ae627841bd514720773c278b49c14a2#file-default-custom-yaml">我的全局配置 default.custom.yaml</a></p>
<h3 id="menu"><a href="#menu" class="headerlink" title="menu"></a>menu</h3><p>可配置项：</p>
<ul>
<li><code>page_size</code>：每页个数，默认 5，允许 1~9</li>
</ul>
<p>该项可在每个输入方案中单独设置</p>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">menu</span><span class="token punctuation">:</span>
  <span class="token key atrule">page_size</span><span class="token punctuation">:</span> <span class="token number">8</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="schema-list"><a href="#schema-list" class="headerlink" title="schema_list"></a>schema_list</h3><p>可配置项：</p>
<ul>
<li><code>schema</code>：每一项 <code>schema</code> 对应一项方案的 <code>schema_id</code></li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">schema_list</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> luna_pinyin
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> luna_pinyin_simp
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> luna_pinyin_fluency
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> bopomofo
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> bopomofo_tw
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> cangjie5
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> stroke
  <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> terra_pinyin<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="switcher"><a href="#switcher" class="headerlink" title="switcher"></a>switcher</h3><p>可配置项：</p>
<ul>
<li><code>caption</code>：切换器被调用时屏幕显示的名字</li>
<li><code>hotkeys</code>：切换器调用的快捷键</li>
<li><code>abbreviate_options</code>：</li>
<li><code>fold_options</code>：</li>
<li><code>option_list_separator</code>：</li>
<li><code>save_options</code>：</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">switcher</span><span class="token punctuation">:</span>
  <span class="token key atrule">abbreviate_options</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">caption</span><span class="token punctuation">:</span> <span class="token string">"〔方案選單〕"</span>
  <span class="token key atrule">fold_options</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token key atrule">hotkeys</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token string">"Alt+Shift+Control+grave"</span>
    <span class="token punctuation">-</span> <span class="token string">"Control+grave"</span>
  <span class="token key atrule">option_list_separator</span><span class="token punctuation">:</span> <span class="token string">"／"</span>
  <span class="token key atrule">save_options</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> full_shape
    <span class="token punctuation">-</span> ascii_punct
    <span class="token punctuation">-</span> simplification
    <span class="token punctuation">-</span> extended_charset
    <span class="token punctuation">-</span> zh_hant
    <span class="token punctuation">-</span> zh_hans
    <span class="token punctuation">-</span> zh_hant_tw<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="自定义-Rime-的输入法方案"><a href="#自定义-Rime-的输入法方案" class="headerlink" title="自定义 Rime 的输入法方案"></a>自定义 Rime 的输入法方案</h2><p>Rime 输入方案主要分为三部分</p>
<ul>
<li><code>schema</code>：关于整个输入方案的信息阐述</li>
<li><code>switches</code>：可以切换的开关，比如切换全半角，切换简繁体</li>
<li><code>engine</code>：Rime 的核心配置，配置整个 Rime 是如何进行运作</li>
</ul>
<!--more-->
<p>特殊说明：</p>
<p>输入方案的某些细项设置是可以写在全局设置中，比如</p>
<ul>
<li><code>key_binder</code></li>
<li><code>punctuator</code></li>
<li><code>ascii_composer</code></li>
<li><code>recognizer</code></li>
</ul>
<p>在输入方案中设置 <code>menu/page_size</code> 是可以的</p>
<h3 id="schema"><a href="#schema" class="headerlink" title="schema"></a>schema</h3><p>可配置项：</p>
<ul>
<li><code>name</code>：方案的显示名称〔即出现于方案选单中，通常为中文〕</li>
<li><code>schema_id</code>：方案内部名，在代码中引用此方案时的名称，通常由英文，数字，下划线组成</li>
<li><code>author</code>：方案作者。如果您对方案做出了修改，请保留原作者名，放入自己的名字加在后面</li>
<li><code>description</code>：简要描述方案历史，码表来源，该方案规则等</li>
<li><code>dependencies</code>：如果本方案依赖于其他方案〔通常来说会依赖其他方案做为反查，抑或是两种或多种方案混用时]</li>
<li><code>version</code>：版本号，在发布新版前请确保已升版本号</li>
</ul>
<p>示例：<code>小鹤双拼</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">schema</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"小鶴雙拼"</span>
  <span class="token key atrule">schema_id</span><span class="token punctuation">:</span> double_pinyin_flypy
  <span class="token key atrule">author</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token string">"double pinyin layout by 鶴"</span>
    <span class="token punctuation">-</span> <span class="token string">"Rime schema by 佛振 &lt;chen.sst@gmail.com>"</span>
  <span class="token key atrule">description</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
    朙月拼音＋小鶴雙拼方案。</span>
  <span class="token key atrule">dependencies</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> stroke
  <span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token number">0.18</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="switches"><a href="#switches" class="headerlink" title="switches"></a>switches</h3><ul>
<li><p><code>ascii_mode</code>：中英文切换开关。0 为中文，1 为英文</p>
</li>
<li><p><code>full_shape</code>：全角符号／半角符号开关。默认 0 为半角，1 为全角。注意，开启全角时英文字母亦为全角。</p>
</li>
<li><p><code>extended_charset</code>：字符集开关。0 为 CJK 基本字符集，1 为 CJK 全字符集。仅 <code>table_translator</code> 可用</p>
</li>
<li><p><code>ascii_punct</code>：中西文标点切换开关，0 为中文句读，1 为西文标点。</p>
</li>
<li><p><code>simplification</code>：是转化字开关。一般情況下与上同，0 为不开启转化，1 为转化。<br>  <code>simplification</code> 选项名称可自定义，亦可添加多套替換用字方案：</p>
  <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> zh_cn
  <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"漢字"</span><span class="token punctuation">,</span> <span class="token string">"汉字"</span><span class="token punctuation">]</span>
  <span class="token key atrule">reset</span><span class="token punctuation">:</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>  或</p>
  <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">options</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> zh_trad<span class="token punctuation">,</span> zh_cn<span class="token punctuation">,</span> zh_mars <span class="token punctuation">]</span>
  <span class="token key atrule">states</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> 字型 → 漢字
    <span class="token punctuation">-</span> 字型 → 汉字
    <span class="token punctuation">-</span> 字型 → 䕼茡
  <span class="token key atrule">reset</span><span class="token punctuation">:</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<ul>
<li><p><code>name/options</code>：須与 <code>engine</code> 中 <code>simplifier</code> 的 <code>option_name</code> 相同</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">switches</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> zh_simp
    <span class="token key atrule">reset</span><span class="token punctuation">:</span> <span class="token number">1</span>
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> 漢字<span class="token punctuation">,</span> 汉字 <span class="token punctuation">]</span>
<span class="token key atrule">simplifier</span><span class="token punctuation">:</span>
  <span class="token key atrule">option_name</span><span class="token punctuation">:</span> zh_simp<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p><code>states</code>：可不写，如不写则此开关存在但不可见，可由快捷鍵操作</p>
</li>
<li><p><code>reset</code>：设定开关默认状态〔若 reset 为空，则使用上次状态〕</p>
</li>
</ul>
</li>
<li><p>字符集过滤。此选项沒有默认名称，須配合 <code>charset_filter</code> 使用。可單用，亦可添加多套字符集</p>
</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">switches</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> ascii_mode
    <span class="token key atrule">reset</span><span class="token punctuation">:</span> <span class="token number">0</span>
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"中文"</span><span class="token punctuation">,</span> <span class="token string">"西文"</span><span class="token punctuation">]</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> full_shape
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"半角"</span><span class="token punctuation">,</span> <span class="token string">"全角"</span><span class="token punctuation">]</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> extended_charset
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"通用"</span><span class="token punctuation">,</span> <span class="token string">"增廣"</span><span class="token punctuation">]</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> simplification
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"漢字"</span><span class="token punctuation">,</span> <span class="token string">"汉字"</span><span class="token punctuation">]</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> ascii_punct
    <span class="token key atrule">states</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"句讀"</span><span class="token punctuation">,</span> <span class="token string">"符號"</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="engine"><a href="#engine" class="headerlink" title="engine"></a>engine</h3><p>Rime 的核心原理是通过 enagine 下的 4 大组件对用户输入进行处理，4 大组件分别是：</p>
<ul>
<li><code>Processors</code></li>
<li><code>Segmentors</code></li>
<li><code>Translators</code></li>
<li><code>Filters</code></li>
</ul>
<p>整个流程为：</p>
<ol>
<li><code>Processors</code> 下的各个 <code>processor</code> 对用户的输入（即按下键盘的哪一个键）依次进行处理，将按键按照预设的规则对按键进行响应<ul>
<li>不处理：Rime 不对该按键做任何响应，使用系统默认操作</li>
<li>特殊操作：比如 <code>Enter</code> 上屏，切换输入方案、组合键等</li>
<li>输入候选：该按键是需要转换为文字的按键，比如 <code>123abc</code>，将该按键字符存入【输入码】上下文</li>
</ul>
</li>
<li>当【输入码】上下文改变时，<code>Segmentors</code> 下的 <code>segmentor</code> 会将当前输入码根据格式分段，各自打上标签。比如【朙月拼音】中，输入码 <code>2012nian\</code>，划分为三个编码段：<code>2012</code>（贴 <code>number</code> 标签）、<code>nian</code>（贴 <code>abc</code> 标签）、<code>\</code>（贴 <code>punct</code> 标签）。</li>
<li>顾名思义，<code>Translators</code> 完成由编码到文字的翻译。但有几个要点：<ul>
<li>翻译的对象是划分好的一个代码段。</li>
<li>某个 <code>translator</code> 组件往往只翻译具有特定标签的代码段。</li>
<li>翻译的结果可能有多条，每条结果成为一个展现给用户的候选项。</li>
<li>代码段可由几种 <code>translator</code> 分别翻译、翻译结果按一定规则合并成一列候选。</li>
<li>候选项所对应的编码未必是整个代码段。用拼音敲一个词组时，词组后面继续列出单字候选，即是此例。</li>
</ul>
</li>
<li>翻译完成后，由 <code>Filters</code> 对所有翻译结果进行处理，比如去重</li>
</ol>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">engine</span><span class="token punctuation">:</span>
  <span class="token key atrule">processors</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> ascii_composer
    <span class="token punctuation">-</span> recognizer
    <span class="token punctuation">-</span> key_binder
    <span class="token punctuation">-</span> speller
    <span class="token punctuation">-</span> punctuator
    <span class="token punctuation">-</span> selector
    <span class="token punctuation">-</span> navigator
    <span class="token punctuation">-</span> express_editor
  <span class="token key atrule">segmentors</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> ascii_segmentor
    <span class="token punctuation">-</span> matcher
    <span class="token punctuation">-</span> abc_segmentor
    <span class="token punctuation">-</span> punct_segmentor
    <span class="token punctuation">-</span> fallback_segmentor
  <span class="token key atrule">translators</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> punct_translator
    <span class="token punctuation">-</span> script_translator
    <span class="token punctuation">-</span> <span class="token string">"table_translator@custom_phrase"</span>
  <span class="token key atrule">filters</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token string">"simplifier@emoji_suggestion"</span>
    <span class="token punctuation">-</span> <span class="token string">"simplifier@zh_simp"</span>
    <span class="token punctuation">-</span> uniquifier
    <span class="token punctuation">-</span> single_char_filter<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="Processors"><a href="#Processors" class="headerlink" title="Processors"></a>Processors</h4><ul>
<li><code>ascii_composer</code>：处理英文模式及中英文切换</li>
<li><code>recognizer</code>：与 <code>matcher</code> 搭配，处理符合特定规则的输入码，如网址，反查等</li>
<li><code>key_binder</code>：在特定条件下将按键绑定到其他按键，如重定义逗号，句号为预设翻页，开关快捷键等<br>  处理某些自定义的组合键</li>
<li><code>speller</code>：拼写处理器，接受字符按键，编辑输入<br>  处理自定义的键，通常为 26 个英文字母</li>
<li><code>punctuator</code>：句读处理器，将个别字符按键直接映射为标点符号或文字<br>  处理句读键、数字键</li>
<li><code>selector</code>：选字处理器<br>  处理数字键、上下方向键、<code>PageUp</code>、<code>PageDown</code></li>
<li><code>navigator</code>：处理输入栏内的光标移动<br>  处理左右方向键、<code>Home</code>、<code>End</code></li>
<li><code>express_editor</code>：编辑器<br>  处理空格、回车、回退键</li>
</ul>
<p>不常用：</p>
<ul>
<li><code>fluid_editor</code>：句式编辑器，用于以空格断开词，回车上屏的【注音】，【语句流】等输入方案，与 <code>express_editor</code> 互斥，也可以写作 <code>fluency_editor</code></li>
<li><code>chord_composer</code>：和弦作曲家或曰并击处理器，用于【宫保拼音】等多键并击的输入方案</li>
<li><code>lua_processor</code>：使用 lua 自定义按键，后接 <code>@lua函数名</code><ul>
<li>lua 函数名即用户文件夹内 rime.lua 中函数名，参数为(key, env)</li>
</ul>
</li>
</ul>
<h5 id="recognizer"><a href="#recognizer" class="headerlink" title="recognizer"></a>recognizer</h5><p>可配置项：</p>
<ul>
<li><code>import_preset</code>：从外部文件导入</li>
<li><code>patterns</code>：配合 <code>segmentor</code> 的 prefix 和 suffix 完成段落划分、tag 标记</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">recognizer</span><span class="token punctuation">:</span>
  <span class="token key atrule">import_preset</span><span class="token punctuation">:</span> default
  <span class="token key atrule">patterns</span><span class="token punctuation">:</span>
    <span class="token key atrule">code</span><span class="token punctuation">:</span> <span class="token string">"[a-zA-Z]+(*$"</span>
    <span class="token key atrule">email</span><span class="token punctuation">:</span> <span class="token string">"^[A-Za-z][-_.0-9A-Za-z]*@.*$"</span>
    <span class="token key atrule">html</span><span class="token punctuation">:</span> <span class="token string">"^&lt;[a-z]+>$"</span>
    <span class="token key atrule">punct</span><span class="token punctuation">:</span> <span class="token string">"^/([a-z]+|[0-9]0?)$"</span>
    <span class="token key atrule">uppercase</span><span class="token punctuation">:</span> <span class="token string">"[A-Z][-_+.'0-9A-Za-z]*$"</span>
    <span class="token key atrule">url</span><span class="token punctuation">:</span> <span class="token string">"^(www[.]|https?:|ftp[.:]|mailto:|file:).*$|^[a-z]&#123;1,10&#125;[.:_-].*$"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h5 id="key-binder"><a href="#key-binder" class="headerlink" title="key_binder"></a>key_binder</h5><p>可配置项：</p>
<ul>
<li><code>import_preset</code>：从外部文件导入</li>
<li><code>bindings</code>：设置组合键的映射<br>每一条 <code>binding</code> 中包含字段：<ul>
<li><code>accept</code>：键盘输入的按键</li>
<li><code>when</code>：作用条件</li>
<li><code>send</code>：实际调用的按键</li>
<li><code>toggle</code>：切换开关，和 <code>send</code> 不共存</li>
</ul>
</li>
</ul>
<p><code>accept</code> 和 <code>send</code> 作用的键，需要输入 Ibus 风格的键名：</p>
<p><strong>Mac 只支持 Alt/Option 键，不支持Command键，详见<a href="https://github.com/rime/squirrel/issues/62"></a></strong></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml">Shift_L		左Shift
Shift_R		右Shift
Control_L	左Ctrl
Control_R	右Ctrl
Alt_L		左Alt
Alt_R		右Alt
<span class="token comment"># 在 windows 是 windows 键</span>
Meta_L		左Meta
Meta_R		右Meta
Super_L		左Super
Super_R		右Super
Hyper_L		左Hyper
Hyper_R		右Hyper

BackSpace	退格
Tab			制表符
Caps_Lock	大写键
Linefeed	换行
Clear		清除
Return		回车
Pause		暂停
Escape		Esc退出
Delete		刪除
Home		Home
Left		左箭头
Up			上箭头
Right		右箭头
Down		下箭头
Prior		上翻
Page_Up		上翻
Next		下翻
Page_Down	下翻
space 		空格
Sys_Req	
End			末位
Begin		始位
Shift_Lock	上檔鎖
Scroll_Lock	滚动锁
Num_Lock	小键盘锁
Select		选择
Print		打印
Execute		执行
Insert		插入
Undo		还原
Redo		重做
Menu		菜单
Find		寻找
Cancel		取消
Help		帮助
Break		中断

grave			`
asciitilde		~
exclam			<span class="token tag">!</span>
at				@
numbersign		<span class="token comment">#</span>
dollar			$
percent			%
ampersand		&amp;
asciicircum		^
asterisk		*
parenleft		(
parenright		)
underscore		_
minus			<span class="token punctuation">-</span>
plus			+
equal			=
bracketleft		<span class="token punctuation">[</span>
bracketright	<span class="token punctuation">]</span>
braceleft		<span class="token punctuation">&#123;</span>
braceright		<span class="token punctuation">&#125;</span>
bar				<span class="token punctuation">|</span>
slash			/
backslash		\
semicolon		;
<span class="token key atrule">colon</span>			<span class="token punctuation">:</span>
apostrophe		'
quotedbl		"
comma			<span class="token punctuation">,</span>
period			.
less			&lt;
greater			<span class="token punctuation">></span>
question		<span class="token punctuation">?</span>

KP_Space		小键盘空格
KP_Tab			小键盘制表符
KP_Enter		小键盘回车
KP_Delete		小键盘刪除
KP_Home			小键盘原位
KP_Left			小键盘左箭头
KP_Up			小键盘上箭头
KP_Right		小键盘右箭头
KP_Down			小键盘下箭头
KP_Prior		小键盘上翻
KP_Page_Up		小键盘上翻
KP_Next			小键盘下翻
KP_Page_Down	小键盘下翻
KP_End			小键盘末位
KP_Begin		小键盘始位
KP_Insert		小键盘插入
KP_Equal		小键盘等于
KP_Multiply		小键盘乘号
KP_Add			小键盘加号
KP_Subtract		小键盘減号
KP_Divide		小键盘除号
KP_Decimal		小键盘小数点
KP_0			小键盘0
KP_1			小键盘1
KP_2			小键盘2
KP_3			小键盘3
KP_4			小键盘4
KP_5			小键盘5
KP_6			小键盘6
KP_7			小键盘7
KP_8			小键盘8
KP_9			小键盘9<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">key_binder</span><span class="token punctuation">:</span>
  <span class="token key atrule">bindings</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> minus<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Up<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> paging<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> equal<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Down<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> has_menu<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> bracketleft<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Up<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> paging<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> bracketright<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Down<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> has_menu<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> <span class="token number">9</span><span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Up<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> paging<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> Page_Down<span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> has_menu<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> semicolon<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> has_menu<span class="token punctuation">&#125;</span>
    <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span><span class="token key atrule">accept</span><span class="token punctuation">:</span> apostrophe<span class="token punctuation">,</span> <span class="token key atrule">send</span><span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> has_menu<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h5 id="speller"><a href="#speller" class="headerlink" title="speller"></a>speller</h5><p>可配置项：</p>
<ul>
<li><code>alphabet</code>：设置本 <code>speller</code> 需要监听的键</li>
<li><code>initials</code>：设置哪些键仅在开头的时候才需要监听</li>
<li><code>finals</code>：设置哪些键仅在末尾的时候才需要监听</li>
<li><code>delimiter</code>：分词符</li>
<li><code>algebra</code>：Rime 核心的拼写运算规则，所有 <code>algebra</code> 算出的规则最后写入 <code>prism</code></li>
<li><code>max_code_length</code>：行码最大码長，超过则自动顶字上屏〔<code>number</code>〕</li>
<li><code>auto_select</code>：是否开启自动上屏〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>auto_select_pattern</code>：自动上屏规则（正则），当输入码匹配正则时自动顶字上屏。</li>
<li><code>use_space</code>：空格是否可作为输入码〔<code>true</code> 或 <code>false</code>〕</li>
</ul>
<h5 id="punctuator"><a href="#punctuator" class="headerlink" title="punctuator"></a>punctuator</h5><p>可配置项：</p>
<ul>
<li><code>import_preset</code>：从外部文件导入</li>
<li><code>half_shape</code>：半角模式下的句读映射<br>每条选项可以设置上屏模式<ul>
<li>默认：选项模式</li>
<li><code>commit</code>：直接上屏</li>
<li><code>pair</code>：交替上屏</li>
</ul>
</li>
<li><code>full_shape</code>：全角模式下的句读映射<br>同 <code>half_shape</code> 可设置上屏模式</li>
<li><code>use_space</code>：是否使用空格顶字〔<code>true</code> 或 <code>false</code>〕</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">punctuator</span><span class="token punctuation">:</span>
  <span class="token comment"># 标点及特殊表情，引入 symbols 文件</span>
  <span class="token key atrule">import_preset</span><span class="token punctuation">:</span> symbols
  <span class="token comment"># 覆盖 symbols 文件对应 key</span>
  <span class="token key atrule">symbols</span><span class="token punctuation">:</span>
    <span class="token key atrule">"/dn"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>⌘<span class="token punctuation">,</span> ⌥<span class="token punctuation">,</span> ⇧<span class="token punctuation">,</span> ⌃<span class="token punctuation">,</span> ⎋<span class="token punctuation">,</span> ⇪<span class="token punctuation">,</span> <span class="token punctuation">,</span> ⌫<span class="token punctuation">,</span> ⌦<span class="token punctuation">,</span> ↩︎<span class="token punctuation">,</span> ⏎<span class="token punctuation">,</span> ↑<span class="token punctuation">,</span> ↓<span class="token punctuation">,</span> ←<span class="token punctuation">,</span> →<span class="token punctuation">,</span> ↖<span class="token punctuation">,</span> ↘<span class="token punctuation">,</span> ⇟<span class="token punctuation">,</span> ⇞<span class="token punctuation">]</span>
    <span class="token key atrule">"/fh"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> ©<span class="token punctuation">,</span> ®<span class="token punctuation">,</span> ℗<span class="token punctuation">,</span> ℠<span class="token punctuation">,</span> ™<span class="token punctuation">,</span> ℡<span class="token punctuation">,</span> ⓘ<span class="token punctuation">,</span> ♂<span class="token punctuation">,</span> ♀<span class="token punctuation">,</span> ☉<span class="token punctuation">,</span> ☊<span class="token punctuation">,</span> ☋<span class="token punctuation">,</span> ☌<span class="token punctuation">,</span> ☍<span class="token punctuation">,</span> ☐<span class="token punctuation">,</span> ☑︎<span class="token punctuation">,</span> ☒<span class="token punctuation">,</span> ☜<span class="token punctuation">,</span> ☝<span class="token punctuation">,</span> ☞<span class="token punctuation">,</span> ☟<span class="token punctuation">,</span> ✎<span class="token punctuation">,</span> ✄<span class="token punctuation">,</span> ♲<span class="token punctuation">,</span> ♻<span class="token punctuation">,</span> ⚐<span class="token punctuation">,</span> ⚑<span class="token punctuation">,</span> ⚠<span class="token punctuation">]</span>
    <span class="token key atrule">"/xh"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> ＊<span class="token punctuation">,</span> ×<span class="token punctuation">,</span> ✱<span class="token punctuation">,</span> ★<span class="token punctuation">,</span> ☆<span class="token punctuation">,</span> ✩<span class="token punctuation">,</span> ✧<span class="token punctuation">,</span> ❋<span class="token punctuation">,</span> ❊<span class="token punctuation">,</span> ❉<span class="token punctuation">,</span> ❈<span class="token punctuation">,</span> ❅<span class="token punctuation">,</span> ✿<span class="token punctuation">,</span> ✲<span class="token punctuation">]</span>
  <span class="token key atrule">half_shape</span><span class="token punctuation">:</span>
    <span class="token key atrule">"`"</span><span class="token punctuation">:</span> <span class="token string">"·"</span>
    <span class="token key atrule">"~"</span><span class="token punctuation">:</span> <span class="token string">"~"</span>
    <span class="token key atrule">"@"</span><span class="token punctuation">:</span> <span class="token string">"@"</span>
    <span class="token key atrule">"#"</span><span class="token punctuation">:</span> <span class="token string">"#"</span>
    <span class="token key atrule">"$"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"¥"</span><span class="token punctuation">,</span> <span class="token string">"$"</span><span class="token punctuation">,</span> <span class="token string">"€"</span><span class="token punctuation">,</span> <span class="token string">"£"</span><span class="token punctuation">,</span> <span class="token string">"¢"</span><span class="token punctuation">,</span> <span class="token string">"¤"</span><span class="token punctuation">]</span>
    <span class="token key atrule">"%"</span><span class="token punctuation">:</span> <span class="token string">"%"</span>
    <span class="token key atrule">"^"</span><span class="token punctuation">:</span> <span class="token string">"……"</span>
    <span class="token key atrule">"*"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"*"</span><span class="token punctuation">,</span> <span class="token string">"×"</span><span class="token punctuation">,</span> <span class="token string">"·"</span><span class="token punctuation">,</span> <span class="token string">"・"</span><span class="token punctuation">,</span> <span class="token string">"※"</span><span class="token punctuation">,</span> <span class="token string">"＊"</span><span class="token punctuation">,</span> <span class="token string">"❂"</span><span class="token punctuation">]</span>
    <span class="token key atrule">"_"</span><span class="token punctuation">:</span> <span class="token string">"——"</span>
    <span class="token key atrule">"="</span><span class="token punctuation">:</span> <span class="token string">"="</span>
    '\'<span class="token punctuation">:</span> <span class="token string">"、"</span>
    <span class="token key atrule">"'"</span><span class="token punctuation">:</span>
      <span class="token key atrule">pair</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token string">"‘"</span>
        <span class="token punctuation">-</span> <span class="token string">"’"</span>
    <span class="token key atrule">"|"</span><span class="token punctuation">:</span> <span class="token string">"|"</span>
    <span class="token key atrule">"("</span><span class="token punctuation">:</span> <span class="token string">"（"</span>
    <span class="token key atrule">")"</span><span class="token punctuation">:</span> <span class="token string">"）"</span>
    <span class="token key atrule">"["</span><span class="token punctuation">:</span> <span class="token string">"【"</span>
    <span class="token key atrule">"]"</span><span class="token punctuation">:</span> <span class="token string">"】"</span>
    <span class="token key atrule">"&#123;"</span><span class="token punctuation">:</span> <span class="token string">"「"</span>
    <span class="token key atrule">"&#125;"</span><span class="token punctuation">:</span> <span class="token string">"」"</span>
    <span class="token key atrule">"&lt;"</span><span class="token punctuation">:</span> <span class="token string">"《"</span>
    <span class="token key atrule">">"</span><span class="token punctuation">:</span> <span class="token string">"》"</span>
    <span class="token key atrule">"/"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"/"</span><span class="token punctuation">,</span> <span class="token string">"÷"</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="Segmentors"><a href="#Segmentors" class="headerlink" title="Segmentors"></a>Segmentors</h4><ul>
<li><code>ascii_segmentor</code>：标识英文段落〔例如在英文模式下〕字母直接上屛</li>
<li><code>matcher</code>：配合 <code>recognizer</code> 标识符合特定规则的段落，如网址，反查等，加上特定 tag</li>
<li><code>abc_segmentor</code>：标识常规的文字段落，加上 <code>abc</code> 这个默认tag</li>
<li><code>punct_segmentor</code>：标识句读段落〔键入标点符号用〕加上 punct 这个tag</li>
<li><code>fallback_segmentor</code>：标识其他未标识段落</li>
<li><code>affix_segmentor</code>：用户自定义 tag<ul>
<li>可加载多个实例，后接 <code>@tag名</code></li>
</ul>
</li>
</ul>
<p>不常用：</p>
<ul>
<li><code>lua_segmentor</code> 使用lua自定义切分，后接 <code>@lua函数名</code></li>
</ul>
<h4 id="Translators"><a href="#Translators" class="headerlink" title="Translators"></a>Translators</h4><ul>
<li><code>table_translator</code>：编码表翻译器，用于仓颉，五笔等基于<strong>编码表</strong>的输入方案<ul>
<li>可加载多个实例，后接 <code>@翻译器名</code>〔如：cangjie，wubi等〕</li>
</ul>
</li>
<li><code>script_translator</code>：脚本翻译器，用于拼音，粤拼等基于<strong>音节表</strong>的输入方案<ul>
<li>可加载多个实例，后接 <code>@翻译器名</code>〔如：pinyin，jyutping等〕</li>
</ul>
</li>
<li><code>punct_translator</code>：配合 <code>punct_segmentor</code> 转换标点符号</li>
<li><code>echo_translator</code>：没有其他候选字时，显示输入码〔输入码可以 Shift+Enter 上屛〕</li>
</ul>
<p>不常用：</p>
<ul>
<li><code>reverse_lookup_translator</code>：反查翻译器，用另一种种编码方案查码</li>
<li><code>lua_translator</code>：使用 lua 自定义输入，例如动态输入当前日期，时间，后接 <code>@lua函数名</code><ul>
<li>lua 函数名即用户文件夹内 rime.lua 中函数名，参数为(input, seg, env)</li>
<li>可以 env.engine.context:get_option(“option_name”)方式绑定到 switch 开关／key_binder 快捷键</li>
</ul>
</li>
</ul>
<h5 id="translator"><a href="#translator" class="headerlink" title="translator"></a>translator</h5><p>每个输入方案有一个关于 <code>translator</code> 的全局设置，可设置项：</p>
<ul>
<li><p><code>dictionary</code>：翻译器使用的字典名</p>
</li>
<li><p><code>prism</code>：设定此翻译器的 <code>speller</code> 生成的棱镜文件名，或此副编译器调用的棱镜名</p>
</li>
<li><p><code>user_dict</code>：设定用户词典名</p>
</li>
<li><p><code>db_class</code>：设定用户词典类型，可设 <code>tabledb</code>〔文本〕或 <code>userdb</code>〔二进制〕</p>
</li>
<li><p><code>preedit_format</code>：上屛码自定义</p>
</li>
<li><p><code>comment_format</code>：提示码自定义</p>
</li>
<li><p><code>initial_quality</code>：设定此翻译器结果优先级</p>
</li>
<li><p><code>disable_user_dict_for_patterns</code>：禁止某些编码录入用户词典〔<code>true</code> 或 <code>false</code>〕</p>
</li>
<li><p><code>enable_sentence</code>：是否开启自动造句〔<code>true</code> 或 <code>false</code>〕</p>
</li>
<li><p><code>enable_user_dict</code>：是否开启用户词典〔用户词典记录动态字词频，用户词〕〔<code>true</code> 或 <code>false</code>〕</p>
</li>
</ul>
<p>仅 <code>table_translator</code> 生效：</p>
<ul>
<li><code>enable_charset_filter</code>：是否开启字符集过滤〔<code>cjk_minifier</code> 启用后可适用于 <code>script_translator</code>〕〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>enable_encoder</code>：是否开启自动造词〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>encode_commit_history</code>：是否对已上屛词自动成词〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>max_phrase_length</code>：最大自动成词词长〔<code>number</code>〕</li>
<li><code>enable_completion</code>：提前显示尚未输入完整码的字〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>sentence_over_completion</code>：在无全码对应字而仅有逐键提示时也开启智能组句〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>strict_spelling</code>：配合 speller 中的 fuzz 规则，仅以畧拼码组词〔<code>true</code> 或 <code>false</code>〕</li>
</ul>
<p>仅 <code>script_translator</code> 生效</p>
<ul>
<li><code>spelling_hints</code>：设定多少字以内部预定标注完整带调拼音</li>
</ul>
<p>除了 <code>translator</code> 项设置，其他通过 <code>@</code> 定义的副翻译器还可单独设置：</p>
<ul>
<li><code>tag</code>：设定此翻译器针对的 tag。默认 abc</li>
<li><code>prefix</code>：设定此翻译器的前缀标识，默认无</li>
<li><code>suffix</code>：设定此翻译器的后缀标识，默认无</li>
<li><code>tips</code>：设定此翻译器的输入前提示符，默认无</li>
<li><code>closing_tips</code>：设定此翻译器的输入结束提示符，默认无</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># tarnslator</span>
<span class="token key atrule">translator</span><span class="token punctuation">:</span>
  <span class="token key atrule">dictionary</span><span class="token punctuation">:</span> luna_pinyin
  <span class="token key atrule">prism</span><span class="token punctuation">:</span> luna_pinyin_simp
  <span class="token key atrule">preedit_format</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> xform/(<span class="token punctuation">[</span>nl<span class="token punctuation">]</span>)v/$1ü/
    <span class="token punctuation">-</span> xform/(<span class="token punctuation">[</span>nl<span class="token punctuation">]</span>)ue/$1üe/
    <span class="token punctuation">-</span> xform/(<span class="token punctuation">[</span>jqxy<span class="token punctuation">]</span>)v/$1u/

<span class="token comment"># 副翻译器设置</span>
<span class="token key atrule">custom_phrase</span><span class="token punctuation">:</span> <span class="token comment"># 這是一個 table_translator</span>
  <span class="token key atrule">dictionary</span><span class="token punctuation">:</span> <span class="token string">""</span>
  <span class="token key atrule">user_dict</span><span class="token punctuation">:</span> custom_phrase
  <span class="token key atrule">db_class</span><span class="token punctuation">:</span> tabledb
  <span class="token key atrule">enable_sentence</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
  <span class="token key atrule">enable_completion</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
  <span class="token key atrule">initial_quality</span><span class="token punctuation">:</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h5 id="词典与码表"><a href="#词典与码表" class="headerlink" title="词典与码表"></a>词典与码表</h5><p><code>translator</code> 通过词典来翻译对应的片段，通常命名为 <code>&lt;词典名&gt;.dict.yaml</code></p>
<p><strong>词典</strong>：</p>
<ul>
<li>name：词典名，内部使用，可以与配套的输入方案名一致，也可不同；</li>
<li>version：管理词典的版本；</li>
<li>sort：词条初始排序方式，〔<code>by_weight</code>（按词频高低排序）或 <code>original</code>（保持原码表中的顺序）〕；</li>
<li>use_preset_vocabulary：选择是否导入默认词汇表【八股文】〔<code>true</code> 或 <code>false</code>〕。</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># 这里以 --- ... 分別标记出 YAML 文件的起始与结束位置</span>
<span class="token punctuation">---</span>
<span class="token key atrule">name</span><span class="token punctuation">:</span> luna_pinyin
<span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"0.9"</span>
<span class="token key atrule">sort</span><span class="token punctuation">:</span> by_weight
<span class="token key atrule">use_preset_vocabulary</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
<span class="token comment"># 在 ... 标记之后的部分就不会作为 YAML 文件來解析</span>
<span class="token punctuation">...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><strong>码表</strong>，定义编码与文字的映射关系，通过制表符分割为三列：</p>
<ul>
<li>文字</li>
<li>编码，如果该编码有多个音节，各音节以空格分开</li>
<li>权重，相同编码时出现在候选列表前面的几率</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml">你	ni
我	wo
的	de	99%
的	di	1%
地	de	10%
地	di	90%
目	mu
好	hao

你我
你的
我的
我的天
天地	tian di
好天
好好地
目的	mu di
目的地	mu di di<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="Filters"><a href="#Filters" class="headerlink" title="Filters"></a>Filters</h4><ul>
<li><code>simplifier</code>：简繁转换，表情转换等<ul>
<li>可加载多个实例，后接 <code>@d转化器名</code>〔如：zh_simp、emoji_suggestion 等〕</li>
</ul>
</li>
<li><code>uniquifier</code>：过滤重复的候选字，有可能来自 <code>simplifier</code></li>
<li><code>reverse_lookup_filter</code>：反查滤镜，以更灵活的方式反查，Rime1.0 后替代 <code>reverse_lookup_translator</code><ul>
<li>可加载多个实例，后接 <code>@滤镜名</code>〔如：pinyin_lookup，jyutping_lookup 等〕</li>
</ul>
</li>
<li><code>charset_filter</code> 字符集过滤<ul>
<li>后接 <code>@字符集名</code>〔如：utf-8（无过滤），big5，big5hkscs，gbk，gb2312〕</li>
</ul>
</li>
<li><code>lua_filter</code>：使用 lua 自定义过滤，例如过滤字符集，调整排序，后接 <code>@lua函数名</code><ul>
<li>lua函数名即用户文件夹内rime.lua中函数名，参数为(input, env)</li>
<li>可以env.engine.context:get_option(“option_name”)方式绑定到switch开关／key_binder快捷键</li>
</ul>
</li>
</ul>
<p>仅 <code>script_translator</code>：</p>
<ul>
<li><code>cjk_minifier</code>：字符集过滤，使之支持 <code>extended_charset</code> 开关</li>
</ul>
<p>仅 <code>table_translator</code>：</p>
<ul>
<li><code>single_char_filter</code>：单字过滤器，如加载此组件，则屛敝词典中的词组</li>
</ul>
<h3 id="使用小鹤双拼"><a href="#使用小鹤双拼" class="headerlink" title="使用小鹤双拼"></a>使用小鹤双拼</h3><p>小鹤双拼原始配置文件：<a href="https://github.com/jiz4oh/backups/blob/master/double_pinyin_flypy.schema.yaml">double_pinyin_flypy.schema.yaml</a></p>
<p><a href="https://gist.github.com/jiz4oh/7ae627841bd514720773c278b49c14a2#file-double_pinyin_flypy-custom-yaml">我的小鹤双拼配置 double_pinyin_flypy.schema.yaml</a></p>
<h2 id="Mac-上如何使用-Squirrel"><a href="#Mac-上如何使用-Squirrel" class="headerlink" title="Mac 上如何使用 Squirrel"></a>Mac 上如何使用 Squirrel</h2><p>在 mac 上通常使用 homebrew 的方式安装 app</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew cask <span class="token function">install</span> squirrel<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>如果没有 homebrew，<a href="https://rime.im/download/">点击这里</a></p>
<h3 id="Squirrel-设置项"><a href="#Squirrel-设置项" class="headerlink" title="Squirrel 设置项"></a>Squirrel 设置项</h3><p>Squirrel 配置项基于 <code>0.14.0</code> 整理</p>
<p><a href="https://gist.github.com/jiz4oh/7ae627841bd514720773c278b49c14a2#file-squirrel-custom-yaml">我的 suirrel 配置 squirrel.custom.yaml</a></p>
<h4 id="特定程序操作-options"><a href="#特定程序操作-options" class="headerlink" title="特定程序操作 options"></a>特定程序操作 options</h4><p>可配置项：</p>
<ul>
<li><code>ascii_mode</code>：是否使用英文〔<code>true</code> 或 <code>false</code>〕</li>
<li><code>vim_mode</code>：<code>config_version 0.34.0</code> 启用，是否支持使用 ESC 键退出编辑模式并切换为英文〔<code>true</code> 或 <code>false</code>〕</li>
</ul>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">options</span><span class="token punctuation">:</span>
  <span class="token key atrule">com.apple.Xcode</span><span class="token punctuation">:</span>
    <span class="token key atrule">ascii_mode</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h4 id="可视化的自定义皮肤"><a href="#可视化的自定义皮肤" class="headerlink" title="可视化的自定义皮肤"></a>可视化的自定义皮肤</h4><p><a href="https://gjrobert.github.io/Rime-See-Me-squirrel/">可视化编辑器</a></p>
<h2 id="Android-上如何使用-Trime"><a href="#Android-上如何使用-Trime" class="headerlink" title="Android 上如何使用 Trime"></a>Android 上如何使用 Trime</h2><p><a href="https://github.com/osfans/trime/releases/download/3.1.3/trime-3.1.3-20190930.apk">正式版，点击下载</a></p>
<p><a href="https://github.com/osfans/trime/raw/gh-pages/release/app-release.apk">测试版，点击下载</a></p>
<p><a href="https://gist.github.com/jiz4oh/7ae627841bd514720773c278b49c14a2#file-jiz4oh-trime-yaml">我的 Trime 同文配置 jiz4oh.trime.yaml</a></p>
<h3 id="基础设置"><a href="#基础设置" class="headerlink" title="基础设置"></a>基础设置</h3><ol>
<li><p>启用输入法<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201004221229.png" alt="20201004221229"></p>
</li>
<li><p>将用户文件夹指向自定义配置文件所在位置<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201004221441.png" alt="20201004221441"></p>
</li>
<li><p>部署<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201004221538.png" alt="20201004221538"></p>
</li>
<li><p>使用自定义主题<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201004222056.png" alt="20201004222056"></p>
</li>
</ol>
<h3 id="其他设置"><a href="#其他设置" class="headerlink" title="其他设置"></a>其他设置</h3><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/telegram-cloud-photo-size-5-6327933625852603060-y.jpg" alt="telegram-cloud-photo-size-5-6327933625852603060-y"></p>
<h2 id="Rime-使用心得"><a href="#Rime-使用心得" class="headerlink" title="Rime 使用心得"></a>Rime 使用心得</h2><h3 id="重点（必看！）"><a href="#重点（必看！）" class="headerlink" title="重点（必看！）"></a>重点（必看！）</h3><ol>
<li><p>很多人修改了方案不生效，或者新安装了 Rime，却发现无法使用，一个很重要但很容易被忽视的解决方案是：</p>
<p> <strong>修改方案后，一定要点击部署！！！</strong><br> <strong>修改方案后，一定要点击部署！！！</strong><br> <strong>修改方案后，一定要点击部署！！！</strong></p>
<p> <a href="https://github.com/rime/home/wiki/RimeWithSchemata#%E4%BD%88%E7%BD%B2-rime">如何部署？点击这里</a></p>
<ul>
<li>【小狼毫】从开始菜单选择「重新部署」；或当开启托盘图标时，在托盘图标上右键选择「重新布署」；</li>
<li>【鼠须管】在系统语言文字选单中选择「重新布署」；</li>
<li>【中州韵】点击输入法状态栏（或IBus菜单）上的⟲ (Deploy) 按钮</li>
</ul>
</li>
<li><p>修改方案时，强烈建议使用 <strong><code>&lt;输入方案名&gt;.custom.yaml</code></strong> 的方式进行，除非这个方案是你自己原创！</p>
</li>
<li><p>使用 <code>&lt;输入方案名&gt;.custom.yaml</code> 修改方案时，一定要写 <strong><code>patch</code></strong> ！</p>
</li>
</ol>
<h3 id="如何切换-Rime-输入方案"><a href="#如何切换-Rime-输入方案" class="headerlink" title="如何切换 Rime 输入方案"></a>如何切换 Rime 输入方案</h3><p>比如如果需要更换输入方案-小鹤双拼，那就需要创建 <code>default.custom.yaml</code> 文件，写入以下内容</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># patch 一定要写</span>
<span class="token key atrule">patch</span><span class="token punctuation">:</span>
  <span class="token key atrule">schema_list</span><span class="token punctuation">:</span>
    <span class="token comment"># 默认的明月拼音</span>
    <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> luna_pinyin_simp
    <span class="token comment"># 额外添加的小鹤双拼，前提是需要存在 double_pinyin_flypy.schema.yaml 文件，不然的话会导致无法输入字符</span>
    <span class="token punctuation">-</span> <span class="token key atrule">schema</span><span class="token punctuation">:</span> double_pinyin_flypy<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="如何修改-Rime-输入方案的配置"><a href="#如何修改-Rime-输入方案的配置" class="headerlink" title="如何修改 Rime 输入方案的配置"></a>如何修改 Rime 输入方案的配置</h3><ol>
<li><p>如果我们对默认的明月拼音方案的一些设置不满意，我们需要创建一个 <code>luna_pinyin_simp.custom.yaml</code> 文件。</p>
<p> Q：为什么我要用 <code>luna_pinyin_simp.custom.yaml</code>，而不是 <code>luna_pinyin_simp.schema.yaml</code>，我听说 <code>&lt;输入方案名&gt;.schema.yaml</code> 才是输入方案的正确命名。<br> A：<code>&lt;输入方案名&gt;.schema.yaml</code> 确实是输入方案的正确命名方式。<br> A：我们使用 <code>&lt;输入方案名&gt;.custom.yaml</code> 是因为原始的 <code>&lt;输入方案名&gt;.schema.yaml</code> 方案（比如 <code>luna_pinyin_simp.schema.yaml</code>）是由他人所编写<br> A：我们<strong>不使用</strong> <code>&lt;输入方案名&gt;.custom.yaml</code> 而是直接修改 <code>&lt;输入方案名&gt;.schema.yaml</code> 文件进行自定义，如果作者对其进行了一些更新，我们会出现两种情况：</p>
<ol>
<li>跟随原作者更新了 <code>&lt;输入方案名&gt;.schema.yaml</code> 文件，我们之前所做的自定义全部白费</li>
<li>不跟随更新，失去原作者新增或修复的功能</li>
</ol>
</li>
<li><p>在 <code>luna_pinyin_simp.custom.yaml</code> 中修改</p>
 <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># 重点！custom.yaml 必须写 patch，且在第一行并只有一个</span>
<span class="token key atrule">patch</span><span class="token punctuation">:</span>
  <span class="token comment"># 需要覆写的设置项</span>
  <span class="token key atrule">xxxx</span><span class="token punctuation">:</span> xxxx<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

</li>
</ol>
<p>一个高度定制化的 Rime 配置文件结构示例：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201003122209.png" alt="20201003122209"></p>
<ul>
<li><code>opencc/*</code>：<a href="https://github.com/BYVoid/OpenCC">OpenCC</a>字形转换配置及字典文件，简繁转换，emoji 转换等</li>
<li><code>custom_phrase.txt</code>：自定义的词典</li>
<li><code>default.custom.yaml</code>：自定义的全局设置</li>
<li><code>double_pinyin_flypy.custom.yaml</code>：自定义的小鹤双拼输入方案设置</li>
<li><code>double_pinyin_flypy.schema.yaml</code>：从网上下载的小鹤双拼原始输入方案</li>
<li><code>luna_pinyin_simp.custom.yaml</code>：自定义的明月拼音输入方案设置</li>
<li><code>pinyin_simp.dict.yaml</code>：网上下载的默认词库文件</li>
<li><code>squirrel.custom.yaml</code>：自定义的 Squirrel 设置</li>
<li><code>symbols.yaml</code>：额外的关于表情符号的输入配置</li>
</ul>
<h3 id="如何删除自造词"><a href="#如何删除自造词" class="headerlink" title="如何删除自造词"></a>如何删除自造词</h3><ol>
<li>打错字后，立刻删掉是不会录入词库的。Rime 是在有新词输入时才把之前的词录入词库</li>
<li>选中已造词，使用 <code>Shift + Delete</code> 即可删除</li>
<li>mbp 因为移除了 <code>Delete</code> 键，使用 <code>Shift + Fn + Backspaces</code> 键删除</li>
</ol>
<h3 id="如何同步-Rime-词库"><a href="#如何同步-Rime-词库" class="headerlink" title="如何同步 Rime 词库"></a>如何同步 Rime 词库</h3><h4 id="同步原理"><a href="#同步原理" class="headerlink" title="同步原理"></a>同步原理</h4><ol>
<li>点击同步按钮</li>
<li>从 <code>installation.yaml</code> 中获取<ul>
<li><code>installation_id</code></li>
<li><code>sync_dir</code></li>
</ul>
</li>
<li>在 <code>sync_dir</code> 文件夹下生成 <code>installation_id</code> 文件夹</li>
<li>Rime 会将 <code>用户文件夹</code> 下所有文件写入到<strong>步骤 3</strong>中文件夹</li>
<li>根据 <code>&lt;词典名&gt;.userdb/*</code> 下的词典文件，生成一个<strong>词典快照文件</strong> <code>&lt;词典名&gt;.userdb.txt</code></li>
<li>将快照文件内容与 <code>sync</code> 文件夹下<em>其他文件夹</em>的<strong>同名快照文件</strong>进行对比，更新当前<strong>步骤 5</strong>中词典文件</li>
<li>将<strong>更新后</strong>的快照文件放入<strong>步骤 3</strong>中文件夹</li>
</ol>
<p>所以重点是设置 <code>installation.yaml</code> 中的 <code>installation_id</code> 和 <code>sync_dir</code></p>
<p><code>installation_id</code> 默认为随机生成的 UUID<br><code>sync_dir</code> 默认为 <code>用户文件夹</code> 下的 <code>sync</code> 文件夹  </p>
<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">distribution_code_name</span><span class="token punctuation">:</span> Squirrel
<span class="token key atrule">distribution_name</span><span class="token punctuation">:</span> <span class="token string">"鼠鬚管"</span>
<span class="token key atrule">distribution_version</span><span class="token punctuation">:</span> 0.14.0
<span class="token key atrule">install_time</span><span class="token punctuation">:</span> <span class="token string">"Tue Apr 28 22:33:50 2020"</span>
<span class="token key atrule">rime_version</span><span class="token punctuation">:</span> 1.5.3
<span class="token comment"># 上面几项由 Rime 维护</span>

<span class="token key atrule">installation_id</span><span class="token punctuation">:</span> <span class="token string">"nuc8_mac"</span>
<span class="token key atrule">sync_dir</span><span class="token punctuation">:</span> <span class="token string">"/Users/jiz4oh/OneDrive/RimeSync"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="同步进阶"><a href="#同步进阶" class="headerlink" title="同步进阶"></a>同步进阶</h4><p>知道原理之后，我们可以通过云盘来同步不同设备的词库及设置  </p>
<p>比如使用 OneDrive</p>
<ol>
<li>在 OneDrive 中设置 <code>RimeSync</code> 文件夹</li>
<li>将设备 A 的 <code>sync_dir</code> 指向 OneDrive 下 <code>RimeSync</code></li>
<li>将设备 B 的 <code>sync_dir</code> 指向 OneDrive 下 <code>RimeSync</code></li>
</ol>
<p>这样就是实现了 A 和 B 的的词库同步</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>注：本文由 Rime 鼠须管【小鹤双拼】输入方案撰写</p>
<ol>
<li><p><a href="https://rime.im/">RIME 官网</a></p>
</li>
<li><p><a href="https://github.com/rime/home/wiki/CustomizationGuide">Rime 定製指南</a></p>
</li>
<li><p><a href="https://github.com/LEOYoon-Tsaw/Rime_collections/blob/master/Rime_description.md">Schema.yaml 詳解</a></p>
</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>rime</tag>
      </tags>
  </entry>
  <entry>
    <title>HTTP 中的缓存机制</title>
    <url>/2020/11/http-cache/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在思考如何优化 web 应用的响应速度，常见的方式就是使用缓存，而最常接触的技术其实是服务端做共享缓存，例如 redis 缓存热点数据、CDN 缓存静态资源。在客户端做缓存这一块，知识面还比较窄，经过资料查询和思考，总结一些心得</p>
<span id="more"></span>

<h2 id="缓存的目的"><a href="#缓存的目的" class="headerlink" title="缓存的目的"></a>缓存的目的</h2><blockquote>
<p>缓存是一种保存资源副本并在下次请求时直接使用该副本的技术</p>
</blockquote>
<p>在不考虑成本的情况下，不使用缓存其实是体验最好方式，可以通过无限堆服务器来达到快速响应的目的，并且不会有一致性等问题。</p>
<p>所以缓存的目的很简单，就是缓解服务器压力，来达到在有限的成本情况下相对最优质的用户体验。</p>
<h2 id="私有缓存和共享缓存"><a href="#私有缓存和共享缓存" class="headerlink" title="私有缓存和共享缓存"></a>私有缓存和共享缓存</h2><p>缓存的种类大致可分为：</p>
<ul>
<li>共享缓存：可被多个用户重复使用，例如 redis 缓存热点数据，CDN 缓存静态资源</li>
<li>私有缓存：只能被单个用户使用，例如浏览器缓存</li>
</ul>
<h2 id="缓存控制"><a href="#缓存控制" class="headerlink" title="缓存控制"></a>缓存控制</h2><h3 id="HTTP-header-Cache-control"><a href="#HTTP-header-Cache-control" class="headerlink" title="HTTP header: Cache-control"></a>HTTP header: Cache-control</h3><p>在 <code>HTTP/1.1</code> 中引入 <code>Cache-control</code> 头用来定义浏览器应该如何进行缓存决策</p>
<p><code>Cache-control</code> 的值有：</p>
<ul>
<li><code>no-store</code>：客户端不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容</li>
<li><code>no-cache</code>：客户端会缓存响应内容，但每次请求都会携带 <strong>验证字段</strong> 到服务器，验证缓存是否过期，未过期才使用本地已缓存内容</li>
<li><code>public</code>：响应内容可以被中间人如 CDN、网关等缓存</li>
<li><code>private</code>：响应内容只能被客户端缓存</li>
<li><code>max-age</code>：缓存过期的相对时间（秒），超过该时间后会再次发起请求</li>
<li><code>must-revalidate</code>：本地缓存未过期时使用本地缓存，本地缓存过期时必须携带 <strong>验证字段</strong> 到服务器，验证缓存是否过期</li>
</ul>
<h4 id="HTTP-header-Expries"><a href="#HTTP-header-Expries" class="headerlink" title="HTTP header: Expries"></a>HTTP header: Expries</h4><p><code>Expries</code> 是 <code>HTTP/1.0</code> 中的内容，作用是指定缓存过期的绝对时间，例如 <code>Expires: Wed, 21 Oct 2015 07:28:00 GMT</code></p>
<p>和 <code>Cache-control: max-age</code> 的区别：</p>
<ul>
<li><code>Expries</code> 是绝对时间，<code>Cache-control</code> 是相对时间</li>
<li>在 <code>HTTP/1.0</code> 中 <code>Expries</code> 优先级大于 <code>Cache-control</code>，在 <code>HTTP/1.1</code> 及以后 <code>Cache-control</code> 优先级大于 <code>Expries</code></li>
</ul>
<h2 id="缓存决策"><a href="#缓存决策" class="headerlink" title="缓存决策"></a>缓存决策</h2><h3 id="强制缓存"><a href="#强制缓存" class="headerlink" title="强制缓存"></a>强制缓存</h3><p>由 <code>Cache-control: max-age</code> 或 <code>Expires</code> 指定的缓存，在时间过期之前，浏览器不会再次请求服务器</p>
<h3 id="协商缓存"><a href="#协商缓存" class="headerlink" title="协商缓存"></a>协商缓存</h3><p>当缓存失效时，会发出 <strong>条件式请求</strong>，这时候和服务器的通信就称为协商缓存  </p>
<p>协商缓存的优点：</p>
<ul>
<li>省去了服务器生成 html 的时间</li>
<li>省去了响应体内容，传输更快</li>
</ul>
<h4 id="Last-Modified"><a href="#Last-Modified" class="headerlink" title="Last-Modified"></a>Last-Modified</h4><p>请求响应流程：</p>
<ol>
<li>当浏览器第一次请求服务器时，服务器会返回 <code>Last-Modified</code> 头部表明当前资源的最后修改日期</li>
<li>浏览器会将这个日期值缓存在本地</li>
<li>当需要发出 <code>条件请求</code> 时，将日期值放在 <code>If-Modified-Since</code> 或 <code>If-Unmodified-Since</code> 头部中向服务器发出</li>
<li>服务器判断日期，决定响应内容</li>
<li>如果该资源新鲜，则返回 <code>304</code>，客户端使用该缓存渲染内容</li>
<li>如果该资源被更改过，则返回 <code>200</code>，及全新内容</li>
</ol>
<h4 id="Etag"><a href="#Etag" class="headerlink" title="Etag"></a>Etag</h4><p>和 <code>Last-Modified</code> 的请求响应流程类似，不同的是服务器响应的是 <code>ETag</code> 头，浏览器发出请求时的头部为 <code>If-Match</code> 和 <code>If-None-Match</code></p>
<ul>
<li>强验证：HTTP 协议默认使用强验证，强验证要求文档的每一个字节都必须相同<br><code>ETag: &quot;618bbc92e2d35ea1945008b42799b0e7&quot;</code></li>
<li>弱验证：<code>ETag</code> 头以 <code>W/</code> 开头时为弱验证，此时只要求文档的内容含义是相同的。<br><code>ETag: W/&quot;618bbc92e2d35ea1945008b42799b0e7&quot;</code></li>
</ul>
<p>因为存在当资源时间更改，但是内容未改变等情况，<code>Last-Modified</code> 对资源的判断没有 <code>ETag</code> 准确，所以服务端处理的时候通常优先判断 <code>ETag</code></p>
<h4 id="决策图"><a href="#决策图" class="headerlink" title="决策图"></a>决策图</h4><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201122225249.png" alt="决策流程图"></p>
<h2 id="刷新页面对-http-缓存的影响"><a href="#刷新页面对-http-缓存的影响" class="headerlink" title="刷新页面对 http 缓存的影响"></a>刷新页面对 http 缓存的影响</h2><p>三种刷新操作：</p>
<ul>
<li>正常操作：地址栏输入 url，跳转链接，前进后退等</li>
<li>手动刷新：F5（mac 为 Cmd + R），点击刷新按钮，点击菜单刷新</li>
<li>强制刷新：Ctrl + F5（mac 为 Cmd + Shift + R）</li>
</ul>
<p>不同刷新操作，不同的缓存策略：</p>
<ul>
<li>正常操作：强制缓存有效，协商缓存有效</li>
<li>手动刷新：强制缓存失效，协商缓存有效</li>
<li>强制刷新：强制缓存失效，协商缓存失效</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://wiki.developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ">HTTP 缓存</a></p>
<p><a href="https://juejin.cn/post/6844903704194203656?utm_source=gold_browser_extension?utm_source=gold_browser_extension">浅谈 HTTP 缓存</a></p>
]]></content>
      <categories>
        <category>网络</category>
      </categories>
      <tags>
        <tag>study</tag>
      </tags>
  </entry>
  <entry>
    <title>NUC8i5BEH 黑苹果安装记录</title>
    <url>/2020/09/install-hackintosh-to-nuc8i5beh/</url>
    <content><![CDATA[<h2 id="安装-mac"><a href="#安装-mac" class="headerlink" title="安装 mac"></a>安装 mac</h2><span id="more"></span>

<h2 id="拷贝-EFI"><a href="#拷贝-EFI" class="headerlink" title="拷贝 EFI"></a>拷贝 EFI</h2><ol>
<li>挂载 u 盘的 EFI 分区（在 Finder 中 带有三角符号的即是 u 盘的 EFI 分区）</li>
<li>拷贝 EFI 分区下的 EFI 文件夹到桌面</li>
<li>挂载磁盘的 EFI 分区（注：在 10.15.3 和 10.15.4 中 Finder 不会显示两个 EFI 磁盘，可以先卸载 u 盘 的 EFI 分区）</li>
<li>将桌面的 EFI 文件夹拷入磁盘的 EFI 分区，替换其下的 EFI 文件夹</li>
</ol>
<h2 id="注入三码"><a href="#注入三码" class="headerlink" title="注入三码"></a>注入三码</h2><ol>
<li><p>点击 Clover Configurator 左下角</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328230934689.png" alt="image-20200328230934689"></p>
</li>
<li><p>打开 EFI 分区 下的 EFI&gt;CLOVER&gt;config.list</p>
</li>
<li><p>引导参数：<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328232028435.png" alt="image-20200328232028435"></p>
</li>
<li><p>（可选）修改 UI 比例，解决开机过程中苹果 logo 从大变小<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328232246807.png" alt="image-20200328232246807"></p>
</li>
<li><p>生成序列号和 SMUUID，多点击几下避免随机<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328232723186.png" alt="image-20200328232723186"></p>
</li>
<li><p>生成设备的唯一标识号<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328232851352.png" alt="image-20200328232851352"></p>
</li>
<li><p>生成 ROM ID<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328233244117.png" alt="image-20200328233244117"></p>
</li>
<li><p>保存<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328233536063.png" alt="image-20200328233536063"></p>
</li>
<li><p>关闭 Clover Configurator，会提示不支持永久性的版本存储，点击 <code>好</code></p>
</li>
<li><p>重启 mac，此处可以拔出 U 盘了</p>
</li>
</ol>
<h2 id="易用性设置"><a href="#易用性设置" class="headerlink" title="易用性设置"></a>易用性设置</h2><h3 id="必做设置"><a href="#必做设置" class="headerlink" title="必做设置"></a>必做设置</h3><h4 id="睡眠修复"><a href="#睡眠修复" class="headerlink" title="睡眠修复"></a>睡眠修复</h4><ol>
<li>打开 Hackintool&gt;电源</li>
<li>修改睡眠值<br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200328234800909.png" alt="image-20200328234800909"></li>
</ol>
<h3 id="可选设置"><a href="#可选设置" class="headerlink" title="可选设置"></a>可选设置</h3><h4 id="打开允许安装其他来源应用"><a href="#打开允许安装其他来源应用" class="headerlink" title="打开允许安装其他来源应用"></a>打开允许安装其他来源应用</h4><p>终端执行：<code>sudo spctl --master-disable</code></p>
<h4 id="关闭读卡器显示"><a href="#关闭读卡器显示" class="headerlink" title="关闭读卡器显示"></a>关闭读卡器显示</h4><p>如果硬改了 NUC8，会在右上角出现一个卡片的标识<br><strong>！！！waring：不要点击关闭卡会造成死机</strong></p>
<p>解决办法是：</p>
<ol>
<li>10.15 Catalina 中系统分区默认为只读，故需执行：<br> <code>sudo mount -uw / &amp;&amp; killall Finder</code></li>
<li>关闭读卡器显示：<br> <code>fn=&quot;.`date +%s`&quot; &amp;&amp; sudo mv /System/Library/CoreServices/Menu\ Extras/ExpressCard.menu /System/Library/CoreServices/Menu\ Extras/ExpressCard.menu$fn &amp;&amp; sudo touch /System/Library/CoreServices/Menu\ Extras/ExpressCard.menu</code></li>
</ol>
<h2 id="黑苹果下装-win10"><a href="#黑苹果下装-win10" class="headerlink" title="黑苹果下装 win10"></a>黑苹果下装 win10</h2><h3 id="制作-winpe"><a href="#制作-winpe" class="headerlink" title="制作 winpe"></a>制作 winpe</h3><p>下载 winpe 和 纯净 win10 镜像，然后写入到 u 盘中</p>
<h3 id="分区"><a href="#分区" class="headerlink" title="分区"></a>分区</h3><p>！！！不要使用 BootCamp，黑苹果使用无效</p>
<ol>
<li>打开磁盘工具</li>
<li>分一个 win10 系统盘的分区（格式随意，后期会重新格式化）出来，如果玩游戏多，可以多分一点</li>
<li>分一个 500M 左右的分区作为 win10 efi 存放地址</li>
</ol>
<h3 id="重启到-winpe"><a href="#重启到-winpe" class="headerlink" title="重启到 winpe"></a>重启到 winpe</h3><ol>
<li>重启到 bios（一般是开机时按 F2 进入 bios）</li>
<li>选择 winpe 的 uefi 启动</li>
<li>打开 diskgenius，备份 efi 文件夹 A（此步为保留苹果和 clover 的启动文件），备份到外部磁盘，以防格盘</li>
<li>选择安装 win10，选择安装文件夹为刚刚格式化的那个磁盘，引导文件夹选择 efi 文件夹 A 所在磁盘（必须先覆盖）</li>
<li>安装完成后，重启到 win10，进行设置。。。</li>
<li>重启到 winpe</li>
<li>将 efi 文件夹 B 中的内容拷贝到 500M 分区中</li>
<li>将之前备份的 efi 文件夹 A 中内容拷贝到当前 efi 文件夹 B 中，覆盖所有</li>
<li>重启，会看见 clover。出现 win10 和 mac 双启动选项</li>
</ol>
<h2 id="问题汇总"><a href="#问题汇总" class="headerlink" title="问题汇总"></a>问题汇总</h2><ol>
<li>使用外置显卡坞为什么无法点亮？  <ol>
<li>进入 BIOS&gt;Security 菜单</li>
<li>将 Thunderbolt Security Level 设置为 Legacy Mode</li>
<li>进入 BIOS&gt;Boot 菜单</li>
<li>Boot Configuration – Boot Devices – Thunderbolt Boot 开启</li>
</ol>
</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>mac</tag>
        <tag>黑苹果</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 pyenv 和 pipenv 管理糟糕的 python 环境</title>
    <url>/2020/10/install-pyenv-and-pipenv/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>经常写 python 的开发一定苦恼过一个问题，那就是如何安装 python 环境。经常遇到如下问题：</p>
<ol>
<li>许多系统默认只自带 python2，导致现在很多使用 python3 的程序无法正常运行</li>
<li>python3 各版本间也不是完全兼容，某些第三库只支持特定的 python 版本</li>
</ol>
<p>这些问题导致了我们经常会在各个版本间进行切换，甚至每个项目的 python 版本都不尽相同。所以我们需要一个环境管理器来帮助我们管理各个版本，这就用到了 <strong>pyenv</strong>。</p>
<p>而当我们项目过多之后，每个项目的依赖包就会有多个版本。这些依赖包的管理就需要用到 <strong>pipenv</strong>。</p>
<span id="more"></span>

<h2 id="pyenv"><a href="#pyenv" class="headerlink" title="pyenv"></a>pyenv</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>mac 使用 homebrew 安装 pyenv</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> pyenv<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="设置环境变量"><a href="#设置环境变量" class="headerlink" title="设置环境变量"></a>设置环境变量</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token builtin class-name">export</span> <span class="token assign-left variable">PYENV_ROOT</span><span class="token operator">=</span><span class="token string">"<span class="token environment constant">$HOME</span>/.pyenv"</span>
<span class="token builtin class-name">export</span> <span class="token assign-left variable"><span class="token environment constant">PATH</span></span><span class="token operator">=</span><span class="token string">"<span class="token variable">$PYENV_ROOT</span>/bin:<span class="token environment constant">$PATH</span>"</span>
<span class="token keyword">if</span> <span class="token builtin class-name">command</span> <span class="token parameter variable">-v</span> pyenv <span class="token operator"><span class="token file-descriptor important">1</span>></span>/dev/null <span class="token operator"><span class="token file-descriptor important">2</span>></span><span class="token file-descriptor important">&amp;1</span><span class="token punctuation">;</span>
<span class="token keyword">then</span>
  <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable"><span class="token variable">$(</span>pyenv init -<span class="token variable">)</span></span>"</span>
<span class="token keyword">fi</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<ul>
<li>如果使用的是 bash，则将上述代码粘贴到 <code>~/.bashrc</code> 中<br>重新加载环境变量，<code>source ~/.bashrc</code></li>
<li>如果使用的是 zsh，则将上述代码粘贴到 <code>~/.zshrc</code> 中<br>重新加载环境变量，<code>source ~/.zshrc</code></li>
</ul>
<h3 id="安装-python-3-6-6"><a href="#安装-python-3-6-6" class="headerlink" title="安装 python 3.6.6"></a>安装 python 3.6.6</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv <span class="token function">install</span> <span class="token number">3.6</span>.6<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>默认使用 3.6.6</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv global <span class="token number">3.6</span>.6<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="pyenv-常用命令"><a href="#pyenv-常用命令" class="headerlink" title="pyenv 常用命令"></a>pyenv 常用命令</h3><ul>
<li><p>查看有哪些 Python 版本可以安装</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv <span class="token function">install</span> <span class="token parameter variable">--list</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>安装某个 Python 版本</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv <span class="token function">install</span> <span class="token number">3.6</span>.4<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>查看当前 Python 版本情况（* 表示系统当前的 Python 版本，system表示系统初始版本）</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv versions<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">  system
* <span class="token number">3.6</span>.6 <span class="token punctuation">(</span>set by /Users/jiz4oh/.pyenv/version<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
</li>
<li><p>切换 Python 默认版本</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 切换全局默认版本</span>
pyenv global <span class="token number">3.6</span>.6
<span class="token comment"># 切换当前项目默认版本</span>
pyenv <span class="token builtin class-name">local</span> <span class="token number">3.6</span>.6
<span class="token comment"># 切换 shell 使用的默认版本</span>
pyenv shell <span class="token number">3.6</span>.6<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>卸载指定 Python 版本</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pyenv uninstall <span class="token number">3.6</span>.6<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ul>
<h2 id="pipenv"><a href="#pipenv" class="headerlink" title="pipenv"></a>pipenv</h2><h3 id="安装-1"><a href="#安装-1" class="headerlink" title="安装"></a>安装</h3><p>mac 使用 homebrew 安装 pipenv</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> pipenv<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>【2020.10.01】homebrew 安装版本为 2018.11.26_3，如果需要安装最新版本需要使用</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">pip3 <span class="token function">install</span> pipenv<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://segmentfault.com/a/1190000015389565">Pipenv – 超好用的 Python 包管理工具</a></p>
]]></content>
      <categories>
        <category>后端</category>
        <category>python</category>
      </categories>
      <tags>
        <tag>开发环境</tag>
      </tags>
  </entry>
  <entry>
    <title>openwrt 扩容 overlay</title>
    <url>/2020/09/openwrt-expand-overlay-storage/</url>
    <content><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p><code>lsblk</code>：查看当前固件的分区信息</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda            <span class="token number">8</span>:0    <span class="token number">1</span> <span class="token number">58</span>.6G  <span class="token number">0</span> disk
├─sda1         <span class="token number">8</span>:1    <span class="token number">1</span>  16M  <span class="token number">0</span> part
├─sda2         <span class="token number">8</span>:2    <span class="token number">1</span>  300M  <span class="token number">0</span> part /mnt/sda4<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711142556881.png" alt="image-20200711142556881"></p>
<p>sda 2 中的浅蓝色和深蓝色区域为底层 Squash 格式，该格式只读，不支持修改，优势是可以在出错时轻松重置</p>
<p>overlay 就是在 upper layer 层进行读写的形式</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711143340836.png" alt="image-20200711143340836"></p>
<p>overlay 的扩容并不是在 sda2 上进行操作，而是新建一个更大的分区 sda3，并将 Overlay 指向 sda3 ，这样的话，重置 sda2 后并不会损坏 sda3 中的配置</p>
<span id="more"></span>

<h2 id="扩容步骤"><a href="#扩容步骤" class="headerlink" title="扩容步骤"></a>扩容步骤</h2><h3 id="创建新分区"><a href="#创建新分区" class="headerlink" title="创建新分区"></a>创建新分区</h3><p>使用 cfdisk 进行磁盘操作(使用 opkg install cfdisk 安装，如果安装失败请更新 opkg 源 opkg update)</p>
<ol>
<li><p><code>cfdisk</code></p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711144836714.png" alt="image-20200711144836714"></p>
</li>
<li><p>新建分区：切换到 free space ，切换到 new 回车，输入分区大小</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711145123408.png" alt="image-20200711145123408"></p>
<p> 选择主分区或者扩展分区：选择 primary</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711145248408.png" alt="image-20200711145248408"></p>
</li>
<li><p>将更改写入分区表：光标移到新分区，选择 wirte，并输入 yes</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711145522455.png" alt="image-20200711145522455"></p>
</li>
<li><p>退出 cfdisk：选择 quit</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711145729840.png" alt="image-20200711145729840"></p>
</li>
</ol>
<h3 id="格式化新分区"><a href="#格式化新分区" class="headerlink" title="格式化新分区"></a>格式化新分区</h3><p><code>mkfs.ext4 /dev/sda3</code></p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711150146466.png" alt="image-20200711150146466"></p>
<h3 id="挂载新分区"><a href="#挂载新分区" class="headerlink" title="挂载新分区"></a>挂载新分区</h3><p>将 /dev/sda3 挂载到 /mnt/sda3 下：<code>mount /dev/sda3 /mnt/sda3</code></p>
<p><code>ls /mnt/sda3</code> 查看 /mnt/sda3 目录，如果有 lost+found 目录则表示挂载成功  </p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/image-20200711150414828.png" alt="image-20200711150414828"></p>
<h3 id="拷贝-overlay-下所有文件"><a href="#拷贝-overlay-下所有文件" class="headerlink" title="拷贝 /overlay 下所有文件"></a>拷贝 /overlay 下所有文件</h3><p><code>ls /overlay</code> 查看 /overlay 目录，如果有文件则拷贝到 /mnt/sda3 中  </p>
<p><code>cp -r /overlay/* /mnt/sda3</code>  </p>
<p>检测是否拷贝成功  </p>
<p><code>ls /mnt/sda3</code></p>
<p>在系统中挂载目录</p>
<p>重启路由器</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://www.vediotalk.com/archives/13889">OPENWRT | ESXI 下 OpenWrt扩容Overlay,增加安装插件空间</a></p>
]]></content>
      <categories>
        <category>路由器</category>
      </categories>
      <tags>
        <tag>openwrt</tag>
      </tags>
  </entry>
  <entry>
    <title>在pve中安装openwrt</title>
    <url>/2021/02/openwrt-in-pve/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>一直对软路由这个东西念念不忘，想要入手折腾一下。最近因为家里需要一台服务器，我就把我的 n1 拿回了家里，转手就入了一个 j3455 的成品软路由，这边文章记录下在 pve 环境下折腾 openwrt 的心得，顺便学习下 pve（说不定能当个 IDC 呢</p>
<span id="more"></span>

<h2 id="安装-PVE"><a href="#安装-PVE" class="headerlink" title="安装 PVE"></a>安装 PVE</h2><ol>
<li>在官网中下载 <a href="https://www.proxmox.com/en/downloads/category/iso-images-pve">ISO 镜像</a></li>
<li>烧录到 U 盘中</li>
<li>使用U盘启动</li>
<li>安装，具体可参考<a href="https://www.cxthhhhh.com/2020/09/21/pure-installation-the-whole-installation-process-of-the-original-proxmox-ve-iso.html">【纯净安装】Proxmox-VE ISO原版 安装 全过程</a></li>
<li>登录PVE后台，地址为 <code>https://IP:8006</code>，重点：<code>https</code>，使用 chrome 登录时因为证书不安全的原因会被拦截，选择信任</li>
</ol>
<h2 id="下载-OPENWRT-镜像"><a href="#下载-OPENWRT-镜像" class="headerlink" title="下载 OPENWRT 镜像"></a>下载 OPENWRT 镜像</h2><p>我使用的<a href="https://drive.google.com/drive/folders/1ktbDgnnP8pTMERjpPuETNphzAjNA6hZ2">esir 的 x86 佛跳墙</a>，下载是 gz 格式的压缩文件，需要解压为 img 格式的镜像文件</p>
<h2 id="分配网卡"><a href="#分配网卡" class="headerlink" title="分配网卡"></a>分配网卡</h2><p>路由器最重要的就是将端口的网卡分配成 WAN 口和 LAN 口，这样才能形成一个网络拓扑结构。因为我是在 PVE 中安装虚拟机的方式使用 OPENWRT，所以需要先在 PVE 中将网卡映射到虚拟机中，路由器才能正确分配端口。</p>
<ol>
<li><p>安装 PVE 的过程中，我们已经将 eth0 口(也就是图上的 enp1s0) 虚拟成了 vmbr0</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20210206155524.png" alt="20210206155524"></p>
</li>
<li><p>因为我的软路由总共有4个网卡，所以我还需要虚拟3个网卡出来，和硬件口一一对应，比如将 en2s0 虚拟成 vmbr1，以此类推<br> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/telegram-cloud-photo-size-5-6123167585387260669-y.jpg" alt="telegram-cloud-photo-size-5-6123167585387260669-y"></p>
<p> 最终效果：</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-02-45.png" alt="2021-02-06-16-02-45"></p>
</li>
<li><p>应用配置</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-04-34.png" alt="2021-02-06-16-04-34"></p>
<p> 如果遇到了这个错误，是因为没有 ifupdown2，需要在 shell 中执行 <code>apt install -y ifupdown2</code></p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-05-23.jpg" alt="2021-02-06-16-05-23"></p>
</li>
</ol>
<h2 id="创建OPENWRT虚拟机"><a href="#创建OPENWRT虚拟机" class="headerlink" title="创建OPENWRT虚拟机"></a>创建OPENWRT虚拟机</h2><ol>
<li><p>点击右上角 <code>创建虚拟机</code></p>
</li>
<li><p><code>一般</code>：输入名称并设置开机自启，点击<code>下一步</code><br> 我使用的 openwrt，注意 VM ID，这是以后在 PVE 中操作虚拟机的关键</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-07-58.png" alt="2021-02-06-16-07-58"></p>
</li>
<li><p><code>操作系统</code>：选择<code>不使用任何介质</code>，点击<code>下一步</code><br> 稍后再上传镜像文件，因为需要对磁盘进行一些操作</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-09-07.png" alt="2021-02-06-16-09-07"></p>
</li>
<li><p><code>系统</code>：全部默认，点击<code>下一步</code></p>
</li>
<li><p><code>硬盘</code>：全部默认，点击<code>下一步</code></p>
</li>
<li><p><code>CPU</code>：选择分配给虚拟机的CPU，点击<code>下一步</code><br> 按个人喜好分配 CPU 个数，我分配的 4 个，CPU 权重是在多个虚拟机中竞争 CPU 时，虚拟机的优先级，默认是 1024，可以增加 OPENWET 的权重保证网络通畅</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-16-55.png" alt="2021-02-06-16-16-55"></p>
</li>
<li><p><code>内存</code>：按照个人喜好分配，如果只是单纯科学上网，1G足矣</p>
</li>
<li><p><code>网络</code>：模型选择 VirtIO<br> 桥接网卡随便选，后面会将全部网卡添加进来</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-21-07.png" alt="2021-02-06-16-21-07"></p>
</li>
<li><p><code>确认</code></p>
</li>
</ol>
<h2 id="配置虚拟机"><a href="#配置虚拟机" class="headerlink" title="配置虚拟机"></a>配置虚拟机</h2><ol>
<li><p>分离创建时选择的硬盘</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-24-12.png" alt="2021-02-06-16-24-12"></p>
</li>
<li><p>删除<code>未使用的磁盘0</code>和<code>CD/DVD驱动器(ide2)</code></p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-25-38.png" alt="2021-02-06-16-25-38"></p>
</li>
<li><p>上传之前下载的 OPENWRT img 文件</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-29-01.png" alt="2021-02-06-16-29-01"></p>
</li>
<li><p>拷贝镜像上传地址</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-31-56.png" alt="2021-02-06-16-31-56"></p>
</li>
<li><p>将 OPENWET 镜像导入磁盘<br> 在 shell 中执行 ：</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">qm importdisk <span class="token number">100</span> /var/lib/vz/template/iso/openwrt-buddha-v2_2021_-x86-64-generic-squashfs-uefi.img local-lvm<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p> 图中第一个绿框中的 100 为虚拟机的 VM ID，第二个绿框为刚刚上传的镜像地址</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-34-18.png" alt="2021-02-06-16-34-18"></p>
</li>
<li><p>设置磁盘</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-37-42.png" alt="2021-02-06-16-37-42"></p>
</li>
<li><p>调整引导顺序，将 sata0 磁盘启用并调整到第一位</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-42-15.png" alt="2021-02-06-16-42-15"></p>
</li>
<li><p>添加虚拟网卡，将之前虚拟出来的网卡都依次添加进去，还是使用 VirtIO 模型</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2021-02-06-16-40-30.png" alt="2021-02-06-16-40-30"></p>
</li>
<li><p>启动虚拟机</p>
</li>
</ol>
<p>现在就可以使用 OPENWRT 了</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://www.cxthhhhh.com/2020/09/21/pure-installation-the-whole-installation-process-of-the-original-proxmox-ve-iso.html">【纯净安装】Proxmox-VE ISO原版 安装 全过程</a></p>
<p><a href="https://www.10bests.com/install-openwrt-lede-on-pve/">PVE安装Openwrt/LEDE软路由保姆级图文教程</a></p>
]]></content>
      <categories>
        <category>路由器</category>
      </categories>
      <tags>
        <tag>openwrt</tag>
        <tag>pve</tag>
      </tags>
  </entry>
  <entry>
    <title>Ruby 是如何调用方法的</title>
    <url>/2020/11/receiver-and-ancestors/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>当一个方法被调用时，要做的事情其实只有两件，1. 找到它。2. 调用它</p>
<span id="more"></span>

<h2 id="接收者-receiver"><a href="#接收者-receiver" class="headerlink" title="接收者 receiver"></a>接收者 receiver</h2><p>接收者就是调用方法所在的对象。比如 <code>&#39;str&#39;.to_sym</code> 语句中，<code>&#39;str&#39;</code> 就是接收者。可以形象的理解为向这个接收者 <code>&#39;str&#39;</code> 发送了一条 <code>to_sym</code> 的消息</p>
<p>上面是显式指定接收者的例子，而在 Ruby 中是可以不指定接收者的，像这样：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">MyClass</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">my_method</span></span>
    test
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">test</span></span>
    p <span class="token string-literal"><span class="token string">"I'm Test"</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token class-name">MyClass</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>my_method
<span class="token comment"># I'm Test</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>当一个字符串被调用时，Ruby 首先会在当前作用域查找是否有这个字符串对应的局部变量，如果没有时就会在 self 这个默认的接收者上调用方法</p>
<h2 id="找到它：祖先链-ancestors"><a href="#找到它：祖先链-ancestors" class="headerlink" title="找到它：祖先链 ancestors"></a>找到它：祖先链 ancestors</h2><p>在面向对象的语言中，继承是一个很常见的概念，比如 Python 支持多继承，通过继承多个父类来复用代码，当方法被调用时，首先查找该对象是否有该方法，如果没有，则查找「方法解析顺序」（Method Resolution Order，或 MRO），而 MRO 通过 <a href="https://en.wikipedia.org/wiki/C3_linearization">C3算法</a>（python3）来计算，简单来说就是广度优先</p>
<p>而在 Ruby 中是不支持多继承的，转而使用 mixinx 的方式来实现代码的复用，mro 是通过搜索祖先链一直向上查找。当 Ruby 调用一个方法时，会首先查找对象是否有该方法，如果没有的话，则向上搜索整个祖先链，这就是 <code>one step to the right, then up</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">M1</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">class</span> <span class="token class-name">M2</span> <span class="token operator">&lt;</span> <span class="token constant">M1</span><span class="token punctuation">;</span><span class="token keyword">end</span>

<span class="token comment"># 查看祖先链</span>
<span class="token constant">M2</span><span class="token punctuation">.</span>ancestors  <span class="token comment"># [M2, M1, Object, Kernel, BasicObject]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="include"><a href="#include" class="headerlink" title="include"></a>include</h3><p><code>include</code> 是实现 mixinx 最常见的方式，当模块 A 被包含在类（或者模块）B 中时，这个 A 在 B 的祖先链的位置就在 B 之上，例：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">M1</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M2</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M3</span>
 <span class="token keyword">include</span> <span class="token constant">M1</span>
 <span class="token keyword">include</span> <span class="token constant">M2</span>
<span class="token keyword">end</span>

<span class="token constant">M3</span><span class="token punctuation">.</span>ancestors  <span class="token comment"># [M3, M2, M1]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="prepend"><a href="#prepend" class="headerlink" title="prepend"></a>prepend</h3><p><code>prepend</code> 方法类似于 <code>include</code>，不过这个方法会将模块插入祖先链的下方，例：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">M1</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M2</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M3</span>
 <span class="token keyword">prepend</span> <span class="token constant">M1</span>
 <span class="token keyword">prepend</span> <span class="token constant">M2</span>
<span class="token keyword">end</span>

<span class="token constant">M3</span><span class="token punctuation">.</span>ancestors  <span class="token comment"># [M2, M1, M3]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="多重包含"><a href="#多重包含" class="headerlink" title="多重包含"></a>多重包含</h3><p>当模块 C 包含模块 A、B，模块 B 包含模块 A 时，A 被重复导入，Ruby 会忽略已经被加入祖先链的重复模块导入</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">A</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">B</span>
  <span class="token keyword">include</span> <span class="token constant">A</span>
<span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">C</span>
  <span class="token keyword">prepend</span> <span class="token constant">A</span>
  <span class="token keyword">include</span> <span class="token constant">B</span>
<span class="token keyword">end</span>

<span class="token constant">B</span><span class="token punctuation">.</span>ancestors   <span class="token comment"># [B, A]</span>
<span class="token constant">C</span><span class="token punctuation">.</span>ancestors   <span class="token comment"># [A, C, B]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>更复杂的包含：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">M1</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M2</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M3</span><span class="token punctuation">;</span><span class="token keyword">end</span>
<span class="token keyword">module</span> <span class="token class-name">M4</span>
  <span class="token keyword">include</span> <span class="token constant">M1</span>
  <span class="token keyword">include</span> <span class="token constant">M2</span>
  <span class="token keyword">prepend</span> <span class="token constant">M3</span>
<span class="token keyword">end</span>

<span class="token keyword">module</span> <span class="token class-name">M5</span>
  <span class="token keyword">prepend</span> <span class="token constant">M1</span>
  <span class="token keyword">include</span> <span class="token constant">M2</span>
  <span class="token keyword">include</span> <span class="token constant">M4</span>
<span class="token keyword">end</span>

<span class="token constant">M4</span><span class="token punctuation">.</span>ancestors  <span class="token comment"># [M3, M4, M2, M1]</span>
<span class="token constant">M5</span><span class="token punctuation">.</span>ancestors  <span class="token comment"># [M1, M5, M3, M4, M2]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="调用它"><a href="#调用它" class="headerlink" title="调用它"></a>调用它</h2><p>刚刚我们已经通过祖先链找到了方法，接下来就要执行这个方法。假如有以下方法：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">my_method</span></span>
  temp <span class="token operator">=</span> <span class="token number">1</span>
  my_other_method<span class="token punctuation">(</span>temp<span class="token punctuation">)</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>当执行 <code>my_method</code> 方法时，方法内部需要调用 <code>my_other_method</code>，而该由哪个对象来调用这个方法？</p>
<h3 id="self-关键字"><a href="#self-关键字" class="headerlink" title="self 关键字"></a>self 关键字</h3><blockquote>
<p>Ruby 中的每一行代码都会在一个对象中被执行–这个对象就是所谓的当前对象。</p>
</blockquote>
<p>当前对象可以用 self 关键字来指代，而所有没有明确指明接收者的方法都会在 self 上调用。</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">MyClass</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">testing_self</span></span>
    <span class="token variable">@var</span> <span class="token operator">=</span> <span class="token number">10</span>
    my_method<span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">self</span>
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">my_method</span></span>
    <span class="token variable">@var</span> <span class="token operator">+=</span> <span class="token number">1</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

obj <span class="token operator">=</span> <span class="token class-name">MyClass</span><span class="token punctuation">.</span><span class="token keyword">new</span>
obj<span class="token punctuation">.</span>testing_self  <span class="token comment"># &lt;MyClass:0x00007faea4131b90 @var=11></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="private-是怎么实现的"><a href="#private-是怎么实现的" class="headerlink" title="private 是怎么实现的"></a>private 是怎么实现的</h3><p>private 方法遵从一个简单的规则：</p>
<blockquote>
<p>不能明确指定接收者来调用私有方法</p>
</blockquote>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">C</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">public_method</span></span>
    <span class="token keyword">self</span><span class="token punctuation">.</span>private_method
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">private_method</span></span>
    <span class="token number">1</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token class-name">C</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>public_method  <span class="token comment"># NoMethodError (private method `private_method' called for #&lt;C:0x00007fd0c40feb90>)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>总结：</p>
<ol>
<li>如果需要调用其他对象的方法，必须显式指定接收者</li>
<li>私有方法不能明确指定接收者来调用</li>
</ol>
<p>所以私有方法不能被其他对象调用，不能被自身显式 <code>self</code> 调用，只能隐式 <code>self</code> 调用</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://book.douban.com/subject/26575429/">Ruby元编程</a></p>
]]></content>
      <categories>
        <category>后端</category>
        <category>ruby</category>
      </categories>
      <tags>
        <tag>study</tag>
      </tags>
  </entry>
  <entry>
    <title>Devise 源码浅析</title>
    <url>/2021/01/ruby-devise/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Devise 是 ruby 世界中最常见的 gem 之一，是用于在 web 请求中做身份验证的，他的设计非常精妙，今天我们来尝试看看 devise 是如何设计的。</p>
<span id="more"></span>

<h2 id="如何使用-Devise"><a href="#如何使用-Devise" class="headerlink" title="如何使用 Devise"></a>如何使用 Devise</h2><p>devise 的使用重点是为 rails 的 MVC 三层中各引入 devise 相关的 magic 方法，比如我们今天以 User 模型为例</p>
<ol>
<li><p>为 controller 层和 view 层引入 devise，需要在 <code>config/routes.rb</code> 中引入</p>
 <pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby">devise_for <span class="token symbol">:users</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ol>
<span id="model-devise">

<ol start="2">
<li><p>为 model 层引入 devise，需要在 <code>app/models/user.rb</code> 中引入</p>
 <pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby">devise <span class="token symbol">:database_authenticatable</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p> 上述我们引入了 <code>database_authenticatable</code> 模块，而 devise 中共有十个模块可由我们按需引入</p>
</li>
</ol>
<h2 id="devise-for"><a href="#devise-for" class="headerlink" title="devise_for"></a>devise_for</h2><p>整个 devise 的核心之一就是 <code>devise_for</code> 方法，如果没有调用这个方法，就不会生成可供我们在 controller 层 和 view 层使用的 helper</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">devise_for</span></span><span class="token punctuation">(</span><span class="token operator">*</span>resources<span class="token punctuation">)</span>
  <span class="token operator">...</span>
  <span class="token operator">...</span>

  resources<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>resource<span class="token operator">|</span>
    mapping <span class="token operator">=</span> Devise<span class="token punctuation">.</span>add_mapping<span class="token punctuation">(</span>resource<span class="token punctuation">,</span> options<span class="token punctuation">)</span>

    <span class="token keyword">begin</span>
      raise_no_devise_method_error<span class="token operator">!</span><span class="token punctuation">(</span>mapping<span class="token punctuation">.</span>class_name<span class="token punctuation">)</span> <span class="token keyword">unless</span> mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span>respond_to<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:devise</span><span class="token punctuation">)</span>
    <span class="token keyword">rescue</span> NameError <span class="token operator">=></span> e
      <span class="token keyword">raise</span> <span class="token keyword">unless</span> mapping<span class="token punctuation">.</span>class_name <span class="token operator">==</span> resource<span class="token punctuation">.</span>to_s<span class="token punctuation">.</span>classify
      warn <span class="token string-literal"><span class="token string">"[WARNING] You provided devise_for </span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">resource<span class="token punctuation">.</span>inspect</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string"> but there is "</span></span> \
        <span class="token string-literal"><span class="token string">"no model </span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">mapping<span class="token punctuation">.</span>class_name</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string"> defined in your application"</span></span>
      <span class="token keyword">next</span>
    <span class="token keyword">rescue</span> NoMethodError <span class="token operator">=></span> e
      <span class="token keyword">raise</span> <span class="token keyword">unless</span> e<span class="token punctuation">.</span>message<span class="token punctuation">.</span><span class="token keyword">include</span><span class="token operator">?</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"undefined method `devise'"</span></span><span class="token punctuation">)</span>
      raise_no_devise_method_error<span class="token operator">!</span><span class="token punctuation">(</span>mapping<span class="token punctuation">.</span>class_name<span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token operator">...</span>
    <span class="token operator">...</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>devise_for</code> 方法在调用之后会对每一个传入的值比如 <code>users</code> 调用 <code>Devise.add_mapping</code>，而在这里就引入一个概念：<code>Devise</code> 中的 <code>mapping</code>。<br><code>Devise</code> 为了支持多种账户的验证，比如 <code>user</code> 和 <code>admin</code> 账户都可以登录，在内部使用了 <code>mapping</code> 进行区分，每一种账户对应一个 <code>mapping</code>，也对应了后面会讲的 <code>Warden</code> 中的 <code>scope</code>。我们可以对每一个 <code>mapping</code> 来设置不同的策略，比如允许 <code>user</code> 账户进行注册，不允许 <code>admin</code> 账户进行注册等，这样就解决了同一个系统中不同账户验证逻辑需要写两套的问题</p>
<h3 id="Devise-add-mapping"><a href="#Devise-add-mapping" class="headerlink" title="Devise.add_mapping"></a>Devise.add_mapping</h3><pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># Small method that adds a mapping to Devise.</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">add_mapping</span></span><span class="token punctuation">(</span>resource<span class="token punctuation">,</span> options<span class="token punctuation">)</span>
  mapping <span class="token operator">=</span> Devise<span class="token double-colon punctuation">::</span><span class="token class-name">Mapping</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>resource<span class="token punctuation">,</span> options<span class="token punctuation">)</span>
  <span class="token variable">@@mappings</span><span class="token punctuation">[</span>mapping<span class="token punctuation">.</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> mapping
  <span class="token variable">@@default_scope</span> <span class="token operator">||=</span> mapping<span class="token punctuation">.</span>name
  <span class="token variable">@@helpers</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token punctuation">&#123;</span> <span class="token operator">|</span>h<span class="token operator">|</span> h<span class="token punctuation">.</span>define_helpers<span class="token punctuation">(</span>mapping<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span>
  mapping
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>当调用 <code>Devise.add_mapping</code> 时<br><span id="devise-mapping"></p>
<ol>
<li><p>会生成一个 <code>Devise::Mapping</code> 对象，这个对象就是上面说的 <code>mapping</code></p>
</li>
<li><p>在生成之后会将这个对象放入 <code>Devise.mappings</code> 映射中，方便后续的取用</p>
</li>
<li><p>然后将默认验证账户 <code>scope</code> 设置为第一个调用 <code>add_mapping</code> 方法的值</p>
</li>
<li><p>为 <code>mapping</code> 定义帮助方法</p>
 <pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">define_helpers</span></span><span class="token punctuation">(</span>mapping<span class="token punctuation">)</span> <span class="token comment">#:nodoc:</span>
  mapping <span class="token operator">=</span> mapping<span class="token punctuation">.</span>name

  class_eval <span class="token operator">&lt;&lt;</span><span class="token operator">-</span><span class="token constant">METHODS</span><span class="token punctuation">,</span> __FILE__<span class="token punctuation">,</span> __LINE__ <span class="token operator">+</span> <span class="token number">1</span>
    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">authenticate_</span></span><span class="token comment">#&#123;mapping&#125;!(opts=&#123;&#125;)</span>
      opts<span class="token punctuation">[</span><span class="token symbol">:scope</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token operator">:</span><span class="token comment">#&#123;mapping&#125;</span>
      warden<span class="token punctuation">.</span>authenticate<span class="token operator">!</span><span class="token punctuation">(</span>opts<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">!</span>devise_controller<span class="token operator">?</span> <span class="token operator">||</span> opts<span class="token punctuation">.</span>delete<span class="token punctuation">(</span><span class="token symbol">:force</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token comment">#&#123;mapping&#125;_signed_in?</span>
      <span class="token operator">!</span><span class="token operator">!</span>current_<span class="token comment">#&#123;mapping&#125;</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">current_</span></span><span class="token comment">#&#123;mapping&#125;</span>
      <span class="token variable">@current_</span><span class="token comment">#&#123;mapping&#125; ||= warden.authenticate(scope: :#&#123;mapping&#125;)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token comment">#&#123;mapping&#125;_session</span>
      current_<span class="token comment">#&#123;mapping&#125; &amp;&amp; warden.session(:#&#123;mapping&#125;)</span>
    <span class="token keyword">end</span>
  <span class="token constant">METHODS</span>

  ActiveSupport<span class="token punctuation">.</span>on_load<span class="token punctuation">(</span><span class="token symbol">:action_controller</span><span class="token punctuation">)</span> <span class="token keyword">do</span>
    <span class="token keyword">if</span> respond_to<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:helper_method</span><span class="token punctuation">)</span>
      helper_method <span class="token string-literal"><span class="token string">"current_</span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">mapping</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">"</span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">mapping</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">_signed_in?"</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">"</span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">mapping</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">_session"</span></span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p> 比如 user ，会生成 <code>authenticate_user!</code>、<code>user_signed_in?</code>、<code>current_user</code>、<code>user_session</code> 四个帮助方法，而 <code>authenticate_user!</code> 就是我们实现验证的重要方法</p>
</li>
</ol>
<h3 id="authenticate-user"><a href="#authenticate-user" class="headerlink" title="authenticate_user!"></a>authenticate_user!</h3><p>在我们的实际应用中，当我们需要对某个 <code>controller</code> 增加验证，不登录就无法访问时，我们需要添加 <code>before_action :authenticate_user!</code>，比如：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">ApplicationController</span> <span class="token operator">&lt;</span> ActionController<span class="token double-colon punctuation">::</span>Base
  before_action <span class="token symbol">:authenticate_user!</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>然后每个请求到来时，就会调用 <code>authenticate_user!</code> 方法对其进行校验</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># 为了方便，我将源码中元编程生成的代码换成了普通的 ruby 代码</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">authenticate_user</span></span><span class="token operator">!</span><span class="token punctuation">(</span>opts<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  opts<span class="token punctuation">[</span><span class="token symbol">:scope</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token symbol">:user</span>
  warden<span class="token punctuation">.</span>authenticate<span class="token operator">!</span><span class="token punctuation">(</span>opts<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">!</span>devise_controller<span class="token operator">?</span> <span class="token operator">||</span> opts<span class="token punctuation">.</span>delete<span class="token punctuation">(</span><span class="token symbol">:force</span><span class="token punctuation">)</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>上述代码可以看出，<code>authenticate_user!</code> 方法实际上是 <code>warden.authenticate!</code> 方法的代理，所以我们要研究 <code>devise</code> 是如何验证的，就需要查看 <code>warden</code> 是什么</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">warden</span></span>
  request<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span> <span class="token keyword">or</span> <span class="token keyword">raise</span> MissingWarden
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">MissingWarden</span> <span class="token operator">&lt;</span> StandardError
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span>
    <span class="token keyword">super</span> <span class="token string-literal"><span class="token string">"Devise could not find the `Warden::Proxy` instance on your request environment.\n"</span></span> <span class="token operator">+</span> \
      <span class="token string-literal"><span class="token string">"Make sure that your application is loading Devise and Warden as expected and that "</span></span> <span class="token operator">+</span> \
      <span class="token string-literal"><span class="token string">"the `Warden::Manager` middleware is present in your middleware stack.\n"</span></span> <span class="token operator">+</span> \
      <span class="token string-literal"><span class="token string">"If you are seeing this on one of your tests, ensure that your tests are either "</span></span> <span class="token operator">+</span> \
      <span class="token string-literal"><span class="token string">"executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` "</span></span> <span class="token operator">+</span> \
      <span class="token string-literal"><span class="token string">"module to inject the `request.env['warden']` object for you."</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>warden</code> 方法从 requset 的 env 中取出，那这个 warden 是如何被注入的呢，其实 <code>MissingWarden</code> 错误中已经给了我们不少提示，<code>request.env[&#39;warden&#39;]</code> 是一个 <code>Warden::Proxy</code> 对象，而这个对象又是由 <code>Warden::Manager</code> 中间件注入</p>
<h3 id="rack-和-middleware"><a href="#rack-和-middleware" class="headerlink" title="rack 和 middleware"></a>rack 和 middleware</h3><p>几乎所有的 Ruby Web 框架都是一个 rack 的应用，而 rails 就是一个 rack 应用加一堆 middleware 的集合，我们可以通过 <code>rails middleware</code> 来查看</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">$ rails middleware
use Webpacker::DevServerProxy
use Raven::Rack
use Rack::Cors
use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use RequestStore::Middleware
use ActionDispatch::RemoteIp
use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
use Warden::Manager
use ExceptionNotification::Rack
use Rack::Attack
run ApplictionName::Application.routes<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>use</code> 后面跟随的就是 rails 启动的 middlreware 名称，最后包含的就是 rack <code>ApplictionName::Application.routes</code>，也就是说在 Rails 中所有的请求在经过中间件之后都会先有一个路由表来处理，路由会根据一定的规则将请求交给其他控制器处理。</p>
<p>中间件的调用其实类似于柯里化</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># config.ru</span>
use MiddleWare1
use MiddleWare2
run RackApp

<span class="token comment"># equals to</span>
<span class="token class-name">MiddleWare1</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span><span class="token class-name">MiddleWare2</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>RackApp<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>具体的 middleware 相关就不延展了。而在上面我们可以看到 <code>Warden::Manager</code> 中间件已被启用，这就是 <code>devise</code> 验证的基础</p>
<h2 id="Warden"><a href="#Warden" class="headerlink" title="Warden"></a>Warden</h2><h3 id="Warden-Manager"><a href="#Warden-Manager" class="headerlink" title="Warden::Manager"></a>Warden::Manager</h3><pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Warden</span>
  <span class="token keyword">class</span> <span class="token class-name">Manager</span>
    <span class="token keyword">extend</span> Warden<span class="token double-colon punctuation">::</span>Hooks

    attr_accessor <span class="token symbol">:config</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span><span class="token punctuation">(</span>app<span class="token punctuation">,</span> options<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
      default_strategies <span class="token operator">=</span> options<span class="token punctuation">.</span>delete<span class="token punctuation">(</span><span class="token symbol">:default_strategies</span><span class="token punctuation">)</span>

      <span class="token variable">@app</span><span class="token punctuation">,</span> <span class="token variable">@config</span> <span class="token operator">=</span> app<span class="token punctuation">,</span> Warden<span class="token double-colon punctuation">::</span><span class="token class-name">Config</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span>
      <span class="token variable">@config</span><span class="token punctuation">.</span>default_strategies<span class="token punctuation">(</span><span class="token operator">*</span>default_strategies<span class="token punctuation">)</span> <span class="token keyword">if</span> default_strategies
      <span class="token keyword">yield</span> <span class="token variable">@config</span> <span class="token keyword">if</span> block_given<span class="token operator">?</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">call</span></span><span class="token punctuation">(</span>env<span class="token punctuation">)</span> <span class="token comment"># :nodoc:</span>
      <span class="token keyword">return</span> <span class="token variable">@app</span><span class="token punctuation">.</span>call<span class="token punctuation">(</span>env<span class="token punctuation">)</span> <span class="token keyword">if</span> env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span><span class="token punctuation">.</span>manager <span class="token operator">!=</span> <span class="token keyword">self</span>

      env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token class-name">Proxy</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>env<span class="token punctuation">,</span> <span class="token keyword">self</span><span class="token punctuation">)</span>
      result <span class="token operator">=</span> catch<span class="token punctuation">(</span><span class="token symbol">:warden</span><span class="token punctuation">)</span> <span class="token keyword">do</span>
        env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span><span class="token punctuation">.</span>on_request
        <span class="token variable">@app</span><span class="token punctuation">.</span>call<span class="token punctuation">(</span>env<span class="token punctuation">)</span>
      <span class="token keyword">end</span>

      result <span class="token operator">||=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>
      <span class="token keyword">case</span> result
      <span class="token keyword">when</span> <span class="token builtin">Array</span>
        handle_chain_result<span class="token punctuation">(</span>result<span class="token punctuation">.</span>first<span class="token punctuation">,</span> result<span class="token punctuation">,</span> env<span class="token punctuation">)</span>
      <span class="token keyword">when</span> <span class="token builtin">Hash</span>
        process_unauthenticated<span class="token punctuation">(</span>env<span class="token punctuation">,</span> result<span class="token punctuation">)</span>
      <span class="token keyword">when</span> Rack<span class="token double-colon punctuation">::</span>Response
        handle_chain_result<span class="token punctuation">(</span>result<span class="token punctuation">.</span>status<span class="token punctuation">,</span> result<span class="token punctuation">,</span> env<span class="token punctuation">)</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>Warden::Manager</code> 中最重要的两个方法就是 <code>initialize</code>，<code>call</code></p>
<ul>
<li><code>initialize</code>：当一个 rack 应用被启用时，会对每一个中间件调用 <code>new</code> 初始化中间件，并传入一个 app 和一个哈希 options</li>
<li><code>call</code>：当每个请求到来时，会依次调用中间件的 <code>call</code> 方法对请求进行处理，<code>call</code> 方法接受一个参数 env，并在结束时将 env 返回  <ul>
<li><code>env</code> 是一个三元数组，按照 rack 协议，分别代表 <code>[HTTP 状态码, HTTP Headers, 响应体]</code></li>
</ul>
</li>
</ul>
<p>所以当请求到来时，<code>Warden::Manager#call</code> 方法被调用，<code>env[&#39;warden&#39;]</code> 被赋值为 <code>Warden::Proxy</code> 的一个对象，这样在 <code>authenticate_user!</code> 方法中就可以使用 <code>warden.authenticate!</code> 进行验证</p>
<span id="throw-warden">

<p>这里的核心重点是 <code>result = catch(:warden)</code>，<code>call</code> 方法会在应用 <code>throw(:warden)</code> 时接管整个后续响应，这时候会根据 <code>result</code> 类型进行处理，通常会进入 <code>process_unauthenticated</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">process_unauthenticated</span></span><span class="token punctuation">(</span>env<span class="token punctuation">,</span> options<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  options<span class="token punctuation">[</span><span class="token symbol">:action</span><span class="token punctuation">]</span> <span class="token operator">||=</span> <span class="token keyword">begin</span>
    opts <span class="token operator">=</span> config<span class="token punctuation">[</span><span class="token symbol">:scope_defaults</span><span class="token punctuation">]</span><span class="token punctuation">[</span>config<span class="token punctuation">.</span>default_scope<span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>
    opts<span class="token punctuation">[</span><span class="token symbol">:action</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token string-literal"><span class="token string">'unauthenticated'</span></span>
  <span class="token keyword">end</span>

  proxy  <span class="token operator">=</span> env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'warden'</span></span><span class="token punctuation">]</span>
  result <span class="token operator">=</span> options<span class="token punctuation">[</span><span class="token symbol">:result</span><span class="token punctuation">]</span> <span class="token operator">||</span> proxy<span class="token punctuation">.</span>result

  <span class="token keyword">case</span> result
  <span class="token keyword">when</span> <span class="token symbol">:redirect</span>
    body <span class="token operator">=</span> proxy<span class="token punctuation">.</span>message <span class="token operator">||</span> <span class="token string-literal"><span class="token string">"You are being redirected to </span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">proxy<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'Location'</span></span><span class="token punctuation">]</span></span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">"</span></span>
    <span class="token punctuation">[</span>proxy<span class="token punctuation">.</span>status<span class="token punctuation">,</span> proxy<span class="token punctuation">.</span>headers<span class="token punctuation">,</span> <span class="token punctuation">[</span>body<span class="token punctuation">]</span><span class="token punctuation">]</span>
  <span class="token keyword">when</span> <span class="token symbol">:custom</span>
    proxy<span class="token punctuation">.</span>custom_response
  <span class="token keyword">else</span>
    options<span class="token punctuation">[</span><span class="token symbol">:message</span><span class="token punctuation">]</span> <span class="token operator">||=</span> proxy<span class="token punctuation">.</span>message
    call_failure_app<span class="token punctuation">(</span>env<span class="token punctuation">,</span> options<span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>根据 <code>options</code> 中 <code>:result</code> 的不同区分处理方式，通常会调用 <code>call_failure_app</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">call_failure_app</span></span><span class="token punctuation">(</span>env<span class="token punctuation">,</span> options <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  <span class="token keyword">if</span> config<span class="token punctuation">.</span>failure_app
    options<span class="token punctuation">.</span>merge<span class="token operator">!</span><span class="token punctuation">(</span><span class="token symbol">:attempted_path</span> <span class="token operator">=></span> <span class="token double-colon punctuation">::</span>Rack<span class="token double-colon punctuation">::</span><span class="token class-name">Request</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>env<span class="token punctuation">)</span><span class="token punctuation">.</span>fullpath<span class="token punctuation">)</span>
    env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">"PATH_INFO"</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">"/</span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">options<span class="token punctuation">[</span><span class="token symbol">:action</span><span class="token punctuation">]</span></span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">"</span></span>
    env<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">"warden.options"</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> options

    _run_callbacks<span class="token punctuation">(</span><span class="token symbol">:before_failure</span><span class="token punctuation">,</span> env<span class="token punctuation">,</span> options<span class="token punctuation">)</span>
    config<span class="token punctuation">.</span>failure_app<span class="token punctuation">.</span>call<span class="token punctuation">(</span>env<span class="token punctuation">)</span><span class="token punctuation">.</span>to_a
  <span class="token keyword">else</span>
    <span class="token keyword">raise</span> <span class="token string-literal"><span class="token string">"No Failure App provided"</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>然后调用 <code>config.failure_app.call</code> 进行处理，这个 <code>config.failure_app</code> 通常是 <code>Devise::FailureApp</code>，当然我们也可以自定义失败处理方式</p>
<p><code>Devise::FailureApp</code> 的处理方式根据配置可以是重定向登录页或者返回401响应码及具体的失败信息，具体的实现和源码可以自行研究</p>
<h3 id="Warden-Proxy"><a href="#Warden-Proxy" class="headerlink" title="Warden::Proxy"></a>Warden::Proxy</h3><h4 id="authenticate"><a href="#authenticate" class="headerlink" title="authenticate!"></a>authenticate!</h4><pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">authenticate</span></span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span>
  user<span class="token punctuation">,</span> opts <span class="token operator">=</span> _perform_authentication<span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span>
  <span class="token keyword">throw</span><span class="token punctuation">(</span><span class="token symbol">:warden</span><span class="token punctuation">,</span> opts<span class="token punctuation">)</span> <span class="token keyword">unless</span> user
  user
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>执行 <code>authenticate!</code> 时，会调用 <code>_perform_authentication</code> 进行验证</p>
<ol>
<li>如果 user 没有被返回的话就会抛出 <code>throw(:warden)</code> 中断请求的执行，<a href="#throw-warden">将后续处理移交 <code>Warden:Manager</code></a></li>
<li>如果 user 被返回，则通过验证，进行对应的 <code>controller#action</code></li>
</ol>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">_perform_authentication</span></span><span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span>
  scope<span class="token punctuation">,</span> opts <span class="token operator">=</span> _retrieve_scope_and_opts<span class="token punctuation">(</span>args<span class="token punctuation">)</span>
  user <span class="token operator">=</span> <span class="token keyword">nil</span>

  <span class="token keyword">return</span> user<span class="token punctuation">,</span> opts <span class="token keyword">if</span> user <span class="token operator">=</span> user<span class="token punctuation">(</span>opts<span class="token punctuation">.</span>merge<span class="token punctuation">(</span><span class="token symbol">:scope</span> <span class="token operator">=></span> scope<span class="token punctuation">)</span><span class="token punctuation">)</span>
  _run_strategies_for<span class="token punctuation">(</span>scope<span class="token punctuation">,</span> args<span class="token punctuation">)</span>

  <span class="token keyword">if</span> winning_strategy <span class="token operator">&amp;&amp;</span> winning_strategy<span class="token punctuation">.</span>successful<span class="token operator">?</span>
    opts<span class="token punctuation">[</span><span class="token symbol">:store</span><span class="token punctuation">]</span> <span class="token operator">=</span> opts<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:store</span><span class="token punctuation">,</span> winning_strategy<span class="token punctuation">.</span>store<span class="token operator">?</span><span class="token punctuation">)</span>
    set_user<span class="token punctuation">(</span>winning_strategy<span class="token punctuation">.</span>user<span class="token punctuation">,</span> opts<span class="token punctuation">.</span>merge<span class="token operator">!</span><span class="token punctuation">(</span><span class="token symbol">:event</span> <span class="token operator">=></span> <span class="token symbol">:authentication</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>

  <span class="token punctuation">[</span><span class="token variable">@users</span><span class="token punctuation">[</span>scope<span class="token punctuation">]</span><span class="token punctuation">,</span> opts<span class="token punctuation">]</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>在 <code>_perform_authentication</code> 方法中</p>
<ol>
<li><p>首先会调用 <code>user</code>，这个方法会反序列化 session 然后查看 session 中是否有该 scope 的用户信息</p>
<ol>
<li>如果有，则通过验证，进行对应的 <code>controller#action</code></li>
<li>如果没有，进行第二步</li>
</ol>
</li>
<li><p>然后调用 <code>_run_strategies_for</code>，对预定策略 <code>strategies</code> 依次进行校验</p>
 <pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># Run the strategies for a given scope</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">_run_strategies_for</span></span><span class="token punctuation">(</span>scope<span class="token punctuation">,</span> args<span class="token punctuation">)</span> <span class="token comment">#:nodoc:</span>
  <span class="token keyword">self</span><span class="token punctuation">.</span>winning_strategy <span class="token operator">=</span> <span class="token variable">@winning_strategies</span><span class="token punctuation">[</span>scope<span class="token punctuation">]</span>
  <span class="token keyword">return</span> <span class="token keyword">if</span> winning_strategy <span class="token operator">&amp;&amp;</span> winning_strategy<span class="token punctuation">.</span>halted<span class="token operator">?</span>

  <span class="token comment"># Do not run any strategy if locked</span>
  <span class="token keyword">return</span> <span class="token keyword">if</span> <span class="token variable">@locked</span>

  <span class="token keyword">if</span> args<span class="token punctuation">.</span>empty<span class="token operator">?</span>
    defaults   <span class="token operator">=</span> <span class="token variable">@config</span><span class="token punctuation">[</span><span class="token symbol">:default_strategies</span><span class="token punctuation">]</span>
    strategies <span class="token operator">=</span> defaults<span class="token punctuation">[</span>scope<span class="token punctuation">]</span> <span class="token operator">||</span> defaults<span class="token punctuation">[</span><span class="token symbol">:_all</span><span class="token punctuation">]</span>
  <span class="token keyword">end</span>

  <span class="token punctuation">(</span>strategies <span class="token operator">||</span> args<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>name<span class="token operator">|</span>
    strategy <span class="token operator">=</span> _fetch_strategy<span class="token punctuation">(</span>name<span class="token punctuation">,</span> scope<span class="token punctuation">)</span>
    <span class="token keyword">next</span> <span class="token keyword">unless</span> strategy <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>strategy<span class="token punctuation">.</span>performed<span class="token operator">?</span> <span class="token operator">&amp;&amp;</span> strategy<span class="token punctuation">.</span>valid<span class="token operator">?</span>

    strategy<span class="token punctuation">.</span>_run<span class="token operator">!</span>
    <span class="token keyword">self</span><span class="token punctuation">.</span>winning_strategy <span class="token operator">=</span> <span class="token variable">@winning_strategies</span><span class="token punctuation">[</span>scope<span class="token punctuation">]</span> <span class="token operator">=</span> strategy
    <span class="token keyword">break</span> <span class="token keyword">if</span> strategy<span class="token punctuation">.</span>halted<span class="token operator">?</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<span id="authenticate-default-strategies">
</li>
<li><p>而这需要获取 scope 对应配置了哪些 strategies，默认查找 <code>Warden::Config</code> 中配置的 <code>default_strategies</code></p>
 <pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">if</span> args<span class="token punctuation">.</span>empty<span class="token operator">?</span>
  defaults   <span class="token operator">=</span> <span class="token variable">@config</span><span class="token punctuation">[</span><span class="token symbol">:default_strategies</span><span class="token punctuation">]</span>
  strategies <span class="token operator">=</span> defaults<span class="token punctuation">[</span>scope<span class="token punctuation">]</span> <span class="token operator">||</span> defaults<span class="token punctuation">[</span><span class="token symbol">:_all</span><span class="token punctuation">]</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

</li>
</ol>
<h2 id="devise"><a href="#devise" class="headerlink" title="devise"></a>devise</h2><p><a href="#model-devise">前面</a> 讲了在 model 层需要定义 <code>devise</code> 方法，这个方法的目的就是引入模块</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">devise</span></span><span class="token punctuation">(</span><span class="token operator">*</span>modules<span class="token punctuation">)</span>
  options <span class="token operator">=</span> modules<span class="token punctuation">.</span>extract_options<span class="token operator">!</span><span class="token punctuation">.</span>dup

  selected_modules <span class="token operator">=</span> modules<span class="token punctuation">.</span>map<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token symbol">:to_sym</span><span class="token punctuation">)</span><span class="token punctuation">.</span>uniq<span class="token punctuation">.</span>sort_by <span class="token keyword">do</span> <span class="token operator">|</span>s<span class="token operator">|</span>
    Devise<span class="token double-colon punctuation">::</span><span class="token constant">ALL</span><span class="token punctuation">.</span>index<span class="token punctuation">(</span>s<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">-</span><span class="token number">1</span>  <span class="token comment"># follow Devise::ALL order</span>
  <span class="token keyword">end</span>

  devise_modules_hook<span class="token operator">!</span> <span class="token keyword">do</span>
    <span class="token keyword">include</span> Devise<span class="token double-colon punctuation">::</span>Models<span class="token double-colon punctuation">::</span>Authenticatable

    selected_modules<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>m<span class="token operator">|</span>
      mod <span class="token operator">=</span> Devise<span class="token double-colon punctuation">::</span>Models<span class="token punctuation">.</span>const_get<span class="token punctuation">(</span>m<span class="token punctuation">.</span>to_s<span class="token punctuation">.</span>classify<span class="token punctuation">)</span>

      <span class="token keyword">if</span> mod<span class="token punctuation">.</span>const_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"ClassMethods"</span></span><span class="token punctuation">)</span>
        class_mod <span class="token operator">=</span> mod<span class="token punctuation">.</span>const_get<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"ClassMethods"</span></span><span class="token punctuation">)</span>
        <span class="token keyword">extend</span> class_mod

        <span class="token keyword">if</span> class_mod<span class="token punctuation">.</span>respond_to<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:available_configs</span><span class="token punctuation">)</span>
          available_configs <span class="token operator">=</span> class_mod<span class="token punctuation">.</span>available_configs
          available_configs<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>config<span class="token operator">|</span>
            <span class="token keyword">next</span> <span class="token keyword">unless</span> options<span class="token punctuation">.</span>key<span class="token operator">?</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span>
            send<span class="token punctuation">(</span><span class="token symbol">:"#&#123;config&#125;="</span><span class="token punctuation">,</span> options<span class="token punctuation">.</span>delete<span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token punctuation">)</span>
          <span class="token keyword">end</span>
        <span class="token keyword">end</span>
      <span class="token keyword">end</span>

      <span class="token keyword">include</span> mod
    <span class="token keyword">end</span>

    <span class="token keyword">self</span><span class="token punctuation">.</span>devise_modules <span class="token operator">|=</span> selected_modules
    options<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token punctuation">&#123;</span> <span class="token operator">|</span>key<span class="token punctuation">,</span> value<span class="token operator">|</span> send<span class="token punctuation">(</span><span class="token symbol">:"#&#123;key&#125;="</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这个方法主要是将各个模块中的定义的实例方法 <code>inclue</code> 到 model 中，将类方法 <code>extend</code> 到 model 中。还有一个重点是为 model 定义了一个 <code>User.devise_modules</code> 方法</p>
<h3 id="strategies"><a href="#strategies" class="headerlink" title="strategies"></a>strategies</h3><pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Devise</span>
  <span class="token keyword">class</span> <span class="token class-name">Mapping</span>
    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">modules</span></span>
      <span class="token variable">@modules</span> <span class="token operator">||=</span> to<span class="token punctuation">.</span>respond_to<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:devise_modules</span><span class="token punctuation">)</span> <span class="token operator">?</span> to<span class="token punctuation">.</span>devise_modules <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">strategies</span></span>
      <span class="token variable">@strategies</span> <span class="token operator">||=</span> <span class="token constant">STRATEGIES</span><span class="token punctuation">.</span>values_at<span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">self</span><span class="token punctuation">.</span>modules<span class="token punctuation">)</span><span class="token punctuation">.</span>compact<span class="token punctuation">.</span>uniq<span class="token punctuation">.</span>reverse
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>每一个 <a href="#devise-add-mapping">mapping 对象</a> 都会有一个 <code>#strategies</code> 方法，判断 <code>User.devise_modules</code> 是否包含 <code>Devise::STRATEGIES</code> 中的模块</p>
<p>当应用启动路由初始化完成后调用会调用 <code>Devise.configure_warden!</code> 方法</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">configure_warden</span></span><span class="token operator">!</span> <span class="token comment">#:nodoc:</span>
  <span class="token variable">@@warden_configured</span> <span class="token operator">||=</span> <span class="token keyword">begin</span>
    warden_config<span class="token punctuation">.</span>failure_app   <span class="token operator">=</span> Devise<span class="token double-colon punctuation">::</span><span class="token class-name">Delegator</span><span class="token punctuation">.</span><span class="token keyword">new</span>
    warden_config<span class="token punctuation">.</span>default_scope <span class="token operator">=</span> Devise<span class="token punctuation">.</span>default_scope
    warden_config<span class="token punctuation">.</span>intercept_401 <span class="token operator">=</span> <span class="token boolean">false</span>

    Devise<span class="token punctuation">.</span>mappings<span class="token punctuation">.</span>each_value <span class="token keyword">do</span> <span class="token operator">|</span>mapping<span class="token operator">|</span>
      warden_config<span class="token punctuation">.</span>scope_defaults mapping<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token symbol">strategies</span><span class="token operator">:</span> mapping<span class="token punctuation">.</span>strategies

      warden_config<span class="token punctuation">.</span>serialize_into_session<span class="token punctuation">(</span>mapping<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>record<span class="token operator">|</span>
        mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span>serialize_into_session<span class="token punctuation">(</span>record<span class="token punctuation">)</span>
      <span class="token keyword">end</span>

      warden_config<span class="token punctuation">.</span>serialize_from_session<span class="token punctuation">(</span>mapping<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>args<span class="token operator">|</span>
        mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span>serialize_from_session<span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token variable">@@warden_config_blocks</span><span class="token punctuation">.</span>map <span class="token punctuation">&#123;</span> <span class="token operator">|</span>block<span class="token operator">|</span> block<span class="token punctuation">.</span>call Devise<span class="token punctuation">.</span>warden_config <span class="token punctuation">&#125;</span>
    <span class="token boolean">true</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这个方法会将每一个 <a href="#devise-add-mapping">mapping 对象</a> 的 <code>#strategies</code> 定义到对应的 <code>scope_defaults</code> 中。</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">default_strategies</span></span><span class="token punctuation">(</span><span class="token operator">*</span>strategies<span class="token punctuation">)</span>
  opts  <span class="token operator">=</span> <span class="token builtin">Hash</span> <span class="token operator">===</span> strategies<span class="token punctuation">.</span>last <span class="token operator">?</span> strategies<span class="token punctuation">.</span>pop <span class="token operator">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>
  hash  <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">[</span><span class="token symbol">:default_strategies</span><span class="token punctuation">]</span>
  scope <span class="token operator">=</span> opts<span class="token punctuation">[</span><span class="token symbol">:scope</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token symbol">:_all</span>

  hash<span class="token punctuation">[</span>scope<span class="token punctuation">]</span> <span class="token operator">=</span> strategies<span class="token punctuation">.</span>flatten <span class="token keyword">unless</span> strategies<span class="token punctuation">.</span>empty<span class="token operator">?</span>
  hash<span class="token punctuation">[</span>scope<span class="token punctuation">]</span> <span class="token operator">||</span> hash<span class="token punctuation">[</span><span class="token symbol">:_all</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">end</span>

<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">scope_defaults</span></span><span class="token punctuation">(</span>scope<span class="token punctuation">,</span> opts <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  <span class="token keyword">if</span> strategies <span class="token operator">=</span> opts<span class="token punctuation">.</span>delete<span class="token punctuation">(</span><span class="token symbol">:strategies</span><span class="token punctuation">)</span>
    default_strategies<span class="token punctuation">(</span>strategies<span class="token punctuation">,</span> <span class="token symbol">:scope</span> <span class="token operator">=></span> scope<span class="token punctuation">)</span>
  <span class="token keyword">end</span>
  <span class="token operator">...</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>scope_defaults</code> 方法又会将 <code>strategies</code> 最终存储在 <code>default_strategies</code>，故 <a href="#authenticate-default-strategies">验证时最终会查找 default_strategies</a></p>
<h3 id="Devise-Strategies"><a href="#Devise-Strategies" class="headerlink" title="Devise::Strategies"></a>Devise::Strategies</h3><p>在 Devise 中默认有两个策略</p>
<ul>
<li><code>Devise::Strategies::DatabaseAuthenticatable</code></li>
<li><code>Devise::Strategies::Rememberable</code></li>
</ul>
<h4 id="DatabaseAuthenticatable"><a href="#DatabaseAuthenticatable" class="headerlink" title="DatabaseAuthenticatable"></a>DatabaseAuthenticatable</h4><p><code>lib/devise/strategies/database_authenticatable.rb</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">authenticate</span></span><span class="token operator">!</span>
  resource  <span class="token operator">=</span> password<span class="token punctuation">.</span>present<span class="token operator">?</span> <span class="token operator">&amp;&amp;</span> mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span>find_for_database_authentication<span class="token punctuation">(</span>authentication_hash<span class="token punctuation">)</span>
  hashed <span class="token operator">=</span> <span class="token boolean">false</span>

  <span class="token keyword">if</span> validate<span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">&#123;</span> hashed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> resource<span class="token punctuation">.</span>valid_password<span class="token operator">?</span><span class="token punctuation">(</span>password<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span>
    remember_me<span class="token punctuation">(</span>resource<span class="token punctuation">)</span>
    resource<span class="token punctuation">.</span>after_database_authentication
    success<span class="token operator">!</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span>
  <span class="token keyword">end</span>

  mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>password <span class="token operator">=</span> password <span class="token keyword">if</span> <span class="token operator">!</span>hashed <span class="token operator">&amp;&amp;</span> Devise<span class="token punctuation">.</span>paranoid
  <span class="token keyword">unless</span> resource
    Devise<span class="token punctuation">.</span>paranoid <span class="token operator">?</span> fail<span class="token punctuation">(</span><span class="token symbol">:invalid</span><span class="token punctuation">)</span> <span class="token operator">:</span> fail<span class="token punctuation">(</span><span class="token symbol">:not_found_in_database</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>database_authenticatable 策略首先会从数据库中查找是否有这个用户，然后会校验密码是否正确，中间会用 BCrypt 进行密码的哈希运算比对。</p>
<h4 id="Rememberable"><a href="#Rememberable" class="headerlink" title="Rememberable"></a>Rememberable</h4><p><code>lib/devise/strategies/rememberable.rb</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">authenticate</span></span><span class="token operator">!</span>
  resource <span class="token operator">=</span> mapping<span class="token punctuation">.</span>to<span class="token punctuation">.</span>serialize_from_cookie<span class="token punctuation">(</span><span class="token operator">*</span>remember_cookie<span class="token punctuation">)</span>

  <span class="token keyword">unless</span> resource
    cookies<span class="token punctuation">.</span>delete<span class="token punctuation">(</span>remember_key<span class="token punctuation">)</span>
    <span class="token keyword">return</span> pass
  <span class="token keyword">end</span>

  <span class="token keyword">if</span> validate<span class="token punctuation">(</span>resource<span class="token punctuation">)</span>
    remember_me<span class="token punctuation">(</span>resource<span class="token punctuation">)</span> <span class="token keyword">if</span> extend_remember_me<span class="token operator">?</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span>
    resource<span class="token punctuation">.</span>after_remembered
    success<span class="token operator">!</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>rememberable 策略会从 cookie 中获取用户的 id 以及 remember_token 和过期时间，判断用户的 token 是否匹配以及是否过期</p>
<h4 id="自定义验证策略"><a href="#自定义验证策略" class="headerlink" title="自定义验证策略"></a>自定义验证策略</h4><p>我们也可以自定义 strategy</p>
<ol>
<li>strategy 必须继承自 <code>Warden::Strategies::Base</code>，每一个 strategy 必须实现 <code>#authenticate!</code> 方法</li>
<li>调用 <code>Warden::Strategies.add</code> 方法，将 strategy 注册到 Warden 中</li>
<li>在 controller 中调用 <code>before_action :authenticate_user!, [自定义策略名]</code> 进行验证</li>
</ol>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>devise 通过将自身模块化，实现了功能的解藕，当我们需要使用某些模块时，只需要 <code>devise</code> 导入就行。在验证方面 devise 默认提供两个策略，而这些策略的执行是由 devise 中抽象出来的一个中间件 warden 来处理，又进一步解藕了策略与执行，我们可以随时增添策略，也可以指定是否运行某一个策略，甚至实现自己的 <code>strategy</code>。</p>
<p>在验证失败时，devise 使用 throw 和 catch 语句跳出后续 <code>controller#action</code> 的执行，直接处理失败响应，这里又一次将失败处理与 devise 解藕，我们可以选择使用默认的 <code>Devise::FailureApp</code> 也可以自己实现一个 <code>FailureApp</code></p>
]]></content>
      <categories>
        <category>后端</category>
        <category>ruby</category>
      </categories>
      <tags>
        <tag>study</tag>
      </tags>
  </entry>
  <entry>
    <title>在 vscode 配置 ruby 开发环境</title>
    <url>/2020/10/ruby-in-vscode/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>常在河边走，哪有不湿鞋  –鲁迅</p>
</blockquote>
<p>作为多年使用 JetBrains 盗版软件的不法用户，昨日终于被逮到了，经历各种再破解尝试，均告失败。<br>只好转战最近广受好评的 vscode，准备接下来试试能不能作为主力开发工具。<br>本篇记录如何在 vscode 配置开发 ruby 环境</p>
<span id="more"></span>

<h2 id="安装-vscode"><a href="#安装-vscode" class="headerlink" title="安装 vscode"></a>安装 vscode</h2><p>vscode 的安装就不赘述了，在官网直接下载，傻瓜式安装就 ok 了。</p>
<h2 id="配置-vscode"><a href="#配置-vscode" class="headerlink" title="配置 vscode"></a>配置 vscode</h2><h3 id="安装配置扩展-ruby"><a href="#安装配置扩展-ruby" class="headerlink" title="安装配置扩展 ruby"></a>安装配置扩展 ruby</h3><ol>
<li><p>这个扩展名称就叫 ruby（作者吕鹏，vscode官方开发人员），在 vscode 应用商店中直接搜索安装就好</p>
</li>
<li><p>在 vscode 配置文件 <code>settings.json</code> 中添加如下</p>
 <pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token property">"ruby.useBundler"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token property">"ruby.lint"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
  <span class="token property">"rubocop"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
    <span class="token property">"useBundler"</span><span class="token operator">:</span> <span class="token boolean">true</span>
  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
<span class="token property">"ruby.format"</span><span class="token operator">:</span> <span class="token string">"rubocop"</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

</li>
</ol>
<h3 id="安装配置扩展-Ruby-Solargraph"><a href="#安装配置扩展-Ruby-Solargraph" class="headerlink" title="安装配置扩展 Ruby Solargraph"></a>安装配置扩展 Ruby Solargraph</h3><ol>
<li><p>在 vscode 应用商店搜索安装</p>
</li>
<li><p>在 <code>settings.json</code> 中添加</p>
 <pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token property">"solargraph.useBundler"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token property">"solargraph.references"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token property">"solargraph.autoformat"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token property">"solargraph.formatting"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

</li>
</ol>
<h2 id="配置运行环境"><a href="#配置运行环境" class="headerlink" title="配置运行环境"></a>配置运行环境</h2><p>在项目目录下新建 <code>.vscode/launch.js</code></p>
<pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>
  <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"0.2.0"</span><span class="token punctuation">,</span>
  <span class="token property">"configurations"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">&#123;</span>
      <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Rails server"</span><span class="token punctuation">,</span>
      <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Ruby"</span><span class="token punctuation">,</span>
      <span class="token property">"request"</span><span class="token operator">:</span> <span class="token string">"launch"</span><span class="token punctuation">,</span>
      <span class="token property">"program"</span><span class="token operator">:</span> <span class="token string">"$&#123;workspaceRoot&#125;/bin/rails"</span><span class="token punctuation">,</span>
      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token string">"server"</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token property">"env"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
        <span class="token property">"PATH"</span><span class="token operator">:</span> <span class="token string">"xxxx"</span><span class="token punctuation">,</span>
        <span class="token property">"GEM_HOME"</span><span class="token operator">:</span> <span class="token string">"xxx"</span><span class="token punctuation">,</span>
        <span class="token property">"GEM_PATH"</span><span class="token operator">:</span> <span class="token string">"xxx"</span><span class="token punctuation">,</span>
        <span class="token property">"RUBY_VERSION"</span><span class="token operator">:</span> <span class="token string">"xxx"</span>
      <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>env 详细内容请在 shell 使用下列命令输出</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token builtin class-name">printf</span> <span class="token string">"<span class="token entity" title="\n">\n</span><span class="token entity" title="\&quot;">\"</span>env<span class="token entity" title="\&quot;">\"</span>: &#123;<span class="token entity" title="\n">\n</span>  <span class="token entity" title="\&quot;">\"</span>PATH<span class="token entity" title="\&quot;">\"</span>: <span class="token entity" title="\&quot;">\"</span><span class="token environment constant">$PATH</span><span class="token entity" title="\&quot;">\"</span>,<span class="token entity" title="\n">\n</span>  <span class="token entity" title="\&quot;">\"</span>GEM_HOME<span class="token entity" title="\&quot;">\"</span>: <span class="token entity" title="\&quot;">\"</span><span class="token variable">$GEM_HOME</span><span class="token entity" title="\&quot;">\"</span>,<span class="token entity" title="\n">\n</span>  <span class="token entity" title="\&quot;">\"</span>GEM_PATH<span class="token entity" title="\&quot;">\"</span>: <span class="token entity" title="\&quot;">\"</span><span class="token variable">$GEM_PATH</span><span class="token entity" title="\&quot;">\"</span>,<span class="token entity" title="\n">\n</span>  <span class="token entity" title="\&quot;">\"</span>RUBY_VERSION<span class="token entity" title="\&quot;">\"</span>: <span class="token entity" title="\&quot;">\"</span><span class="token variable">$RUBY_VERSION</span><span class="token entity" title="\&quot;">\"</span><span class="token entity" title="\n">\n</span>&#125;<span class="token entity" title="\n">\n</span><span class="token entity" title="\n">\n</span>"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h2 id="安装-gem"><a href="#安装-gem" class="headerlink" title="安装 gem"></a>安装 gem</h2><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">gem <span class="token function">install</span> ruby-debug-ide
gem <span class="token function">install</span> debase
gem <span class="token function">install</span> solargraph<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h2 id="如何打开-settings-json"><a href="#如何打开-settings-json" class="headerlink" title="如何打开 settings.json"></a>如何打开 <code>settings.json</code></h2><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201028113907.png" alt="20201028113907"></p>
]]></content>
      <categories>
        <category>后端</category>
        <category>ruby</category>
      </categories>
      <tags>
        <tag>vscode</tag>
        <tag>开发环境</tag>
      </tags>
  </entry>
  <entry>
    <title>Ruby 是如何实现多重继承的</title>
    <url>/2020/10/ruby-include-implementation/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Ruby 用了也接近一年了，有很多黑魔法让我感到非常有意思，刚开始学习的时候也经常让我不知所措。<br>其中很有意思的一点就是 <code>include</code> 和 <code>extend</code> 这两个方法来变相实现 <code>多重继承</code>，即代码的复用。但是用归用，却没有细细的研究其中的实现，故此，今天好好的看一看。</p>
<span id="more"></span>

<h2 id="include"><a href="#include" class="headerlink" title="include"></a>include</h2><p><code>include</code> 方法在 <code>Ruby</code> 中会被 <code>include</code> 模块的方法添加为当前模块下的实例方法</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Person</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">name</span></span>
    p <span class="token string-literal"><span class="token string">'My Name Is Person'</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">module</span> <span class="token class-name">User</span>
  <span class="token keyword">include</span> Person
<span class="token keyword">end</span>

<span class="token class-name">User</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>name
<span class="token comment"># My Name Is Person</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这样，<code>Person</code> 就可以被随意引用，使用其中的实例方法 <code>name</code>。<br>然而，这是怎么做到的呢，定义在 <code>Person</code> 中的 <code>name</code> 为什么能被 <code>User</code> 的实例调用呢？  </p>
<p>那这就需要看看 <code>include</code> 的源码了</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">Module</span>
  <span class="token comment"># include(module, ...)    -> self</span>
  <span class="token comment"># </span>
  <span class="token comment"># Invokes &lt;code>Module.append_features&lt;/code> on each parameter in reverse order.</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">include</span></span><span class="token punctuation">(</span>module1<span class="token punctuation">,</span> <span class="token operator">*</span>smth<span class="token punctuation">)</span>
    <span class="token comment"># This is a stub, used for indexing</span>
  <span class="token keyword">end</span>
<span class="token operator">...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="append-features"><a href="#append-features" class="headerlink" title="append_features"></a>append_features</h3><p><code>Ruby</code> 源码由 <code>C</code> 写成，所以我们只能看文档，文档中很清楚的指明了调用 <code>include</code> 方法会触发 <code>append_features</code> 方法，那我们就看看 <code>append_features</code> 的源码</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># append_features(mod)   -> mod</span>
<span class="token comment"># </span>
<span class="token comment"># When this module is included in another, Ruby calls</span>
<span class="token comment"># &lt;code>append_features&lt;/code> in this module, passing it the</span>
<span class="token comment"># receiving module in _mod_. Ruby's default implementation is</span>
<span class="token comment"># to add the constants, methods, and module variables of this module</span>
<span class="token comment"># to _mod_ if this module has not already been added to</span>
<span class="token comment"># _mod_ or one of its ancestors. See also &lt;code>Module#include&lt;/code>.</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">append_features</span></span><span class="token punctuation">(</span>mod<span class="token punctuation">)</span>
  <span class="token comment"># This is a stub, used for indexing</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<ol>
<li>当一个 <code>module</code> 作为 <code>mixin</code> 被 <code>include</code> 时，会默认调用 <code>append_features</code></li>
<li>在 <code>Ruby</code> 中 <code>append_features</code> 的默认实现是将 <code>mixin</code> 中的 <code>constants</code>、<code>methods</code> 和 <code>module variables</code> 添加到需要 <code>include</code> 的 <code>module</code> 中。  </li>
<li>所以当 <code>append_features</code> 执行后，<code>Person</code> <code>module</code> 下的 <code>name</code> 被添加到了 <code>User</code> 中</li>
</ol>
<h4 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h4><p>重写 <code>append_features</code> 方法：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Person</span>
  <span class="token keyword">extend</span> ActiveSupport<span class="token double-colon punctuation">::</span>Concern

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">name</span></span>
    p <span class="token string-literal"><span class="token string">'My Name Is Person'</span></span>
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">append_features</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token class-name">User</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>name
<span class="token comment"># undefined method `name' for #&lt;User:0x00007f84b918c898> (NoMethodError)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>很显然，<code>append_features</code> 被覆盖，导致 <code>name</code> 没有被添加到 <code>User</code> 中</p>
<h3 id="included：include-的回调"><a href="#included：include-的回调" class="headerlink" title="included：include 的回调"></a>included：include 的回调</h3><p><code>append_features</code> 的实现满足了我们对实例方法的代码重用，但是没有实现对类方法的重用，如果我们想在 <code>include</code> 时同时导入类方法，或者其他操作，我们可以选择重写 <code>append_features</code>。但是在 <code>Ruby</code> 中并不建议直接重写 <code>append_features</code>，而是给我们提供了简单便捷的钩子方法 <code>included</code>。</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># included(othermod)</span>
<span class="token comment"># </span>
<span class="token comment"># Callback invoked whenever the receiver is included in another</span>
<span class="token comment"># module or class. This should be used in preference to</span>
<span class="token comment"># &lt;tt>Module.append_features&lt;/tt> if your code wants to perform some</span>
<span class="token comment"># action when a module is included in another.</span>
<span class="token comment"># </span>
<span class="token comment">#        module A</span>
<span class="token comment">#          def A.included(mod)</span>
<span class="token comment">#            puts "#&#123;self&#125; included in #&#123;mod&#125;"</span>
<span class="token comment">#          end</span>
<span class="token comment">#        end</span>
<span class="token comment">#        module Enumerable</span>
<span class="token comment">#          include A</span>
<span class="token comment">#        end</span>
<span class="token comment">#         # => prints "A included in Enumerable"</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">included</span></span><span class="token punctuation">(</span>othermod<span class="token punctuation">)</span>
<span class="token comment"># This is a stub, used for indexing</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>如果我们想要导入类方法，重写刚刚的 <code>Person</code> 类：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Person</span>
  <span class="token comment"># 与 included 做对比</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">run</span></span>
    p <span class="token string-literal"><span class="token string">'I Can Run'</span></span>
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">eat</span></span>
        p <span class="token string-literal"><span class="token string">'I Can Eat'</span></span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

User<span class="token punctuation">.</span>eat
<span class="token comment"># I Can Eat</span>
User<span class="token punctuation">.</span>run
<span class="token comment"># undefined method `run' for User:Class (NoMethodError)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="Rails-是如何扩展的"><a href="#Rails-是如何扩展的" class="headerlink" title="Rails 是如何扩展的"></a>Rails 是如何扩展的</h3><p><code>Rails</code> 中有一个非常好用的库 <code>ActiveSupport</code>，对 <code>Ruby</code> 语言的很多特性都实现了扩展，今天要看的就是其中 <code>ActiveSupport::Concern</code> 是如何扩展 <code>include</code> 的</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># 所有注释都被我删除了，有兴趣的请查看源码</span>
<span class="token keyword">module</span> <span class="token class-name">ActiveSupport</span>
  <span class="token keyword">module</span> <span class="token class-name">Concern</span>
    <span class="token keyword">class</span> <span class="token class-name">MultipleIncludedBlocks</span> <span class="token operator">&lt;</span> StandardError
      <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span>
        <span class="token keyword">super</span> <span class="token string-literal"><span class="token string">"Cannot define multiple 'included' blocks for a Concern"</span></span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">extended</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
      base<span class="token punctuation">.</span>instance_variable_set<span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">append_features</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
      <span class="token keyword">if</span> base<span class="token punctuation">.</span>instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">)</span>
        base<span class="token punctuation">.</span>instance_variable_get<span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token keyword">self</span>
        <span class="token boolean">false</span>
      <span class="token keyword">else</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token keyword">if</span> base <span class="token operator">&lt;</span> <span class="token keyword">self</span>
        <span class="token variable">@_dependencies</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token punctuation">&#123;</span> <span class="token operator">|</span>dep<span class="token operator">|</span> base<span class="token punctuation">.</span><span class="token keyword">include</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span>
        <span class="token keyword">super</span>
        base<span class="token punctuation">.</span><span class="token keyword">extend</span> const_get<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span> <span class="token keyword">if</span> const_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span>
        base<span class="token punctuation">.</span>class_eval<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span> <span class="token keyword">if</span> instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">included</span></span><span class="token punctuation">(</span>base <span class="token operator">=</span> <span class="token keyword">nil</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>block<span class="token punctuation">)</span>
      <span class="token keyword">if</span> base<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span>
        <span class="token keyword">if</span> instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span>
          <span class="token keyword">if</span> <span class="token variable">@_included_block</span><span class="token punctuation">.</span>source_location <span class="token operator">!=</span> block<span class="token punctuation">.</span>source_location
            <span class="token keyword">raise</span> MultipleIncludedBlocks
          <span class="token keyword">end</span>
        <span class="token keyword">else</span>
          <span class="token variable">@_included_block</span> <span class="token operator">=</span> block
        <span class="token keyword">end</span>
      <span class="token keyword">else</span>
        <span class="token keyword">super</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">class_methods</span></span><span class="token punctuation">(</span><span class="token operator">&amp;</span>class_methods_module_definition<span class="token punctuation">)</span>
      mod <span class="token operator">=</span> const_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token operator">?</span>
        const_get<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span> <span class="token operator">:</span>
        const_set<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">,</span> <span class="token class-name">Module</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">)</span>

      mod<span class="token punctuation">.</span>module_eval<span class="token punctuation">(</span><span class="token operator">&amp;</span>class_methods_module_definition<span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>Concern</code> 的源码非常简单，总共就定义了 3 个实例方法，1 个类方法，1 个错误，其中 <code>append_features</code> 和 <code>included</code> 方法刚刚讲过，我们来看看 Rails 做了什么</p>
<h4 id="included：更优雅的-included"><a href="#included：更优雅的-included" class="headerlink" title="included：更优雅的 included"></a>included：更优雅的 included</h4><pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">included</span></span><span class="token punctuation">(</span>base <span class="token operator">=</span> <span class="token keyword">nil</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>block<span class="token punctuation">)</span>
  <span class="token keyword">if</span> base<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span>
    <span class="token keyword">if</span> instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token variable">@_included_block</span><span class="token punctuation">.</span>source_location <span class="token operator">!=</span> block<span class="token punctuation">.</span>source_location
        <span class="token keyword">raise</span> MultipleIncludedBlocks
      <span class="token keyword">end</span>
    <span class="token keyword">else</span>
      <span class="token variable">@_included_block</span> <span class="token operator">=</span> block
    <span class="token keyword">end</span>
  <span class="token keyword">else</span>
    <span class="token keyword">super</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>Rails 对 <code>included</code> 进行的扩展其实很简单</p>
<ol>
<li><code>included</code> 继续作为 <code>include</code> 的回调使用时，<code>base</code> 不为 <code>nil</code>，则执行 <code>super</code></li>
<li><code>included</code> 作为 <code>mixin</code> 中的类方法被调用，并传入一个 <code>block</code>，则将该 <code>block</code> 存入 <code>@_included_block</code>，并且不允许有多个 <code>@_included_block</code> 存在</li>
</ol>
<p>这么做的目的是，使 <code>mixin</code> 可以通过 <code>included</code> 方法定义 <code>def self.included</code>，让代码更优雅</p>
<p>比如，如果使用 <code>def slef.included</code> 方法</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Student</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">study</span></span>
        p <span class="token string-literal"><span class="token string">'I Can Study'</span></span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">include</span> Student
<span class="token keyword">end</span>

User<span class="token punctuation">.</span>study
<span class="token comment"># I Can Study</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>而使用 <code>included</code> 方法就可以这样写</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'active_support/concern'</span></span>

<span class="token keyword">module</span> <span class="token class-name">Student</span>
  <span class="token keyword">extend</span> ActiveSupport<span class="token double-colon punctuation">::</span>Concern

  included <span class="token keyword">do</span>
    <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">study</span></span>
      p <span class="token string-literal"><span class="token string">'I Can Study'</span></span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">include</span> Student
<span class="token keyword">end</span>

User<span class="token punctuation">.</span>study
<span class="token comment"># I Can Study</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="class-methods：更简洁的类方法定义"><a href="#class-methods：更简洁的类方法定义" class="headerlink" title="class_methods：更简洁的类方法定义"></a>class_methods：更简洁的类方法定义</h4><p>在上面我们看见了 <code>included</code> 是如何更优雅的实现类方法的定义的，而 Rails 还顺便提供了一个更简单的 magic 方法 <code>class_methods</code></p>
<p>使用：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'active_support/concern'</span></span>

<span class="token keyword">module</span> <span class="token class-name">Foo</span>
  <span class="token keyword">extend</span> Concern

  class_methods <span class="token keyword">do</span>
    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">run</span></span>
      <span class="token string-literal"><span class="token string">'I Can Run'</span></span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">Bar</span>
  <span class="token keyword">include</span> Foo
<span class="token keyword">end</span>

p Bar<span class="token punctuation">.</span>run
<span class="token comment"># I Can Run</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>源码：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">class_methods</span></span><span class="token punctuation">(</span><span class="token operator">&amp;</span>class_methods_module_definition<span class="token punctuation">)</span>
  mod <span class="token operator">=</span> const_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token operator">?</span>
    const_get<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span> <span class="token operator">:</span>
    const_set<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">,</span> <span class="token class-name">Module</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">)</span>

  mod<span class="token punctuation">.</span>module_eval<span class="token punctuation">(</span><span class="token operator">&amp;</span>class_methods_module_definition<span class="token punctuation">)</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>class_methods</code> 的实现比 <code>included</code> 更简单，就是将 <code>block</code> 转换成一个新的 <code>module ClassMethods</code>，而如何注入则依赖于 <code>append_features</code> 来实现</p>
<h4 id="append-features：解决内部依赖"><a href="#append-features：解决内部依赖" class="headerlink" title="append_features：解决内部依赖"></a>append_features：解决内部依赖</h4><p>Rails 使用 <code>append_features</code> 解决了一个非常重要的问题，那就是内部依赖的引入</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Foo</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">test</span></span>
        <span class="token string-literal"><span class="token string">'test'</span></span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">module</span> <span class="token class-name">Bar</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      p test
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">include</span> Foo
  <span class="token keyword">include</span> Bar
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>如果 A <code>mixin</code> 依赖于 B <code>mixin</code> 中 <code>included</code> 定义的方法，那引入的时候就必须同时引入 <code>A</code> 和 <code>B</code>，这就要求开发者必须对每一个 <code>mixin</code> 的引入都要非常熟悉才能保证不出错。<br>而 <code>ActiveSupport::Concern</code> 通过 <code>append_features</code> 解决了这个问题</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'active_support/concern'</span></span>

<span class="token keyword">module</span> <span class="token class-name">Foo</span>
  <span class="token keyword">extend</span> ActiveSupport<span class="token double-colon punctuation">::</span>Concern

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">test</span></span>
        <span class="token string-literal"><span class="token string">'test'</span></span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">module</span> <span class="token class-name">Bar</span>
  <span class="token keyword">extend</span> ActiveSupport<span class="token double-colon punctuation">::</span>Concern
  <span class="token keyword">include</span> Foo

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">included</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    base<span class="token punctuation">.</span>class_eval <span class="token keyword">do</span>
      p test
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">include</span> Bar
<span class="token keyword">end</span>

<span class="token comment"># test</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>像这样的话，开发者只需要引入 <code>Bar</code>，而不再需要管 <code>Bar</code> 中 <code>include</code> 了几个模块。</p>
<p>而 <code>Rails</code> 中是如何这么聪明的实现的呢，源码如下：</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token comment"># 1. Foo 和 Bar 使用 extend ActiveSupport::Concern 时，定义实例变量 @_dependencies = []</span>
<span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">extended</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span> <span class="token comment">#:nodoc:</span>
  base<span class="token punctuation">.</span>instance_variable_set<span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">end</span>

<span class="token keyword">def</span> <span class="token method-definition"><span class="token function">append_features</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
  <span class="token keyword">if</span> base<span class="token punctuation">.</span>instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">)</span>
    <span class="token comment"># 2. 当 Bar include Foo 时触发 Foo 的 append_features，发现 Bar 已定义实例变量 @_dependencies</span>
    <span class="token comment"># 3. 将当前模块 Foo 添加入 Bar 的 @_dependencies</span>
    base<span class="token punctuation">.</span>instance_variable_get<span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_dependencies</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token keyword">self</span>
    <span class="token boolean">false</span>
  <span class="token keyword">else</span>
    <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token keyword">if</span> base <span class="token operator">&lt;</span> <span class="token keyword">self</span>
    <span class="token comment"># 4. 当 User include Bar 时 @_dependencies 中的所有模块（Foo）被 User 同时 include</span>
    <span class="token variable">@_dependencies</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token punctuation">&#123;</span> <span class="token operator">|</span>dep<span class="token operator">|</span> base<span class="token punctuation">.</span><span class="token keyword">include</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span>
    <span class="token keyword">super</span>
    <span class="token comment"># 配合 class_methods 方法实现类方法的注入</span>
    base<span class="token punctuation">.</span><span class="token keyword">extend</span> const_get<span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span> <span class="token keyword">if</span> const_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:ClassMethods</span><span class="token punctuation">)</span>
    <span class="token comment"># 配合 included 方法实现 self.included</span>
    base<span class="token punctuation">.</span>class_eval<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span> <span class="token keyword">if</span> instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token variable">@_included_block</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>而 <code>append_features</code> 除了解决了内部依赖的引入，还实现了两个 magic 方法 <code>included</code> 和 <code>class_methods</code> 的注入</p>
<h2 id="extend"><a href="#extend" class="headerlink" title="extend"></a>extend</h2><p><code>extend</code> 方法在 <code>Ruby</code> 中会被 <code>extend</code> 模块的方法添加为当前模块下的类方法</p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Person</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">name</span></span>
    <span class="token string-literal"><span class="token string">"My name is Person"</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">extend</span> Person
<span class="token keyword">end</span>

p User<span class="token punctuation">.</span>name
<span class="token comment"># My name is Person</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="extended：extend-的回调"><a href="#extended：extend-的回调" class="headerlink" title="extended：extend 的回调"></a>extended：extend 的回调</h3><p>在 <code>extend</code> 之后如果需要执行某些回调，则只需要定义 <code>self.extended</code></p>
<pre class="line-numbers language-ruby" data-language="ruby"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Person</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">extended</span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>
    p <span class="token string-literal"><span class="token string">"</span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content">base</span><span class="token delimiter punctuation">&#125;</span></span><span class="token string"> extended </span><span class="token interpolation"><span class="token delimiter punctuation">#&#123;</span><span class="token content"><span class="token keyword">self</span></span><span class="token delimiter punctuation">&#125;</span></span><span class="token string">"</span></span>
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">name</span></span>
    <span class="token string-literal"><span class="token string">"My name is Person"</span></span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">class</span> <span class="token class-name">User</span>
  <span class="token keyword">extend</span> Person
<span class="token keyword">end</span>
<span class="token comment"># User extended Person</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://ruby-china.org/topics/25397">Ruby 中一些重要的钩子方法</a></p>
]]></content>
      <categories>
        <category>后端</category>
        <category>ruby</category>
      </categories>
      <tags>
        <tag>think</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 Docker 轻松运行 EFB (EhForwarderBot)</title>
    <url>/2023/01/run-efb-in-docker/</url>
    <content><![CDATA[<h2 id="什么是-EFB？为什么要用-Docker？"><a href="#什么是-EFB？为什么要用-Docker？" class="headerlink" title="什么是 EFB？为什么要用 Docker？"></a>什么是 EFB？为什么要用 Docker？</h2><p><strong>EFB (EhForwarderBot)</strong> 是一个强大的工具，可以帮你把微信消息转发到 Telegram 等其他聊天平台，也可以让你在 Telegram 里直接回复微信消息。简单来说，就是让你在一个地方管理多个聊天工具的消息。</p>
<p><strong>Docker</strong> 又是什么呢？你可以把它想象成一个“魔法集装箱”。我们把 EFB 和它运行需要的所有东西都打包到这个集装箱里。这样，无论你的服务器是什么环境，只要装上 Docker，就能轻松运行 EFB，省去了很多复杂的安装和配置步骤。</p>
<p>这篇教程会教你如何使用 Docker 来运行 EFB。</p>
<blockquote>
<p><strong>背景小故事</strong>：之前我用的微信网页版登录方式 (uos 补丁) 失效了，而且账号还收到了警告，所以我切换到了一个叫做 <code>comwechat</code> 的新方案。下面会介绍这两种方案的区别。</p>
</blockquote>
<h2 id="微信接入方案：comwechat-vs-wechat-web"><a href="#微信接入方案：comwechat-vs-wechat-web" class="headerlink" title="微信接入方案：comwechat vs wechat web"></a>微信接入方案：<code>comwechat</code> vs <code>wechat web</code></h2><p>EFB 连接微信主要有两种方式，你需要根据自己的情况选择一种：</p>
<ol>
<li><p><strong><code>wechat web</code> (微信网页版)</strong></p>
<ul>
<li>  <strong>优点</strong>：占用资源少，对服务器要求低，小内存 VPS 也能跑。</li>
<li>  <strong>缺点</strong>：功能相对较少，而且<strong>只有部分老微信账号（大约 2017 年前注册）才能登录网页版</strong>（我的账号就不行了 O.O）。你需要自己测试一下你的账号是否还能登录 <a href="https://wx.qq.com/">wx.qq.com</a>。</li>
<li>  <strong>原理</strong>：模拟浏览器登录微信网页版。</li>
</ul>
</li>
<li><p><strong><code>comwechat</code></strong></p>
<ul>
<li>  <strong>优点</strong>：功能更全，支持发送高清图片和 Telegram 贴纸。</li>
<li>  <strong>缺点</strong>：占用资源较多（内存建议 2GB 以上），因为它背后运行了一个模拟的 Windows 微信客户端环境。</li>
<li>  <strong>原理</strong>：基于一个开源项目，模拟了 Windows 微信客户端。</li>
</ul>
</li>
</ol>
<p><strong>主要功能区别：</strong></p>
<table>
<thead>
<tr>
<th align="left">功能</th>
<th align="left"><code>wechat web</code> (网页版)</th>
<th align="left"><code>comwechat</code> (模拟客户端)</th>
</tr>
</thead>
<tbody><tr>
<td align="left">撤回消息</td>
<td align="left">✅ 支持</td>
<td align="left">❌ 不支持</td>
</tr>
<tr>
<td align="left">防止撤回消息</td>
<td align="left">✅ 支持</td>
<td align="left">✅ 支持</td>
</tr>
<tr>
<td align="left">回复消息样式</td>
<td align="left">普通文本样式</td>
<td align="left">部分支持原生样式</td>
</tr>
<tr>
<td align="left">发送 Telegram 贴纸</td>
<td align="left">可能会被压缩</td>
<td align="left">✅ 支持</td>
</tr>
<tr>
<td align="left">发送高清图片</td>
<td align="left">大图会被压缩</td>
<td align="left">✅ 支持</td>
</tr>
<tr>
<td align="left">接收企业微信消息</td>
<td align="left">❌ 不支持</td>
<td align="left">✅ 支持</td>
</tr>
</tbody></table>
<p><strong>选择建议：</strong></p>
<ul>
<li>  如果你的微信账号还能登录网页版，并且对资源占用比较敏感，可以选择 <code>wechat web</code>。</li>
<li>  如果你的账号无法登录网页版，或者你需要更完整的功能（如发送高清图、贴纸），并且服务器资源充足（建议 2GB 内存以上），请选择 <code>comwechat</code>。</li>
</ul>
<span id="more"></span>

<h2 id="第一步：安装-Docker"><a href="#第一步：安装-Docker" class="headerlink" title="第一步：安装 Docker"></a>第一步：安装 Docker</h2><p>如果你的服务器上还没有安装 Docker，需要先安装它。Docker 是我们运行 EFB 的基础环境。</p>
<p>打开服务器的终端（命令行界面），复制并运行以下命令：</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console"># 这个命令会下载 Docker 的官方安装脚本
curl -fsSL https:&#x2F;&#x2F;get.docker.com -o get-docker.sh
# 这个命令会执行脚本来安装 Docker
sudo sh get-docker.sh
# 安装完成后，删除下载的脚本（可选）
rm get-docker.sh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<blockquote>
<p><strong>提示</strong>：<code>sudo</code> 表示需要管理员权限来执行安装。根据系统提示，可能需要输入你的服务器密码。</p>
</blockquote>
<h2 id="第二步：下载-EFB-配置模板"><a href="#第二步：下载-EFB-配置模板" class="headerlink" title="第二步：下载 EFB 配置模板"></a>第二步：下载 EFB 配置模板</h2><p>为了方便大家使用，我已经准备好了一份包含常用配置的模板。你只需要把它下载到你的服务器上。</p>
<p>继续在终端里运行：</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console"># 这个命令会从 GitHub 下载配置模板，并存放到名为 ehforwarderbot 的文件夹里
git clone https:&#x2F;&#x2F;github.com&#x2F;jiz4oh&#x2F;ehforwarderbot.git ehforwarderbot<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>下载下来的 <code>ehforwarderbot</code> 文件夹里大概是这样的结构（你暂时不需要完全弄懂每个文件的作用）：</p>
<pre class="line-numbers language-none"><code class="language-none">.
├── Dockerfile                  # Docker 构建镜像的说明书 (一般不用管)
├── README.md                   # 项目说明文件
├── docker-compose.web.yaml     # 用于启动 wechat web 版本的配置文件
├── docker-compose.yaml         # 用于启动 comwechat 版本的配置文件
├── entrypoint.sh               # Docker 容器启动时运行的脚本 (一般不用管)
├── profiles&#x2F;                   # 核心配置文件夹！存放不同账号的配置
│   ├── comwechat&#x2F;              # 第一个 comwechat 账号的配置
│   │   ├── ... (一些中间件配置，可以先忽略)
│   │   ├── blueset.telegram&#x2F;
│   │   │   └── config.yaml     # !! Telegram 配置，需要修改 !!
│   │   ├── config.yaml         # EFB 主配置 (一般不用改)
│   │   └── honus.comwechat&#x2F;
│   │       └── config.yaml     # comwechat 配置 (一般不用改)
│   ├── comwechat2&#x2F;             # 如果你有第二个微信账号想用 comwechat，可以复制 comwechat 文件夹并改名
│   │   └── ...
│   └── default&#x2F;                # wechat web 版本的配置 (网页版只需这一个)
│       ├── blueset.telegram&#x2F;
│       │   └── config.yaml     # !! Telegram 配置，需要修改 !!
│       ├── blueset.wechat&#x2F;
│       │   └── config.yaml     # 微信网页版配置 (可选修改)
│       └── config.yaml         # EFB 主配置 (一般不用改)
└── run2.py                     # (comwechat 辅助脚本，一般不用管)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<blockquote>
<p><strong>关键点</strong>：你需要关注的是 <code>profiles</code> 文件夹里的配置，特别是 <code>blueset.telegram/config.yaml</code> 文件。</p>
</blockquote>
<h2 id="第三步：修改你的专属配置"><a href="#第三步：修改你的专属配置" class="headerlink" title="第三步：修改你的专属配置"></a>第三步：修改你的专属配置</h2><p>现在，我们需要修改一些配置，主要是告诉 EFB 如何连接你的 Telegram 账号。</p>
<ol>
<li><p><strong>进入配置目录</strong></p>
 <pre class="line-numbers language-console" data-language="console"><code class="language-console">cd ehforwarderbot<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p><strong>修改 Telegram 配置</strong></p>
<ul>
<li><p>  <strong>如果你选择 <code>comwechat</code> 方案</strong>：你需要修改 <code>profiles/comwechat/blueset.telegram/config.yaml</code> 文件。</p>
</li>
<li><p><strong>如果你选择 <code>wechat web</code> 方案</strong>：你需要修改 <code>profiles/default/blueset.telegram/config.yaml</code> 文件。</p>
<p>使用你熟悉的编辑器（如 <code>nano</code> 或 <code>vim</code>）打开对应的 <code>config.yaml</code> 文件。例如，使用 <code>nano</code> 编辑 <code>comwechat</code> 的 Telegram 配置：</p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console">nano profiles&#x2F;comwechat&#x2F;blueset.telegram&#x2F;config.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>你需要修改文件里的 <code>token</code> 和 <code>admins</code> 两个地方：</p>
</li>
<li><p>  <code>token</code>: 填入你从 @BotFather 那里申请到的 Telegram Bot Token。</p>
</li>
<li><p><code>admins</code>: 填入你的 Telegram User ID (纯数字)。你可以发 <code>/getid</code> 给 @userinfobot 来获取。</p>
<p>修改完成后，保存并关闭文件（<code>nano</code> 编辑器中按 <code>Ctrl+X</code>，然后按 <code>Y</code>，再按 <code>Enter</code>）。</p>
</li>
</ul>
</li>
<li><p><strong>(可选) 修改微信配置</strong></p>
<ul>
<li>  如果你使用 <code>wechat web</code> 方案，可以按需修改 <code>profiles/default/blueset.wechat/config.yaml</code> 文件中的一些选项，但通常默认配置就可以工作。</li>
<li>  如果你使用 <code>comwechat</code> 方案，通常不需要修改 <code>profiles/comwechat/honus.comwechat/config.yaml</code>。</li>
</ul>
</li>
</ol>
<h2 id="第四步：启动-EFB！"><a href="#第四步：启动-EFB！" class="headerlink" title="第四步：启动 EFB！"></a>第四步：启动 EFB！</h2><p>配置完成！现在可以启动 EFB 了。请根据你选择的方案运行对应的命令：</p>
<p><strong>如果你选择 <code>comwechat</code> 方案：</strong></p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console"># 确保你当前在 ehforwarderbot 文件夹内
# 这个命令会根据 docker-compose.yaml 文件在后台启动 EFB
docker compose up -d<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p><strong>如果你选择 <code>wechat web</code> 方案：</strong></p>
<pre class="line-numbers language-console" data-language="console"><code class="language-console"># 确保你当前在 ehforwarderbot 文件夹内
# 这个命令会根据 docker-compose.web.yaml 文件在后台启动 EFB
# 注意这里用 -f 指定了不同的配置文件
docker compose -f .&#x2F;docker-compose.web.yaml up -d<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<blockquote>
<p><strong>提示</strong>：<code>up -d</code> 的意思是“启动并在后台运行”。</p>
</blockquote>
<p><strong>首次启动：登录微信</strong></p>
<ul>
<li>  **<code>comwechat</code>**：启动后，你需要查看容器日志来扫码登录。运行 <code>docker compose logs -f</code>，你会看到一个二维码链接或二维码图片（取决于你的终端支持情况）。用手机微信扫描该二维码进行登录。登录成功后按 <code>Ctrl+C</code> 退出日志查看。</li>
<li>  **<code>wechat web</code>**：同样运行 <code>docker compose -f ./docker-compose.web.yaml logs -f</code> 查看日志，扫描日志中出现的二维码登录。</li>
</ul>
<p>登录成功后，EFB 就会开始工作了！你的 Telegram Bot 应该会给你发送消息，并且微信消息也会开始转发过来。</p>
<h2 id="后续管理"><a href="#后续管理" class="headerlink" title="后续管理"></a>后续管理</h2><ul>
<li><strong>查看日志</strong>：<ul>
<li>  <code>comwechat</code>: <code>docker compose logs -f</code></li>
<li>  <code>wechat web</code>: <code>docker compose -f ./docker-compose.web.yaml logs -f</code></li>
</ul>
</li>
<li><strong>停止 EFB</strong>：<ul>
<li>  <code>comwechat</code>: <code>docker compose down</code></li>
<li>  <code>wechat web</code>: <code>docker compose -f ./docker-compose.web.yaml down</code></li>
</ul>
</li>
<li><strong>重启 EFB</strong>：<ul>
<li>  <code>comwechat</code>: <code>docker compose restart</code></li>
<li>  <code>wechat web</code>: <code>docker compose -f ./docker-compose.web.yaml restart</code></li>
</ul>
</li>
<li><strong>更新 EFB 镜像和配置</strong>：<ol>
<li> 进入 <code>ehforwarderbot</code> 目录。</li>
<li> <code>git pull</code> # 更新配置模板</li>
<li> <code>docker compose pull</code> 或 <code>docker compose -f ./docker-compose.web.yaml pull</code> # 拉取最新的 Docker 镜像</li>
<li> <code>docker compose down</code> 或 <code>docker compose -f ./docker-compose.web.yaml down</code> # 停止旧容器</li>
<li> <code>docker compose up -d</code> 或 <code>docker compose -f ./docker-compose.web.yaml up -d</code> # 使用新镜像和配置启动</li>
</ol>
</li>
</ul>
<p>现在，你应该可以通过 Docker 顺利运行 EFB 了！</p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>efb</tag>
        <tag>ehforwarderbot</tag>
        <tag>telegram</tag>
        <tag>wechat</tag>
      </tags>
  </entry>
  <entry>
    <title>EFB 新玩法：用 Telegram 话题（Topic）分类管理微信群消息</title>
    <url>/2025/04/set-topic-in-efb/</url>
    <content><![CDATA[<h2 id="背景知识"><a href="#背景知识" class="headerlink" title="背景知识"></a>背景知识</h2><ul>
<li><strong>EFB (EhForwarderBot) 是什么？</strong><br>简单来说，它是一个帮你把微信消息转发到 Telegram，并且能让你在 Telegram 里回复微信消息的工具。你需要自己准备服务器或者电脑来运行它。</li>
<li><strong>Telegram 话题（Topic）是什么？</strong><br>这是 Telegram 群组的一个功能。如果一个群组开启了话题功能，那么群里的消息就可以像论坛帖子一样，分成不同的话题进行讨论，让信息更有条理。只有群组管理员可以开启或关闭这个功能。</li>
</ul>
<h2 id="这个功能解决了什么问题？"><a href="#这个功能解决了什么问题？" class="headerlink" title="这个功能解决了什么问题？"></a>这个功能解决了什么问题？</h2><p>用 EFB 把微信消息转发到 Telegram 后，默认情况下，每一个微信群或者好友都需要你 <strong>手动</strong> 在 Telegram 里创建一个对应的群组。如果你的微信联系人很多，Telegram 里就会变得非常杂乱。</p>
<p>这个教程介绍的方法，利用了 Telegram 的<strong>话题（Topic）</strong>功能，可以将<strong>所有未单独绑定</strong>的微信群聊和私聊消息，都聚合到<strong>一个</strong> Telegram 大群组里，并自动为每个微信对话创建一个对应的话题。这样你的 Telegram 界面会清爽很多！</p>
<p>效果图：<br>（所有来自不同微信群的消息，都被整理到 “EFB Topic Group” 这个群的不同话题下了）<br><img src="https://raw.githubusercontent.com/jiz4oh/backups/master/img/2025/04/upgit_20250413_1744529293.png" alt="upgit_20250413_1744529293.png"></p>
<span id="more"></span>

<h2 id="如何设置？"><a href="#如何设置？" class="headerlink" title="如何设置？"></a>如何设置？</h2><p><strong>重要提示：</strong> 这个功能需要使用一个修改版的 EFB Telegram 主端（<code>efb-telegram-master</code>），官方版本目前还不支持。</p>
<p><strong>步骤一：安装修改版的 EFB Telegram 主端</strong></p>
<p>如果你之前已经安装了官方的 <code>efb-telegram-master</code>，需要先卸载掉，然后安装我修改过的版本。打开你的终端（命令行界面），运行以下命令：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 如果你之前安装过，先卸载</span>
pip uninstall efb-telegram-master <span class="token parameter variable">-y</span>

<span class="token comment"># 安装修改版（注意：这个命令会从 GitHub 直接安装）</span>
pip <span class="token function">install</span> git+https://github.com/jiz4oh/efb-telegram-master.git<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><strong>步骤二：修改 EFB 配置文件</strong></p>
<p>找到你的 EFB 配置文件。通常在目录下的 <code>profiles/default/blueset.telegram/config.yaml</code> (路径可能因人而异)。</p>
<p>用文本编辑器打开 <code>config.yaml</code> 文件，在 <code>flags:</code> 下面添加一行 <code>topic_group:</code>，后面跟上你的 Telegram 话题群组 ID。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># config.yaml 文件内容示例</span>
<span class="token comment"># ... 其他配置项 ...</span>

<span class="token key atrule">flags</span><span class="token punctuation">:</span>
  <span class="token comment"># ... 其他可能存在的 flag ...</span>
  <span class="token key atrule">topic_group</span><span class="token punctuation">:</span> <span class="token punctuation">-</span>100xxxxxxxxxx <span class="token comment"># &lt;--- 把这里换成你的 Topic Group ID</span>

<span class="token comment"># ... 其他配置项 ...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><strong>如何获取 <code>topic_group</code> ID？</strong> 请看下面的 FAQ 部分。</p>
<p><strong>步骤三：设置 Telegram 机器人权限</strong></p>
<ol>
<li> 在 Telegram 里创建一个<strong>新的群组</strong>，并且<strong>启用话题（Topics）功能</strong>。（在群组设置里可以找到开启选项）</li>
<li> 把你的 EFB 机器人账号（就是你在 Telegram 里用来收发消息的那个 Bot）<strong>添加</strong>到这个新创建的话题群组中。</li>
<li> 确保你的 EFB 机器人账号在这个群组里有<strong>“管理话题 (Manage Topics)”</strong> 的权限。你需要将机器人提升为管理员，并勾选这个权限。这一步很重要，否则机器人无法自动创建话题。</li>
</ol>
<p>完成以上三步并重启 EFB 后，所有新的、未被你手动绑定到其他 Telegram 群组的微信消息，就会自动出现在这个话题群组里，并按聊天对象自动创建话题了。</p>
<h2 id="常见问题解答-FAQ"><a href="#常见问题解答-FAQ" class="headerlink" title="常见问题解答 (FAQ)"></a>常见问题解答 (FAQ)</h2><ol>
<li><p><strong>Q：如何获取 <code>topic_group</code> ID？</strong><br> <strong>A：</strong></p>
<ol>
<li> 首先，你需要创建一个<strong>新的</strong> Telegram 群组，并在群组设置里<strong>启用话题（Topics）功能</strong>。</li>
<li> 随便在群里的某个话题（比如默认的 “General”）里发一条消息。</li>
<li> 右键点击这条消息（或者长按消息），选择“复制消息链接 (Copy Message Link)”或类似的选项。</li>
<li>你会得到一个类似 <code>https://t.me/c/1234567890/1</code> 或 <code>https://t.me/your_public_group_username/1</code> 的链接。<ul>
<li>如果是 <code>https://t.me/c/1234567890/1</code> 这种形式，那么你的群组 ID 就是 <code>-1001234567890</code>。（注意前面的 <code>-100</code> 是必须加上的！）</li>
<li>如果是公开群组链接 <code>https://t.me/your_public_group_username/1</code>，你需要先通过其他方法获取群组的数字 ID（比如使用一些 Telegram Bot 查询），然后同样在前面加上 <code>-100</code>。对于私有群组，使用第一种方法通常更简单。</li>
</ul>
</li>
<li> 将获取到的 <code>-100xxxxxxxxxx</code> 填入 <code>config.yaml</code> 文件中的 <code>topic_group:</code> 后面。</li>
</ol>
</li>
<li><p><strong>Q：设置好了但没效果，消息还是没出现在话题群组里？</strong><br> <strong>A：</strong> 请检查以下几点：</p>
<ul>
<li>确认 <code>config.yaml</code> 文件里的 <code>topic_group</code> ID 填写正确，特别是前面的 <code>-100</code> 不要漏掉。</li>
<li>确认你的 EFB 机器人账号<strong>已经加入</strong>了你设置的那个话题群组。</li>
<li>确认你的 EFB 机器人账号在那个话题群组里<strong>有“管理话题 (Manage Topics)”的权限</strong>。</li>
</ul>
</li>
<li><p><strong>Q：如果我不设置 <code>topic_group</code> 会怎么样？</strong><br> <strong>A：</strong> 如果你不设置 <code>topic_group</code> 或者把它注释掉（在前面加 <code>#</code>），那么 EFB 的行为就和原版一样。</p>
</li>
<li><p><strong>Q：用了这个话题功能，我还能像以前一样把某个特别重要的微信群单独绑定到一个普通的 Telegram 群吗？</strong><br> <strong>A：</strong> 可以的。话题聚合功能只对那些<strong>没有被手动 <code>/link</code> 绑定</strong>的聊天生效。你仍然可以使用 <code>/link</code> 命令将特定的微信聊天（比如你的家庭群、工作群）绑定到任意一个普通的 Telegram 群组（不需要开启话题功能）。这些被手动绑定的群聊消息<strong>不会</strong>出现在话题群组里。</p>
</li>
<li><p><strong>Q：我有好几个微信账号，跑了好几个 EFB 实例，可以让它们都把消息发到同一个 Telegram 话题群组吗？</strong><br> <strong>A：</strong> 可以。不同的 EFB 实例（即使它们使用同一个 Telegram 机器人账号或者不同的机器人账号）可以配置<strong>相同</strong>的 <code>topic_group</code> ID。它们会自动管理各自负责的微信聊天对应的话题，互不影响。</p>
</li>
<li><p><strong>Q：我以前把某个微信聊天（比如“摸鱼群”）用 <code>/link</code> 命令绑定到了一个普通 Telegram 群组，现在想让它也进入话题群组统一管理，怎么办？</strong><br> <strong>A：</strong> 很简单。去那个你<strong>之前绑定用的普通 Telegram 群组</strong>（不是话题群组），在里面发送命令 <code>/unlink_all</code>。这样就解除了手动绑定。之后“摸鱼群”再有新消息，就会自动出现在你的话题群组里，并创建一个名为“摸鱼群”的话题了。</p>
</li>
<li><p><strong>Q：有没有更快速的搭建方法？我不想一步步配置。</strong><br> <strong>A：</strong> 如果你熟悉 Docker，可以参考<a href="https://www.jiz4oh.com/2023/01/run-efb-in-docker">我的另一篇教程</a>来快速部署 EFB。我已经准备好了包含这个话题功能的 <a href="https://github.com/jiz4oh/ehforwarderbot/blob/master/docker-compose.yaml">docker-compose 配置文件</a>，可以帮你简化很多配置步骤。</p>
</li>
</ol>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>efb</tag>
        <tag>ehforwarderbot</tag>
        <tag>telegram</tag>
        <tag>wechat</tag>
      </tags>
  </entry>
  <entry>
    <title>Tailscale 与 clash 共存</title>
    <url>/2024/09/tailscale-with-clash/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在家里买了 Nas 之后最头疼的事就是如何进行外网访问，如果有公网 IP 的话就非常简单，但可惜我没有，所以我选择了进行内网穿透，常见的内网穿透工具有 <strong>tailscale、zerotier、frp、ngrok</strong>，最终由于易用性等原因选择了 <a href="https://tailscale.com/"><strong>tailscale</strong></a>。</p>
<p>由于移动端的限制，ios/android 都同时只能使用一个 vpn 程序，而我又经常有 <em>科学上网</em> 的需求，所以如何使 <strong>tailscale</strong> 和 <strong>clash</strong> 等工具并存，在 <em>科学上网</em> 的同时进行内网穿透成为了一个问题，以下是我长期使用下来总结的一些经验。</p>
<span id="more"></span>

<h2 id="移动端"><a href="#移动端" class="headerlink" title="移动端"></a>移动端</h2><h3 id="android"><a href="#android" class="headerlink" title="android"></a>android</h3><p>安卓是我的主力设备，所以在此之上的使用经验更多，大约有以下几种使用方式：</p>
<h4 id="未-root"><a href="#未-root" class="headerlink" title="未 root"></a>未 root</h4><p>在不 root 的情况下，只能同时存在一个 vpn 程序，所以只能通过 <a href="https://github.com/tailscale/tailscale-android">tailscale android</a> + <strong>exit node</strong> 实现</p>
<ol>
<li>优点：不需要 root，简单易用（对于使用者来说）</li>
<li>缺点：<ol>
<li><del>暂时不支持分应用代理，无法设置某些 app 绕过，导致国内应用网速受限于 exit node 的上传带宽</del> <a href="https://tailscale.com/kb/1444/android-app-split-tunneling">v1.70.0 已支持</a></li>
<li>需要至少一个额外设备运行 clash，并设置 <strong>exit node</strong></li>
<li>在 nat 环境下如果无法打洞成功，则网速还会受限于 derper server</li>
</ol>
</li>
</ol>
<p>如果家里有 linux 或者 macos 设备，<a href="#linux">可以将 clash 和 exit node 运行在同一台设备</a>，否则需要两台设备，一台运行 clash 做旁路网关，一台运行 tailscale exit node</p>
<h4 id="已-root"><a href="#已-root" class="headerlink" title="已 root"></a>已 root</h4><table>
<thead>
<tr>
<th>/</th>
<th><a href="https://github.com/anasfanani/Magisk-Tailscaled">Magisk Tailscaled</a> + <a href="https://github.com/MetaCubeX/ClashMetaForAndroid">clash for android</a></th>
<th><a href="https://github.com/anasfanani/Magisk-Tailscaled">Magisk Tailscaled</a> + <a href="https://github.com/taamarin/box_for_magisk">box_for_magisk</a></th>
<th><a href="https://github.com/taamarin/box_for_magisk">box_for_magisk</a> + <a href="https://github.com/tailscale/tailscale-android">tailscale android</a></th>
</tr>
</thead>
<tbody><tr>
<td>分应用代理</td>
<td>支持</td>
<td>支持[1]</td>
<td>支持[2]</td>
</tr>
<tr>
<td>ipv6</td>
<td>不支持[3]</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>Taildrop</td>
<td>不支持[4]</td>
<td>不支持[4]</td>
<td>支持</td>
</tr>
<tr>
<td>修复 dns</td>
<td>需要[5]</td>
<td>需要[5]</td>
<td>不需要</td>
</tr>
</tbody></table>
<ol>
<li><p>需要修改 <a href="https://github.com/taamarin/box_for_magisk">box_for_magisk</a> 配置文件</p>
</li>
<li><p>需要 <a href="https://tailscale.com/kb/1444/android-app-split-tunneling">tailscale android v1.70.0</a></p>
</li>
<li><p><strong>clash for android</strong> 不支持使用 ipv6，所以无法通过 tailscale 建立 ipv6 直连</p>
</li>
<li><p><strong>Magisk Tailscaled</strong> 使用的是 linux 版，Taildrop 无法自动接收文件到 Downloads 目录</p>
</li>
<li><p><strong>Magisk Tailscaled</strong> 使用的是 linux 版，默认 dns 是通过 <code>/etc/resolv.conf</code> 查找的，但安卓没有该文件，需要手动添加以下内容到 <code>/etc/resolv.conf</code>，否则可能出现问题</p>
 <pre class="line-numbers language-none"><code class="language-none">nameserver 8.8.8.8
nameserver 8.8.4.4<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

</li>
</ol>
<h3 id="ios"><a href="#ios" class="headerlink" title="ios"></a>ios</h3><p>ios 参照 <a href="#%E6%9C%AA-root">android</a> 设置，使用 tailscale ios + <strong>exit-node</strong> 即可</p>
<p><a href="https://kb.nssurge.com/surge-knowledge-base/v/zh/guidelines/ponte">surge ponte</a> 也能实现内网穿透 + 科学上网的需求，但有以下几个缺点：</p>
<ol>
<li>付费。surge 实在有点贵</li>
<li>不兼容非苹果生态。假如有 windows/android 需求，或者需要分享给非苹果生态的家人，都无法使用</li>
</ol>
<h2 id="桌面端"><a href="#桌面端" class="headerlink" title="桌面端"></a>桌面端</h2><h3 id="linux"><a href="#linux" class="headerlink" title="linux"></a>linux</h3><p>如果想要将 clash 和 tailscale exit node 同时运行在在同一台设备上，需要按如下步骤修改 tailscale 设置</p>
<ol>
<li><p>修改 <code>/etc/default/tailscaled</code> 为以下内容，这一步的作用是使 tailscale 不再修改 iptables，只充当纯净代理，<a href="https://tailscale.com/kb/1112/userspace-networking#step-1-start-tailscaledtailscale-in-userspace-networking-mode">参考</a></p>
 <pre class="line-numbers language-none"><code class="language-none"># Set the port to listen on for incoming VPN packets.
# Remote nodes will automatically be informed about the new port number,
# but you might want to configure this in order to set external firewall
# settings.
PORT&#x3D;&quot;0&quot;

# Extra flags you might want to pass to tailscaled.
FLAGS&#x3D;&quot;--tun&#x3D;userspace-networking --socks5-server&#x3D;0.0.0.0:1099&quot;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>重启 tailscale</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">systemctl restart tailscaled<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>设置 tailscale exite-node</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">tailscale <span class="token builtin class-name">set</span> --advertise-exit-node<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

</li>
</ol>
<p>如此设置之后的网络图：</p>
<pre class="mermaid">
flowchart BT
tailscale1 --&gt; tailscale
tailscale2 --&gt; tailscale
exit-node --&gt; Internet

subgraph exit-node
    tailscale --&gt; clash
end

subgraph Android1
    Android --&gt; tailscale1
end

subgraph Others
    Other --&gt; tailscale2
end
</pre>

<p>不过这样会有一个问题，exit-node 这台设备无法连接到 tailnet，修复该问题需要修改 clash 或其他科学上网程序的设置，详细解决方案可参考 <a href="https://github.com/anasfanani/Magisk-Tailscaled/releases/tag/v1.56.1.3">这里</a>，clash 的解决方案如下：</p>
<ol>
<li><p>增加新 <code>proxies</code></p>
 <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">proxies</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"tailscale-socks"</span>
    <span class="token key atrule">type</span><span class="token punctuation">:</span> socks5
    <span class="token key atrule">server</span><span class="token punctuation">:</span> localhost
    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">1099</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>添加规则，<a href="https://tailscale.com/kb/1105/other-vpns#workaround-split-tunnels">参考</a></p>
 <pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">rules</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> IP<span class="token punctuation">-</span>CIDR<span class="token punctuation">,</span>100.64.0.0/10<span class="token punctuation">,</span>tailscale<span class="token punctuation">-</span>socks
<span class="token punctuation">-</span> IP<span class="token punctuation">-</span>CIDR<span class="token punctuation">,</span>fd7a<span class="token punctuation">:</span>115c<span class="token punctuation">:</span>a1e0<span class="token punctuation">:</span><span class="token punctuation">:</span>/48<span class="token punctuation">,</span>tailscale<span class="token punctuation">-</span>socks
<span class="token comment"># 其他 subroutes</span>
<span class="token comment"># - IP-CIDR,xxxx,tailscale-socks </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>


</li>
</ol>
<h3 id="macos"><a href="#macos" class="headerlink" title="macos"></a>macos</h3><p>macos 可参照 <a href="#%E6%9C%AA-root">android</a> 设置，使用 tailscale macos + <strong>exit-node</strong> 即可</p>
<p>macos 也可像 linux 一样同时运行 clash 和 tailscale 并作为 exit-node 节点对其他设备提供服务，如果家里没有软路由，但是有闲置 macos 设备时可考虑，需要使用 <a href="https://tailscale.com/kb/1065/macos-variants#open-source-tailscaled-variant">Open source tailscaled variant</a></p>
<ol>
<li><p>下载 tailscale</p>
 <pre class="line-numbers language-none"><code class="language-none">brew install tailscale<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>添加 <code>/Library/LaunchDaemons/tailscale.plist</code>，文件内容如下</p>
 <pre class="line-numbers language-plist" data-language="plist"><code class="language-plist">&lt;?xml version&#x3D;&quot;1.0&quot; encoding&#x3D;&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-&#x2F;&#x2F;Apple&#x2F;&#x2F;DTD PLIST 1.0&#x2F;&#x2F;EN&quot; &quot;http:&#x2F;&#x2F;www.apple.com&#x2F;DTDs&#x2F;PropertyList-1.0.dtd&quot;&gt;
&lt;plist version&#x3D;&quot;1.0&quot;&gt;
&lt;dict&gt;
    &lt;key&gt;KeepAlive&lt;&#x2F;key&gt;
    &lt;true&#x2F;&gt;
    &lt;key&gt;Label&lt;&#x2F;key&gt;
    &lt;string&gt;tailscale&lt;&#x2F;string&gt;
    &lt;key&gt;LimitLoadToSessionType&lt;&#x2F;key&gt;
    &lt;array&gt;
        &lt;string&gt;Background&lt;&#x2F;string&gt;
        &lt;string&gt;LoginWindow&lt;&#x2F;string&gt;
        &lt;string&gt;System&lt;&#x2F;string&gt;
    &lt;&#x2F;array&gt;
    &lt;key&gt;ProgramArguments&lt;&#x2F;key&gt;
    &lt;array&gt;
        &lt;string&gt;&#x2F;opt&#x2F;homebrew&#x2F;opt&#x2F;tailscale&#x2F;bin&#x2F;tailscaled&lt;&#x2F;string&gt;
        &lt;string&gt;--tun&#x3D;userspace-networking&lt;&#x2F;string&gt;
        &lt;string&gt;--socks5-server&#x3D;localhost:1099&lt;&#x2F;string&gt;
        &lt;string&gt;--outbound-http-proxy-listen&#x3D;localhost:1099&lt;&#x2F;string&gt;
    &lt;&#x2F;array&gt;
    &lt;key&gt;RunAtLoad&lt;&#x2F;key&gt;
    &lt;true&#x2F;&gt;
    &lt;key&gt;StandardErrorPath&lt;&#x2F;key&gt;
    &lt;string&gt;&#x2F;opt&#x2F;homebrew&#x2F;var&#x2F;log&#x2F;tailscaled.log&lt;&#x2F;string&gt;
    &lt;key&gt;StandardOutPath&lt;&#x2F;key&gt;
    &lt;string&gt;&#x2F;opt&#x2F;homebrew&#x2F;var&#x2F;log&#x2F;tailscaled.log&lt;&#x2F;string&gt;
&lt;&#x2F;dict&gt;
&lt;&#x2F;plist&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>启动 tailscale</p>
 <pre class="line-numbers language-none"><code class="language-none">sudo launchctl unload &#x2F;Library&#x2F;LaunchDaemons&#x2F;tailscale.plist &amp;&amp; sudo launchctl load &#x2F;Library&#x2F;LaunchDaemons&#x2F;tailscale.plist<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>打开 clash <code>tun</code> 模式</p>
</li>
</ol>
<p>如此，将 <code>tailscale</code> 的所有发出流量均会经过 <code>clash</code></p>
<h3 id="windows"><a href="#windows" class="headerlink" title="windows"></a>windows</h3><p>可参照 <a href="#%E6%9C%AA-root">android</a> 设置，使用 tailscale windows + <strong>exit-node</strong> 即可</p>
]]></content>
      <categories>
        <category>网络</category>
        <category>路由器</category>
      </categories>
      <tags>
        <tag>内网穿透</tag>
        <tag>tailscale</tag>
        <tag>clash</tag>
      </tags>
  </entry>
  <entry>
    <title>TCP/IP 五层协议</title>
    <url>/2020/11/tcp-ip/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在极客时间学习刘超老师的<a href="https://time.geekbang.org/column/intro/85">趣谈网络协议</a>，对 TCP/IP 有了更深的了解与感触，记录下学习的心得与体会</p>
<span id="more"></span>

<h3 id="什么是-TCP-IP-五层协议"><a href="#什么是-TCP-IP-五层协议" class="headerlink" title="什么是 TCP/IP 五层协议"></a>什么是 TCP/IP 五层协议</h3><p>TCP/IP 五层协议是在一定程度上参考了 <a href="https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B">OSI 7层模型</a> 的体系结构，将 OSI 7层模型简化为了五层（也有将链路层和物理层合并后简化为四层的）</p>
<p>五层协议：应（应表会）传网数物</p>
<ul>
<li>应用层：HTTP,FTP 等协议<br>封装请求正文</li>
<li>传输层：TCP/UDP 协议<br>将端口信息（当前应用监听端口、目标应用监听端口）等封装</li>
<li>网络层：ICMP,IP 等协议<br>封装 IP 地址（当前主机 ip 地址、目标主机 ip 地址）</li>
<li>数据链路层：以太网等协议<br>封装 MAC 地址（当前主机的 MAC 地址、网关的 MAC 地址），通过 ARP 协议在局域网广播找到网关后，通过网关发送出去</li>
<li>物理层：<br>将数据转换为二进制，作为电信号发送出去</li>
</ul>
<p>五层协议中协议有哪些：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-08-06-50-40.png" alt="2020-09-08-06-50-40"></p>
<h2 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h2><p>物理层就是网线，中间传输的是光信号</p>
<p>两台电脑通过网线直接连接，就可以组成一个最小的 <strong>局域网</strong>，即 LAN，Local Area Network</p>
<p>如果多台电脑需要组成一个局域网，就需要一个设备来控制数据传输的目的地和先后顺序，比如交换机</p>
<h2 id="数据链路层"><a href="#数据链路层" class="headerlink" title="数据链路层"></a>数据链路层</h2><p>数据链路层包格式：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-16-00.png" alt="2020-09-12-17-16-00"></p>
<p>在数据链路层（MAC 层）需要解决 3 个问题：</p>
<ol>
<li>数据的先后顺序<br> MAC 层全称 Medium Access Control，即媒体访问控制。控制如何进行 <strong>多路访问</strong></li>
<li>数据的目的地<br> 这就需要一个物理地址，叫作 <strong>链路层地址</strong>，也即 <strong>MAC 地址</strong></li>
<li>发送错误的处理方式<br> 对于以太网，第二层的最后面是 <strong>CRC</strong>，也就是循环冗余检测。通过 XOR异或 的算法，来计算整个包是否在发送的过程中出现了错误</li>
</ol>
<h3 id="MAC-地址"><a href="#MAC-地址" class="headerlink" title="MAC 地址"></a>MAC 地址</h3><p>MAC 地址更像是身份证，是一个唯一的标识。它的唯一性设计是为了组网的时候，不同的网卡放在一个网络里面的时候，可以不用担心冲突。从硬件角度，保证不同的网卡有不同的标识。</p>
<p>MAC 地址的通信范围比较小，局限在一个子网里面。例如，从 192.168.0.2/24 访问 192.168.0.3/24 是可以用 MAC 地址的。一旦跨子网，即从 192.168.0.2/24 到 192.168.1.2/24，MAC 地址就不行了，需要 IP 地址起作用了</p>
<h3 id="交换机"><a href="#交换机" class="headerlink" title="交换机"></a>交换机</h3><p>交换机是二层转发设备，主要作用就是获取到网络包之后，检查包的目标 MAC 头，再根据策略进行转发，避免主机每次都进行广播发送网络包</p>
<h3 id="ARP-协议"><a href="#ARP-协议" class="headerlink" title="ARP 协议"></a>ARP 协议</h3><p>当局域网中如果有多台机器，知道目标 IP 地址而不知道 MAC 地址时，就需要使用 ARP 协议来获取。  </p>
<blockquote>
<p>已知 IP 地址，求 MAC 地址的协议</p>
</blockquote>
<p>原理是向局域网发出 ARP 协议进行广播，等待目标主机回应</p>
<p>具体询问和回答的报文：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-24-39.png" alt="2020-09-12-17-24-39"></p>
<h4 id="ARP-表"><a href="#ARP-表" class="headerlink" title="ARP 表"></a>ARP 表</h4><p>主机不可能每次都通过 ARP 协议来广播查找 MAC 地址，这样会极大降低效率，所以当主机通过 ARP 协议获取了 MAC 地址之后，就会在本地缓存一个 ARP 表，存储 IP-MAC 的映射。</p>
<p>只有当 ARP 表没有对应的 MAC 地址时，主机才会通过 ARP 协议去获取 MAC 地址并更新 ARP 表</p>
<p>因为主机的 IP 和 MAC 可能会改变，所以 ARP 表是有过期时间的。</p>
<h3 id="RARP-协议"><a href="#RARP-协议" class="headerlink" title="RARP 协议"></a>RARP 协议</h3><p>RARP 协议即 Reverse ARP 协议</p>
<blockquote>
<p>已知 MAC 地址，求 IP 地址的协议</p>
</blockquote>
<h3 id="转发表"><a href="#转发表" class="headerlink" title="转发表"></a>转发表</h3><p>当源主机发出了数据报文之后，数据报文其实只知道 MAC 地址，而不知道具体路径，就像你要到一个地方去，但是你不知道如何去一样  </p>
<p>当主机通过网线向连接的设备发送请求时</p>
<ol>
<li>设备为 hub 集线器：<br> hub 集线器是一层设备，只会简单的完全复制，会向所有非源设备广播转发请求，通过其他主机自己来确认数据包是否应该接受</li>
<li>设备为交换机：<br> 交换机刚开始也不会知道所有主机的 MAC 地址，但是当 主机 A 发送一个请求到达交换机时，交换机会记录下该请求的主机的 MAC 地址，当有其他请求目标地址为主机 A 时，会只转发给主机A，不再进行广播。<br> 然后过一段时间之后就会记录下局域网下所有主机的 MAC 地址，这个就是 <strong>转发表</strong></li>
</ol>
<p>因为主机的 MAC 可能会改变，所以转发表是有过期时间的。</p>
<h3 id="拓扑结构"><a href="#拓扑结构" class="headerlink" title="拓扑结构"></a>拓扑结构</h3><p>当局域网中主机过多，比如办公室场景，可能有几十几百个网口，这时候一个交换机是不够用的，就需要多台交换机，这时候交换机连接起来就是 <strong>拓扑结构</strong></p>
<p>当交换机过多时，不可避免的会出现 <strong>环路问题</strong>，例如下图：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-44-19.png" alt="2020-09-12-17-44-19"></p>
<ol>
<li>当机器 1 发送 ARP 广播寻找机器 2 的 MAC 地址时，交换机 A 和交换机 B 都会收到这个广播，此时，都会记得 机器 1 是在左边这个网口</li>
<li>然后都同时向 LAN2 转发这条广播消息，当交换机 A 收到交换机 B 转发的消息时，会发现是机器 1 在寻找机器 2，这时候就会发现机器 1 位于右边这个网口，于是更新自己的记录，将这个消息转发到 LAN1。</li>
<li>同理，交换机 B 也会更新自己的记录，然后转发消息到 LAN1</li>
<li>然后这个广播请求会来回转发，就会堵塞网络</li>
</ol>
<p>这时候需要破除环路</p>
<h3 id="STP-协议"><a href="#STP-协议" class="headerlink" title="STP 协议"></a>STP 协议</h3><p>STP：Spanning Tree Protocol，是一个生成树的算法，用来破解环路</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-52-00.png" alt="2020-09-12-17-52-00"></p>
<ul>
<li><p><strong>Root Bridge</strong>，也就是根交换机。这个比较容易理解，可以比喻为“掌门”交换机，是某棵树的老大，是掌门，最大的大哥。</p>
</li>
<li><p><strong>Designated Bridges</strong>，有的翻译为指定交换机。这个比较难理解，可以想像成一个“小弟”，对于树来说，就是一棵树的树枝。所谓“指定”的意思是，我拜谁做大哥，其他交换机通过这个交换机到达根交换机，也就相当于拜他做了大哥。这里注意是树枝，不是叶子，因为叶子往往是主机。</p>
</li>
<li><p><strong>Bridge Protocol Data Units</strong> （BPDU） ，网桥协议数据单元。可以比喻为“相互比较实力”的协议。行走江湖，比的就是武功，拼的就是实力。当两个交换机碰见的时候，也就是相连的时候，就需要互相比一比内力了。BPDU只有掌门能发，已经隶属于某个掌门的交换机只能传达掌门的指示。</p>
</li>
<li><p><strong>Priority Vector</strong>，优先级向量。可以比喻为实力 （值越小越牛）。实力是啥？就是一组ID数目，[Root Bridge ID, Root Path Cost, Bridge ID, and Port ID]。为什么这样设计呢？这是因为要看怎么来比实力。先看Root Bridge ID。拿出老大的ID看看，发现掌门一样，那就是师兄弟；再比Root Path Cost，也即我距离我的老大的距离，也就是拿和掌门关系比，看同一个门派内谁和老大关系铁；最后比Bridge ID，比我自己的ID，拿自己的本事比。</p>
</li>
</ul>
<p>STP的工作过程是怎样的？</p>
<ol>
<li><p>一开始，江湖纷争，异常混乱。大家都觉得自己是掌门，谁也不服谁。于是，所有的交换机都认为自己是掌门，每个网桥都被分配了一个ID。这个ID里有管理员分配的优先级，当然网络管理员知道哪些交换机贵，哪些交换机好，就会给它们分配高的优先级。这种交换机生下来武功就很高，起步就是乔峰</p>
<p> (图中圆圈内数据代表优先级，线上数字代表传输距离，优先级越小越优先，距离越小越优先)</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-54-49.png" alt="2020-09-12-17-54-49"></p>
</li>
<li><p>既然都是掌门，互相都连着网线，就互相发送 BPDU 来比功夫呗。这一比就发现，有人是岳不群，有人是封不平，赢的接着当掌门，输的就只好做小弟了。当掌门的还会继续发 BPDU，而输的人就没有机会了。它们只有在收到掌门发的 BPDU 的时候，转发一下，表示服从命令。</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-55-22.png" alt="2020-09-12-17-55-22"></p>
<p> 数字表示优先级。就像这个图，5 和 6 碰见了，6 的优先级低，所以乖乖做小弟。于是一个小门派形成，5 是掌门，6 是小弟。其他诸如 1-7、2-8、3-4 这样的小门派，也诞生了。于是江湖出现了很多小的门派，小的门派，接着合并。</p>
<p> 合并的过程根据传输距离会出现以下<strong>四种情形</strong></p>
<ol>
<li><p>情形一：掌门遇到掌门<br> 当 5 碰到了 1，掌门碰见掌门，1 觉得自己是掌门，5 也刚刚跟别人PK完成为掌门。这俩掌门比较功夫，最终 1 胜出。于是输掉的掌门 5 就会率领所有的小弟归顺。结果就是 1 成为大掌门。</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-58-41.png" alt="2020-09-12-17-58-41"></p>
</li>
<li><p>情形二：同门相遇<br> 同门相遇可以是掌门与自己的小弟相遇，这说明存在“环”了。这个小弟已经通过其他门路拜在你门下，结果你还不认识，就 PK 了一把。结果掌门发现这个小弟功夫不错，不应该级别这么低，就把它招到门下亲自带，那这个小弟就相当于升职了。</p>
<p> 我们再来看，假如 1 和 6 相遇。6 原来就拜在 1 的门下，只不过 6 的上司是 5，5 的上司是 1。1 发现，6 距离我才只有 2，比从 5 这里过来的 5（=4+1）近多了，那 6 就直接汇报给我吧。于是，5 和 6 分别汇报给 1。  </p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-17-59-00.png" alt="2020-09-12-17-59-00"></p>
</li>
<li><p>情形三：掌门与其他帮派小弟相遇<br> 小弟拿本帮掌门和这个掌门比较，赢了，这个掌门拜入门来。输了，会拜入新掌门，并且逐渐拉拢和自己连接的兄弟，一起弃暗投明</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-01-23.png" alt="2020-09-12-18-01-23"></p>
</li>
<li><p>情形四：不同门小弟相遇<br> 各自拿掌门比较，输了的拜入赢的门派，并且逐渐将与自己连接的兄弟弃暗投明。</p>
<p> <img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-03-23.png" alt="2020-09-12-18-03-23"></p>
<p> 例如，5 和 4 相遇。虽然 4 的武功好于 5，但是 5 的掌门是 1，比 4 牛，于是 4 拜入 5 的门派。后来当 3 和 4 相遇的时候，3 发现 4 已经叛变了，4 说我现在老大是 1，比你牛，要不你也来吧，于是 3 也拜入 1。</p>
</li>
</ol>
</li>
</ol>
<h3 id="VLAN"><a href="#VLAN" class="headerlink" title="VLAN"></a>VLAN</h3><p>当交换机过多时，虽然 STP 解决了环路问题，避免了广播风暴，但是在同一个局域网中，总有些数据是想在小黑屋里单独交流的，不希望被人给抓包了。</p>
<p>这时候可以有两个解决方案：</p>
<ol>
<li>物理隔离<br> 交换机隔离，不在同一个拓扑网络下。</li>
<li>虚拟隔离 VLAN，也就是常说的虚拟局域网<br> 在同一个交换机上形成多个局域网</li>
</ol>
<p>交换机如何区分机器属于哪个局域网呢？通过 VLAN ID</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-10-06.png" alt="2020-09-12-18-10-06"></p>
<p>VLAN 下的网络结构：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-11-16.png" alt="2020-09-12-18-11-16"></p>
<h2 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h2><h3 id="ICMP-协议"><a href="#ICMP-协议" class="headerlink" title="ICMP 协议"></a>ICMP 协议</h3><p>ICMP 协议介于网络层和传输层之间，普遍认为属于网络层，ICMP 通常用于 ping 检测网络连通</p>
<p>ICMP：Internet Control Message Protocol，就是互联网控制报文协议</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-25-18.png" alt="2020-09-12-18-25-18"></p>
<p>ICMP 报文有很多的类型，不同的类型有不同的代码。最常用的类型是主动请求为 8，主动请求的应答为 0</p>
<p>报文类型，可以大致分为两类：查询报文 和 差错报文</p>
<h4 id="查询报文"><a href="#查询报文" class="headerlink" title="查询报文"></a>查询报文</h4><p>ping 就是使用查询报文的一种  </p>
<ul>
<li>ping 的主动请求，被称为 <strong>ICMP ECHO REQUEST</strong>，类型为 8，</li>
<li>主动请求的响应，被称为 <strong>ICMP ECHO REPLY</strong>，类型为 0</li>
</ul>
<p>比起原生的 ICMP，这里面多了两个字段，一个是<strong>标识符</strong>，另一个是<strong>序号</strong>，在选项数据中，ping 还会存放发送请求的时间，来计算响应时间</p>
<p>ping 的收发过程：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-12-18-32-03.png" alt="2020-09-12-18-32-03"></p>
<ol>
<li>源主机首先会构建一个 ICMP 请求数据包，ICMP 数据包内包含多个字段。</li>
<li>最重要的是两个，第一个是<strong>类型字段</strong>，对于请求数据包而言该字段为 8；</li>
<li>另外一个是<strong>顺序号</strong>，主要用于区分连续ping的时候发出的多个数据包。每发出一个请求数据包，顺序号会自动加 1。</li>
<li>为了能够计算往返时间 RTT，它会在报文的数据部分插入发送时间</li>
</ol>
<h4 id="差错报文"><a href="#差错报文" class="headerlink" title="差错报文"></a>差错报文</h4><p>差错报文的类型为有：</p>
<ul>
<li>终点不可达，类型为 3<ul>
<li>网络不可达：代码为 0，对方局域网无响应</li>
<li>主机不可达：代码为 1，对方局域网响应了，但是找不到这个 ip 对应的主机</li>
<li>协议不可达：代码为 2，对方主机响应了，但是对方不接受这个协议</li>
<li>端口不可达：代码为 3，对方主机响应了，但是没有程序监听这个端口</li>
<li>需要进行分片但设置了不分片：代码为 4，中间某个网关需要对长度进行限制，但是源主机不允许</li>
</ul>
</li>
<li>源站抑制：类型为 4，需要源主机放慢发送速度</li>
<li>路由重定向：类型为 5，下次发送时发给另一个路由器</li>
<li>时间超时：类型为 11，超过网络包生命周期还是未到达目标</li>
</ul>
<p>差错报文的结构相对复杂一些。除了前面还是 IP，ICMP 的前 8 字节不变，后面则跟上出错的那个 IP 包的 IP 头和 IP 正文的前8  个字节</p>
<p><strong>Traceroute</strong> 使用差错报文：</p>
<ol>
<li>故意设置特殊的 TTL，来追踪去往目的地时沿途经过的路由器</li>
<li>故意设置不分片，从而确定路径的 MTU</li>
</ol>
<p><strong>TTL</strong>：Time To Live。最大值为 255。该字段指定IP包被路由器丢弃之前允许通过的最大网段数量，每个路由器要将 TTL 减1，TTL 通常表示被丢弃前经过的路由器的个数。当 TTL 变为 0 时，该路由器丢弃该包，并发送一个 ICMP 包给最初的发送者</p>
<h3 id="网关"><a href="#网关" class="headerlink" title="网关"></a>网关</h3><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-13-10-04-28.png" alt="2020-09-13-10-04-28"></p>
<p>网关往往是一个路由器，是一个三层（网络层）转发的设备。<br>当服务器 A 需要访问服务器 B 时：</p>
<ol>
<li>如果目标 ip 地址是本网段主机，所以直接发给目标主机，封装目标主机的 MAC 地址</li>
<li>如果目标 ip 地址是外网主机，则将请求发给网关，封装网关的 MAC 地址</li>
</ol>
<p>当网关收到了一个请求时</p>
<ol>
<li>取下 MAC 头<ol>
<li>如果 MAC 头不是本主机，则通过转发表将请求转发到本网段其他主机上</li>
<li>如果 MAC 头是本主机，则取下 IP 头，查看目标 IP 地址<ol>
<li>如果是本主机，则由本主机程序处理</li>
<li>如果不是本主机，则查看路由表，查询应该由哪个 LAN 口处理，并封装好下一跳（可能是对应主机，也可能是网关）的 MAC 地址，这时包的 <em>目的 MAC 地址</em> 变为了<strong>路由规则匹配的下一跳的 MAC 地址</strong>，<em>源 MAC 地址</em> 变为了网关的 MAC 地址</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>网关分为两种类型：</p>
<ul>
<li>转发网关：<strong>不修改</strong>目标 IP 地址和源 IP 地址的网关</li>
<li>NAT（Network Address Translation） 网关：<strong>修改</strong>目标 IP 地址和源 IP 地址的网关</li>
</ul>
<h4 id="和路由器的关系"><a href="#和路由器的关系" class="headerlink" title="和路由器的关系"></a>和路由器的关系</h4><p>很多情况下，人们把网关就叫作路由器。其实不完全准确，而另一种比喻更加恰当：<strong>路由器是一台设备，它有五个 LAN 网口或者网卡，相当于有五只手，分别连着五个局域网。每只手的 IP 地址都和局域网的 IP 地址相同的网段，每只手都是它握住的那个局域网的网关</strong></p>
<h4 id="静态路由"><a href="#静态路由" class="headerlink" title="静态路由"></a>静态路由</h4><p>当网关收到一个外网的请求时，就会通过 <strong>静态路由</strong> 和 <strong>动态路由</strong> 规则修改 MAC 头（和 IP 头），将包发出去</p>
<p>静态路由，其实就是在路由器上，写死配置一条一条规则。<br>这些规则包括：想访问 BBS 站（它肯定有个网段），从 2 号口出去，下一跳是 IP2；想访问教学视频站（它也有个自己的网段），从 3 号口出去，下一跳是 IP3，然后保存在路由器里</p>
<h4 id="动态路由"><a href="#动态路由" class="headerlink" title="动态路由"></a>动态路由</h4><p>网络环境复杂多变时，静态路由手动修改太麻烦，所以需要动态路由通过一定算法进行自动配置</p>
<p>动态路由算法：</p>
<ul>
<li>链路状态路由（link state routing），基于 Dijkstra 算法</li>
<li>距离矢量路由（distance vector routing），基于 Bellman-Ford 算法，只适用于小型网络  </li>
</ul>
<p>动态路由协议：</p>
<ul>
<li>基于链路状态路由算法的 <strong>OSPF</strong>（Open Shortest Path First，开放式最短路径优先），广泛应用在数据中心中的协议。由于主要用在数据中心内部，用于路由决策，因而称为内部网关协议（Interior Gateway Protocol，简称 IGP）</li>
<li>基于距离矢量路由算法的 <strong>BGP</strong>（Border Gateway Protocol，边界路由协议）<ul>
<li>eBGP：在多个 AS（独立的内部网络，如家庭网络） 之间进行路由交换的协议</li>
<li>iBGP：在多个 edge touter（AS 面向外界的出口路由器） 之间进行路由交换的协议</li>
</ul>
</li>
</ul>
<h2 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h2><h3 id="UDP-协议"><a href="#UDP-协议" class="headerlink" title="UDP 协议"></a>UDP 协议</h3><p>UDP 协议：数据报协议，不需要接收方确认消息。<br>特点：面向无连接，无状态，不保证不丢失，不保证到达顺序</p>
<ul>
<li>优点是速度快</li>
<li>缺点是可能会丢包</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-13-18-26-14.png" alt="2020-09-13-18-26-14"></p>
<p>应用场景：</p>
<ul>
<li>流媒体，比如直播</li>
<li>实时游戏</li>
<li>loT物联网</li>
<li>移动通信</li>
<li>需要低时延的 http 访问</li>
</ul>
<h3 id="TCP-协议"><a href="#TCP-协议" class="headerlink" title="TCP 协议"></a>TCP 协议</h3><p>TCP 协议：流式 stream 协议，通过双向管道传输数据，发送请求之后必须收到确认消息，安全但效率稍低<br>特点：面向连接，有状态，保证无差错，无重复，并按序到达  </p>
<ul>
<li>优点是稳定可靠</li>
<li>缺点是速度较慢，需要维护状态机</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-13-18-39-47.png" alt="2020-09-13-18-39-47"></p>
<ul>
<li><p><strong><em>建立连接：</em></strong><br>TCP<strong>三次</strong>握手：</p>
<ol>
<li>client 发送管道建立请求(syn)给 server。</li>
<li>server 同意建立 client 到 server 管道(ack)，及管道建立请求(syn)给 client。</li>
<li>client 同意建立 server 到 client 管道给 server(ack)。TCP  双向管道连接建立成功  </li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-13-19-12-26.png" alt="2020-09-13-19-12-26"></p>
</li>
<li><p><strong><em>断开连接：</em></strong><br>TCP<strong>四次</strong>挥手：</p>
<ol>
<li>client 发送数据<strong>发送</strong>完毕并请求断开管道(FIN)给 server。</li>
<li>server 同意断开管道(ack)，client 单向断开管道。</li>
<li>数据接收完毕后，server 发送数据<strong>接收</strong>完毕并请求断开管道(FIN)给 client。</li>
<li>client 同意断开管道(ack)，server 单向断开管道。TCP 双向管道连接断开</li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/2020-09-13-19-56-04.png" alt="2020-09-13-19-56-04"></p>
</li>
</ul>
<p>握手和挥手整体流程状态图：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/%E7%BD%91%E7%BB%9C-20200913222940.png" alt="20200913222940"></p>
<ul>
<li>加粗的<strong>实线</strong>是客户端 A 的状态变迁，是主要流程<br>其中阿拉伯数字的序号，是握手过程中的顺序<br>而大写中文数字的序号，是挥手过程中的顺序</li>
<li>加粗的<strong>虚线</strong>是服务端 B 的状态变迁</li>
<li>点线是非主要流程</li>
</ul>
<h4 id="TCP-状态机"><a href="#TCP-状态机" class="headerlink" title="TCP 状态机"></a>TCP 状态机</h4><p>为了实现 TCP 协议的可靠性，TCP 协议规定两端必须实现一个状态机，对每一个发出包与接受包进行记录</p>
<p>TCP 发送方状态机的数据结构：</p>
<ul>
<li>第一部分：发送了并且已经确认的。</li>
<li>第二部分：发送了并且尚未确认的。</li>
<li>第三部分：没有发送，但是已经等待发送的。</li>
<li>第四部分：没有发送，并且暂时还不会发送的。<br>第三部分和第四部分的区别是，接收方会返回给发送方一个窗口大小，表示接收方处理数据的能力大小，叫 <strong>Advertised window</strong>，超过这个窗口大小的包，发送方会暂缓发送</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200914080034.png" alt="20200914080034"></p>
<ul>
<li>LastByteAcked：第一部分和第二部分的分界线</li>
<li>LastByteSent：第二部分和第三部分的分界线</li>
<li>LastByteAcked + AdvertisedWindow：第三部分和第四部分的分界线</li>
</ul>
<p>TCP 接收方状态机的数据结构：</p>
<ul>
<li>第一部分：接受并且确认过的。</li>
<li>第二部分：还没接收，但是马上就能接收的。</li>
<li>第三部分：还没接收，也没法接收的。</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200914075917.png" alt="20200914075917"></p>
<ul>
<li>LastByteRead：之后是已经接收了，但是还没被应用层读取的；</li>
<li>NextByteExpected：是第一部分和第二部分的分界线。</li>
<li>MaxRcvBuffer：最大缓存的量；</li>
</ul>
<h4 id="累计应答"><a href="#累计应答" class="headerlink" title="累计应答"></a>累计应答</h4><p>为了保证顺序性，发送方的包都有一个序号，并且按照序号挨个发送。对于接收方，应答却不是挨个应答，会应答一个序号，表示这个序号之前的包都已收到。</p>
<h4 id="确认与重发机制"><a href="#确认与重发机制" class="headerlink" title="确认与重发机制"></a>确认与重发机制</h4><p>因为底层 IP 协议的不可靠性，无法保证网络包的不丢失与按序到达，TCP 协议自己实现了确认与重发机制来保证顺序问题和丢包问题</p>
<ol>
<li>超时重试：对于每一个发送了，但是没有 ACK 的包，都设有一个超时时间，超过时间则重新尝试。  <ul>
<li><strong>超时时间</strong>：是 TCP 通过采样 RTT 的时间，然后进行加权平均算出的，这个值是不断变化的。这个算法被称为<strong>自适应重传算法</strong><br>当一个数据包再次超时时，TCP 的策略是超时间隔加倍。每当遇到一次超时重传的时候，都会将下一次超时时间间隔设为先前值的两倍。两次超时，就说明网络环境差，不宜频繁反复发送</li>
<li><strong>快速重传</strong>：当接收方收到一个序号大于下一个所期望的序号时，就检测到了数据流中的间隔，于是发送三个冗余的 ACK，发送方收到后就会在超时时间之前提前重传<br>比如，接收方收到了 6、8、9，发现 7 没来，于是发送三个 6 的 ACK。发送方收到 3 个 ACK 后，就会立刻重传 7 的报文</li>
</ul>
</li>
<li>Selective Acknowledgment（SACK）：这种方式需要在 TCP 头里加一个 SACK 的东西，可以将缓存的地图发送给发送方。例如可以发送 ACK6、SACK8、SACK9，有了地图，发送方一下子就能看出来是 7 丢了</li>
</ol>
<h4 id="流量控制"><a href="#流量控制" class="headerlink" title="流量控制"></a>流量控制</h4><ol>
<li>在每一个包的确认中，都会返回一个 AdvertisedWindow（rwnd） 大小。</li>
<li>当接受端处理信息过慢时，这个窗口会一直缩减直到 0，发送方就会停止发送。</li>
<li>这时，发送方会定时发送窗口探测数据包，看窗口是否有空余</li>
<li>接收方为了避免低能窗口综合征，会在窗口达到一定大小，或者缓冲区一半时才会通知发送方窗口有空余了</li>
</ol>
<h4 id="拥塞控制"><a href="#拥塞控制" class="headerlink" title="拥塞控制"></a>拥塞控制</h4><p>TCP 的拥塞控制就是在不堵塞，不丢包的情况下，尽量发挥带宽</p>
<p>拥塞也是通过窗口来控制，这个窗口被称为 cwnd （Congestion Window），有一个公式 LastByteSent - LastByteAcked &lt;= min {cwnd, rwnd} 共同来控制带宽</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200916211417.png" alt="TCP 发送接受过程"></p>
<p>如果一个设备如上图所示，只能同时处理 8 个包，如果再调大窗口，就会出现丢包，不想丢包的话就得在中间设备增加缓存，这样就会增加时延</p>
<p>TCP 的拥塞控制就是来避免出现上述问题</p>
<h4 id="BBR-算法"><a href="#BBR-算法" class="headerlink" title="BBR 算法"></a>BBR 算法</h4><p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200916211957.png" alt="20200916211957"></p>
<p>TCP 的拥塞控制有两个问题：</p>
<ol>
<li>丢包不代表通道已满，有可能是管子本身就漏水。例如公网本身就有丢包问题</li>
<li>TCP 的拥塞控制会等中间设备的缓存都填充满之后才降速</li>
</ol>
<p>BBR 拥塞算法：企图找到一个平衡点，就是通过不断的加快发送速度，将管道填满，但是不要填满中间设备的缓存，因为这样时延会增加，在这个平衡点可以很好的达到高带宽和低时延的平衡</p>
<h3 id="Socket-接口"><a href="#Socket-接口" class="headerlink" title="Socket 接口"></a>Socket 接口</h3><p>因为 TCP/IP 协议过于复杂，如果程序员每开发一个程序都需要自己处理报文的发送与接受等问题，效率太低了，于是由操作系统封装了一套接口，便于快速处理数据包的发送与接受</p>
<h4 id="基于-TCP-协议的-Socket"><a href="#基于-TCP-协议的-Socket" class="headerlink" title="基于 TCP 协议的 Socket"></a>基于 TCP 协议的 Socket</h4><p>函数调用过程：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200916230544.png" alt="20200916230544"></p>
<ul>
<li><p>服务端调用 socket 流程：</p>
<ol>
<li>调用 bind 监听 ip 和 端口</li>
<li>调用 listen 进入监听状态（监听 socket）</li>
<li>调用 accept，当 TCP 连接成功时，accpet 返回另一个 socket（已连接 socket）进行处理</li>
<li>和客户端进行通信</li>
</ol>
<p>重点：监听的 socket 和通信使用的 socket <strong>不相同</strong>，一个连接对应一个 socket</p>
</li>
<li><p>客户端调用 socket 流程：</p>
<ol>
<li>调用 connect 连接 ip 和 端口</li>
<li>和服务端进行通信</li>
</ol>
</li>
</ul>
<p>socket 在内核中读写过程：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200916231334.png" alt="20200916231334"></p>
<h4 id="基于-UDP-协议的-Socket"><a href="#基于-UDP-协议的-Socket" class="headerlink" title="基于 UDP 协议的 Socket"></a>基于 UDP 协议的 Socket</h4><p>函数调用过程：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200916231723.png" alt="20200916231723"></p>
<p>UDP 无连接，所以只需要一个 socket 就可以和多个客户端通信，每次通信的时候都会调用 sendto 和 recvfrom，都可以传入 IP 地址和端口</p>
<h4 id="应用处理-scoket-连接方式"><a href="#应用处理-scoket-连接方式" class="headerlink" title="应用处理 scoket 连接方式"></a>应用处理 scoket 连接方式</h4><p>单体应用可以承载的 socket 连接数是有限的，如何在资源有限的情况下，尽可能多的处理连接</p>
<ol>
<li>多进程</li>
<li>多线程</li>
<li>IO 复用（select）：一个线程通过 select 轮询的方式检查 socket</li>
<li>IO 复用（epoll）：采用事件通知的方式</li>
</ol>
<h2 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h2><h3 id="DHCP-协议"><a href="#DHCP-协议" class="headerlink" title="DHCP 协议"></a>DHCP 协议</h3><p>DHCP：Dynamic Host Configuration Protocol，动态主机配置协议</p>
<p>因为网络中的每一个主机都需要 ip，而在复杂公共环境下，不可能让管理员来手动给每一个设备分配 ip，所以出现了 DHCP。</p>
<p>DHCP 工作方式：</p>
<ol>
<li>新主机使用 0.0.0.0 向 255.255.255.255 发送 DHCP Discover 广播</li>
<li>DHCP Server 监听到这个广播，判断如果是新主机的话，通过 DHCP Offer 方式将 ip 池里的随机 ip 发送过去（也是广播形式）</li>
<li>新主机发送 DHCP Request 广播数据包，包中包含客户端的 MAC 地址、接受的租约中的 IP 地址、提供此租约的 DHCP 服务器地址等</li>
<li>DHCP Server 收到 DHCP Request 后，会返回 DHCP ACK 消息包，表明新主机已被加入网络</li>
<li>租约达成后，广播通知其他 DHCP Server</li>
</ol>
<h3 id="HTTP-协议"><a href="#HTTP-协议" class="headerlink" title="HTTP 协议"></a>HTTP 协议</h3><p>目前大部分的 HTTP 协议是 1.1，默认开启了 Keep-Alive 的，实现了连接复用</p>
<p>HTTP 请求的报文格式：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200917075642.png" alt="20200917075642"><br><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/%E8%AF%B7%E6%B1%82%E6%8A%A5%E6%96%87%E6%9E%84%E6%88%90.png" alt="请求报文"></p>
<ol>
<li><p>请求行<br> HTTP 请求常用的请求方法：</p>
<ol>
<li>get</li>
<li>post</li>
<li>put</li>
<li>patch</li>
<li>delete</li>
</ol>
</li>
<li><p>首部<br> 常用的 request headers：</p>
<ol>
<li>Accept-Type</li>
<li>Content-Type</li>
<li>Cache-control</li>
<li>Last-Modified | If-Modified-Since</li>
<li>Etag | If-None-Match</li>
</ol>
</li>
<li><p>正文实体</p>
</li>
</ol>
<p>一个完整的 HTTP 请求：</p>
<p><img src="https://static001.geekbang.org/resource/image/99/49/99c282efaca15deb79c7821c9c577349.jpg" alt="一个完整的 HTTP 请求"></p>
<h4 id="HTTP-2-0"><a href="#HTTP-2-0" class="headerlink" title="HTTP 2.0"></a>HTTP 2.0</h4><p>HTTP 2.0 为了尝试解决 HTTP 1.1 的实时性、并发性等问题，进行了以下操作：</p>
<ol>
<li>对头部进行压缩：将每次都携带的 k-v 结构建立索引，每次只发送索引</li>
<li>将一个连接切分成多个流</li>
<li>将所有传输信息分割为更小的帧：header 帧，data 帧</li>
</ol>
<p>HTTP 2.0 解决了</p>
<ol>
<li>HTTP 1.1 的队首阻塞问题，同时，也不需要通过 HTTP 1.x 的 pipeline 机制用多条TCP连接来实现并行请求与响应；</li>
<li>减少了 TCP 连接数对服务器性能的影响，同时将页面的多个数据 css、js、 jpg 等通过一个数据链接进行传输，能够加快页面组件的传输速度</li>
</ol>
<h3 id="QUIC-协议"><a href="#QUIC-协议" class="headerlink" title="QUIC 协议"></a>QUIC 协议</h3><p>因为 HTTP 协议底层是基于 TCP 协议的，严重依赖于包的串行处理，所以 google 出了一个基于 UDP 的 QUIC 协议，尝试解决 HTTP 的一些问题</p>
<p>QUIC 的机制：</p>
<ol>
<li>自定义连接机制<br> TCP 基于 [源 IP，端口，目的 IP，目的端口] 的四元数组进行连接握手，如果四元数组进行了 wifi 切换等，就会导致重连。<br> QUIC 以一个 64 位的随机数作为 ID 标识，只要 ID 不变，不需要重新连接</li>
<li>自定义重传机制  </li>
<li>无阻塞的多路复用</li>
<li>自定义流量控制</li>
</ol>
<h3 id="HTTPS-协议"><a href="#HTTPS-协议" class="headerlink" title="HTTPS 协议"></a>HTTPS 协议</h3><p>HTTP 协议在网络中由于是明文信息，容易遭到恶意份子的篡改，所以有了 TLS 协议对其进行加密，保证内容安全。<br>HTTP + TLS = HTTPS</p>
<p>TLS 加密原理：首先使用非对称加密协商出公钥，然后再使用这个公钥对内容进行对称加密</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200920011230.png" alt="20200920011230"></p>
<ol>
<li>对称加密：公钥机制，由公钥加密的内容也能由公钥解开</li>
<li>非对称加密：公私钥机制，只有对应的私钥才能解开公钥加密的内容。服务端公钥的正确性由 CA 证书来保证</li>
</ol>
<h3 id="DNS-协议"><a href="#DNS-协议" class="headerlink" title="DNS 协议"></a>DNS 协议</h3><p>在正常的用户上网过程中，用户是不可能能记住每一个网站的 ip 地址的，而且 ip 地址经常会因为 NAT、负载均衡等问题发生改变，这时候我们需要一个方便记忆 ip 的方法，DNS 协议就是当用户使用某个域名（比如 baidu.com）时，获取域名对应的 ip 地址的协议</p>
<blockquote>
<p>已知域名，求 IP 的协议</p>
</blockquote>
<p>DNS 服务器结构：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200920161444.png" alt="20200920161444"></p>
<p>DNS 递归查询过程：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200920224427.png" alt="20200920224427"></p>
<p>DNS 负载均衡：</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200920225045.png" alt="20200920225045"></p>
<h3 id="HTTPDNS-协议"><a href="#HTTPDNS-协议" class="headerlink" title="HTTPDNS 协议"></a>HTTPDNS 协议</h3><p>传统 DNS 存在以下问题：</p>
<ol>
<li>域名缓存：缓存是为了降低递归查询的次数，提高查询效率。<br> 但是带来的问题是<ol>
<li>服务器地址更改后没有及时刷新缓存，导致访问失败</li>
<li>客户端地址更改后导致全局负载均衡失败，缓存的并不是最优服务器地址</li>
</ol>
</li>
<li>域名转发：权威域名服务器的负载均衡会根据运营商的不同返回不同的服务器地址，但是域名查询由 A 运营商转发给 B 运营商查询，就会导致运营商判断错误</li>
<li>出口 NAT：NAT 会导致权威域名服务器判断运营商错误</li>
<li>域名更新：IP 更新后重新解析 DNS 会导致一定时间的延迟乃至服务不可用</li>
<li>解析延迟：DNS 查询需要递归遍历多个 DNS 服务器，延迟较高</li>
</ol>
<p>HTTPDNS 就是<strong>不走传统的 DNS 解析</strong>，而是<strong>应用自己搭建基于 HTTP 协议的 DNS 服务器集群</strong>，分布在多个地点和多个运营商。当客户端需要 DNS 解析的时候，直接通过 HTTP 协议进行请求这个服务器集群，得到就近的地址</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups/img/20200920230522.png" alt="20200920230522"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://time.geekbang.org/column/intro/85">趣谈网络协议</a></p>
<p><img src="https://static001.geekbang.org/resource/image/79/aa/79adec391c62b8cf90c210804b704daa.jpg" alt="网络协议知识图谱"></p>
<p><img src="https://img-blog.csdn.net/2018041112053246?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5NTIxNTU0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="OSI7层协议图谱"></p>
<p><a href="https://en.wikipedia.org/wiki/Network_socket">socket</a></p>
]]></content>
      <categories>
        <category>网络</category>
      </categories>
      <tags>
        <tag>think</tag>
        <tag>study</tag>
      </tags>
  </entry>
  <entry>
    <title>解放双手：在 EFB comwechat 中优雅转发微信聊天记录</title>
    <url>/2025/05/transfer-chat-history-in-efb/</url>
    <content><![CDATA[<h2 id="🌟-引言：为何需要这个功能？"><a href="#🌟-引言：为何需要这个功能？" class="headerlink" title="🌟 引言：为何需要这个功能？"></a>🌟 引言：为何需要这个功能？</h2><p><a href="https://github.com/ehForwarderBot/EhForwarderBot">EhForwarderBot (EFB)</a> 是一个强大的开源消息隧道框架，它允许我们将微信、QQ 等多种聊天平台的消息聚合到 Telegram (TG) 等统一的客户端中进行管理。其中，<code>efb-wechat-comwechat-slave</code> 是连接微信和 EFB 的一个热门选择。</p>
<p>然而，尽管 EFB 极大地提升了跨平台沟通的便利性，但在处理某些微信特有消息类型时，仍会遇到一些不便。例如，当朋友从微信转发来一段<strong>聊天记录</strong>，或者分享了一个<strong>小程序</strong>、<strong>公众号文章卡片</strong>时，我们想通过 TG 将这些内容再次转发给其他微信好友，往往会发现 EFB 默认不支持完美转发这类复杂消息。这时，我们可能不得不拿起手机，在微信原生应用中完成操作，稍显繁琐。</p>
<p>幸运的是，我发现底层的 <a href="https://github.com/ljc545w/ComWeChatRobot/blob/6c63780c2dc2c28a8ca7f114affd1e0d87c221e5/Python/http/wxDriver.py#L86">ComWeChatRobot</a> 实际上具备转发聊天记录等消息的能力。因此，我对 <code>efb-wechat-comwechat-slave</code> 进行了一些小小的定制，实现了在 TG 端直接操作转发这些微信原生消息的功能。</p>
<span id="more"></span>

<h2 id="🚀-操作指南：三步轻松转发"><a href="#🚀-操作指南：三步轻松转发" class="headerlink" title="🚀 操作指南：三步轻松转发"></a>🚀 操作指南：三步轻松转发</h2><p>通过这个自定义功能，转发微信聊天记录等消息变得非常简单：</p>
<ol>
<li><p><strong>标记目标消息</strong>：在 TG 中，对着你想要转发的那条来自微信的消息（比如一段聊天记录），回复 <code>/forward</code> 命令。</p>
<blockquote>
<p>✨ <strong>提示</strong>：此命令仅用于标记，不会立即发送。</p>
</blockquote>
</li>
<li><p><strong>确认转发标记</strong>：发送 <code>/forward</code> 后，原消息下方会出现一个特殊的“转发标记”符号，表明这条消息已被选定。<br> <img src="https://raw.githubusercontent.com/jiz4oh/backups/master/img/2025/05/upgit_20250509_1746775787.png" alt="转发标记示例"><br> <em>图1: 消息已附加转发标记</em></p>
</li>
<li><p><strong>执行 Telegram 转发</strong>：现在，使用 Telegram 自带的转发功能，将这条带有“转发标记”的消息转发给你想发送的微信好友或群聊。<br> <img src="https://raw.githubusercontent.com/jiz4oh/backups/master/img/2025/05/upgit_20250509_1746775857.png" alt="upgit_20250509_1746775857.png"><br> <em>图2: 转发功能</em></p>
</li>
</ol>
<p>完成以上步骤后，你的微信好友将会收到一条完整的、与微信原生格式一致的聊天记录（或其他类型的消息）。</p>
<p><img src="https://raw.githubusercontent.com/jiz4oh/backups/master/img/2025/05/upgit_20250509_1746775888.png" alt="微信接收效果"><br><em>图3: 微信好友收到的消息样式（此为 ComWeChat 显示效果，实际在微信中为原生格式）</em></p>
<h2 id="🎯-功能价值与适用场景"><a href="#🎯-功能价值与适用场景" class="headerlink" title="🎯 功能价值与适用场景"></a>🎯 功能价值与适用场景</h2><p>你可能会问，简单的文本和图片不是可以直接复制粘贴或者通过 TG 的普通转发实现吗？确实如此。但这个功能的真正价值在于处理那些 <strong>Telegram 无法直接完美呈现或转发的微信原生消息类型</strong>，例如：</p>
<ul>
<li>  <strong>合并转发的聊天记录</strong>：这是最主要的应用场景。</li>
<li>  <strong>小程序卡片</strong>：轻松分享实用的小程序给朋友。</li>
<li>  <strong>公众号文章/分享卡片</strong>：保持原始的卡片样式进行分享。</li>
<li>  <strong>视频号内容</strong>等其他复杂消息。</li>
</ul>
<p>通过 <code>/forward</code> 命令预处理后，这些消息就能以接近微信原生的方式被转发，极大地提升了使用 EFB 时的体验和效率。</p>
<h2 id="🛠️-实现思路简介"><a href="#🛠️-实现思路简介" class="headerlink" title="🛠️ 实现思路简介"></a>🛠️ 实现思路简介</h2><p>对于有兴趣了解其背后原理的朋友，这里简单提一下：</p>
<p>此功能的核心在于利用了 <code>ComWeChatRobot</code> 提供的底层接口。<code>efb-wechat-comwechat-slave</code> 在接收到 <code>/forward</code> 指令后，会将目标消息的 ID 缓存起来。当用户通过 Telegram 转发这条被标记的消息时，slave 插件会识别到这个特殊标记，并调用 <code>ComWeChatRobot</code> 的相应方法，将原始消息内容转发到目标会话，而不是简单地传递 Telegram 格式的消息。</p>
<p>这确保了即使是复杂的微信原生消息，也能被正确地重新发送。</p>
]]></content>
      <categories>
        <category>技术教程</category>
        <category>其他</category>
      </categories>
      <tags>
        <tag>efb</tag>
        <tag>ehforwarderbot</tag>
        <tag>telegram</tag>
        <tag>wechat</tag>
      </tags>
  </entry>
  <entry>
    <title>vim 中使用 esc 切换英文输入</title>
    <url>/2020/10/vim-fast-esc/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 vim 中使用编辑模式进行了中文输入之后，切换到普通模式时，必须手动切换到英文模式才能进行命令输入，不太方便，故在网上找了找如何自动切换中英文的解决方案</p>
<span id="more"></span>

<h2 id="rime-用户"><a href="#rime-用户" class="headerlink" title="rime 用户"></a>rime 用户</h2><h3 id="方式一"><a href="#方式一" class="headerlink" title="方式一"></a>方式一</h3><p>在方案中填入</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">key_binder/bindings</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token punctuation">&#123;</span> <span class="token key atrule">when</span><span class="token punctuation">:</span> always<span class="token punctuation">,</span> <span class="token key atrule">accept</span><span class="token punctuation">:</span> Escape<span class="token punctuation">,</span> <span class="token key atrule">toggle</span><span class="token punctuation">:</span> ascii_mode<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>这样就可以使用 Esc 键切换为英文模式，但是这样有一个弊端，必须得先按一次 Esc 切换为英文后，再按一次 Esc 切换为普通模式</p>
<h3 id="方式二（推荐）"><a href="#方式二（推荐）" class="headerlink" title="方式二（推荐）"></a>方式二（推荐）</h3><p>在发行版方案中填入</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">app_options</span><span class="token punctuation">:</span>
  <span class="token key atrule">应用</span><span class="token punctuation">:</span>
    <span class="token key atrule">vim_mode</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>示例：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">app_options</span><span class="token punctuation">:</span>
  <span class="token key atrule">com.googlecode.iterm2</span><span class="token punctuation">:</span>
    <span class="token key atrule">vim_mode</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>这样在 iterm 中使用 vim 的时候，就可以在编辑模式按一下 Esc 切换为英文并且 vim 切换为普通模式</p>
<p><strong>!截至此时（2020-11-05）官网 suirrel 稳定版 14.0 尚不支持该功能，可<a href="https://bintray.com/rime/squirrel/testing/0.14.0%2Bgit118aee6#files">下载测试版</a>常鲜</strong><br><em>如何查看当前版本是否支持该 feature？</em><br><em>检查 <code>build/squirrel.yaml</code> 文件 config_version 最低需求 0.34</em></p>
<h2 id="其他输入法用户"><a href="#其他输入法用户" class="headerlink" title="其他输入法用户"></a>其他输入法用户</h2><p>因为我是 mac 用户，所以暂时先介绍关于 mac 的设置方案，其他平台的设置方案请查看<a href="https://www.zhihu.com/question/25744174/answer/506519877">解决恼人的 vim 中文输入法切换问题</a></p>
<ol>
<li><p>安装依赖</p>
 <pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/myshov/xkbswitch-macosx.git
<span class="token function">cp</span> xkbswitch-macosx/bin/xkbswitch /usr/local/bin
<span class="token function">git</span> clone https://github.com/myshov/libxkbswitch-macosx.git
<span class="token function">cp</span> libxkbswitch-macosx/bin/libxkbswitch.dylib /usr/local/lib/<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
</li>
<li><p>安装 vim 插件 <a href="https://github.com/lyokha/vim-xkbswitch">vim-xkbswitch</a><br> Vundle 用户添加到 <code>.vimrc</code>：</p>
 <pre class="line-numbers language-vim" data-language="vim"><code class="language-vim">Plugin <span class="token string">'lyokha/vim-xkbswitch'</span>
<span class="token comment">" 然后执行 :PluginInstall</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

</li>
</ol>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://github.com/rime/squirrel/issues/124">[提案] app_options 参数设定追加 esc 自动切换 ascii_mode 选项</a></p>
<p><a href="https://www.zhihu.com/question/25744174/answer/506519877">解决恼人的 vim 中文输入法切换问题</a></p>
]]></content>
      <categories>
        <category>其他</category>
      </categories>
      <tags>
        <tag>rime</tag>
        <tag>vim</tag>
      </tags>
  </entry>
  <entry>
    <title>配置 Webpack 解析 @ 路径</title>
    <url>/2020/10/webpack-resovle-@-path/</url>
    <content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><code>Vue-cli</code> 默认配置了一个使用 <code>@</code> 表示 <code>src</code> 的功能，这个功能的原理是配置 <code>webpack</code> 解析路径，这篇文章来介绍如何配置 <code>Webpack</code> 使其他项目比如 <code>React</code> 也能使用这个功能</p>
<span id="more"></span>

<h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>在 <code>webpack</code> 的配置文件中，写入</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token literal-property property">resolve</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">// 自动补全的扩展名</span>
    <span class="token literal-property property">extensions</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'.js'</span><span class="token punctuation">,</span> <span class="token string">'.vue'</span><span class="token punctuation">,</span> <span class="token string">'.json'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token comment">// 默认路径代理</span>
    <span class="token comment">// 例如 import Vue from 'vue'，会自动到 'vue/dist/vue.common.js'中寻找</span>
    <span class="token literal-property property">alias</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
        <span class="token string-property property">'@'</span><span class="token operator">:</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'src'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token comment">// '@': paths.appSrc, // react</span>
        <span class="token string-property property">'@config'</span><span class="token operator">:</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'config'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string-property property">'vue$'</span><span class="token operator">:</span> <span class="token string">'vue/dist/vue.common.js'</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="IDEA-识别"><a href="#IDEA-识别" class="headerlink" title="IDEA 识别"></a>IDEA 识别</h2><p>在 <code>IDEA</code> 中无法正确解析 <code>@</code> 代表的路径，导致经常提醒 <code>Module is not installed</code></p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/20201015121929.png" alt="20201015121929"></p>
<!--more-->

<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>项目根目录新建 <code>jsconfig.json</code> 文件即可</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>
  <span class="token string-property property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
    <span class="token string-property property">"baseUrl"</span><span class="token operator">:</span> <span class="token string">"./"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"paths"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
      <span class="token string-property property">"@/*"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token string">"src/*"</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">&#125;</span>
  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
  <span class="token string-property property">"exclude"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token string">"node_modules"</span><span class="token punctuation">,</span>
    <span class="token string">"dist"</span><span class="token punctuation">,</span>
    <span class="token string">"build"</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
]]></content>
      <categories>
        <category>前端</category>
      </categories>
      <tags>
        <tag>webpack</tag>
      </tags>
  </entry>
  <entry>
    <title>谈谈免流</title>
    <url>/2023/07/zero-rating/</url>
    <content><![CDATA[<h2 id="免流的由来"><a href="#免流的由来" class="headerlink" title="免流的由来"></a>免流的由来</h2><p>不知道各位是否还记得曾经被 5 元 30M 支配的恐惧吗？<br>在大概 10 多年前，那时候的流量非常昂贵，网页也非常简单，大部分都是一些文字信息，<br>随着时间的推移，互联网在飞速发展，手机上网从文字转变为了以视频和图片为主，网页内容越来越丰富，10M 流量几个图片就用完了，流量需求大幅提升，<br>但是运营商套餐价格并没有随着需求的提升而降低，一度都是 5 元 30M 的离谱价格，<br>人们为了（<del>白嫖</del>）推进运营商的技术发展，研究出了免流</p>
<h2 id="什么是免流"><a href="#什么是免流" class="headerlink" title="什么是免流"></a>什么是免流</h2><p>“免流”通常是指在移动网络中使用特定的应用或服务而不消耗用户的数据流量，即上网不要钱。<br>通常由移动运营商或特定的应用提供商推出，旨在吸引更多用户使用这些应用或服务。</p>
<span id="more"></span>

<h2 id="免流的基本原理"><a href="#免流的基本原理" class="headerlink" title="免流的基本原理"></a>免流的基本原理</h2><p>上述的免流通常局限于某些应用，我们所聊的免流是不限于特定应用的免流，通过特定手段欺骗运营商的计费系统达到免费的目的。<br>首先我们要了解运营商的计费系统是如何工作的</p>
<p>访问正常网站：</p>
<pre class="mermaid">
flowchart LR

subgraph 运营商
    direction LR
    A[&quot;计费系统

    baidu.com
    计费 1M&quot;]--&gt;B[&quot;代理服务器

    baidu.com&quot;]
end

用户--baidu.com--&gt;运营商--&gt;百度服务器
</pre>

<p>访问免流网站：</p>
<pre class="mermaid">
flowchart LR

subgraph 运营商
    direction LR
    A[&quot;计费系统

    10086.cn
    不计费&quot;]--&gt;B[&quot;代理服务器

    10086.cn&quot;]
end

用户--10086.cn--&gt;运营商--&gt;中国移动服务器
</pre>

<p>免流的核心原理就是让计费系统以为用户在访问免流网站，但实际上却访问了正常网站</p>
<pre class="mermaid">
flowchart LR

subgraph 运营商
    direction LR
    A[&quot;计费系统

    10086.cn
    不计费&quot;]--&gt;B[&quot;代理服务器

    baidu.com&quot;]
end

用户--baidu.com--&gt;运营商--&gt;百度服务器
</pre>

<h2 id="免流的几种方式"><a href="#免流的几种方式" class="headerlink" title="免流的几种方式"></a>免流的几种方式</h2><h3 id="本地免流"><a href="#本地免流" class="headerlink" title="本地免流"></a>本地免流</h3><p>本地免流是在用户手机上运行一个代理程序对所有数据包进行修改，从而欺骗计费系统</p>
<pre class="mermaid">
flowchart LR

subgraph 手机
    direction LR
    浏览器--baidu.com--&gt;代理程序
end

subgraph 运营商
    direction LR
    A[&quot;计费系统

    10086.cn
    不计费&quot;]--&gt;B[&quot;代理服务器

    baidu.com&quot;]
end

手机--&gt;运营商--&gt;百度服务器
</pre>

<p>代理程序的主要实现方式是对 http 数据包请求头进行修改。</p>
<p>请求头中有两个关键字段 Host 和 <a href="https://www.cnblogs.com/xitang/archive/2011/11/07/2239454.html">X-Online-Host</a>。<br>假设计费系统是通过检测 http 数据包中的 Host 字段进行计费，代理服务器通过 http 数据包中的 X-Online-Host 进行实际数据访问，则可以通过修改 http headers 中的 Host 字段即可达到欺骗计费系统的目的。</p>
<p>运营商也不是傻子，对检测系统进行了更新，上述手段就失效了，后来各大网友开始了和运营商的斗志斗勇</p>
<ol>
<li>有插入两个 Host 字段的双 h 模式，让计费系统查看第一个 Host 字段，让代理服务器查看第二个 Host 字段</li>
<li>也有插入两个 X-Online-Host 的双 x 模式</li>
<li>伪首模式</li>
<li>伪彩模式</li>
<li>…</li>
</ol>
<p>本地免流优点是不需要额外的资源，在用户本地手机即可实现，主要利用的是运营商计费系统和代理服务器系统的实现差异；缺点是各地运营商的系统差异不一致，在上海能免的模式在广东并不一定通用，并且可利用修改的地方有限，所有模式被封禁后就无法使用了</p>
<p>本地免流最大的问题是因为当时运营商账单具有滞后性，当你之前使用的免流模式被修复后，流量已经开始正常计费，你却全然不知，第二天早上起来发现收到了 10086 的巨额账单</p>
<h3 id="定向免流"><a href="#定向免流" class="headerlink" title="定向免流"></a>定向免流</h3><p>2015 开始，国家要求三大运营商提速降费，漫游费逐渐被取消，流量都是全国通用，费用也越来越低，但流量费用还没低到可以任意挥霍的程度。<br>针对有些人喜欢看抖音，有些人喜欢看腾讯视频，三大运营商纷纷推出了定向流量卡，常见的有腾讯王卡，阿里宝卡等。<br>定向流量卡是针对某些互联网服务在通用流量之外给予大额的定向流量，比如腾讯王卡允许腾讯系应用免费使用最多 40G 流量。</p>
<p>定向免流就是针对这部分流量卡，将其他网站的流量伪装成特定应用的流量使用实现定向流量，并不是完全的无限流量。<br>比如使用腾讯王卡时将其他应用如抖音的流量伪装成腾讯系应用的流量</p>
<pre class="mermaid">
flowchart LR

subgraph 手机
    direction LR
    浏览器--baidu.com--&gt;代理程序
end

subgraph 运营商
    direction LR
    A[&quot;计费系统

    qq.com
    定向流量计费1M&quot;]--&gt;B[&quot;代理服务器

    baidu.com&quot;]
end

手机--&gt;运营商--&gt;百度服务器
</pre>

<h4 id="云端免流-云免"><a href="#云端免流-云免" class="headerlink" title="云端免流(云免)"></a>云端免流(云免)</h4><p>本地免流利用了计费系统和代理服务器之间的差异，后续差异被不断补全（感谢各大网友自费做 QA），甚至后来运营商取消了代理服务器，直接使用计费系统进行互联网访问，本地免流就此绝迹。<br>后面常用的一般都是云端免流，这是现在的主流免流方式。</p>
<p>云端免流的原理需要一点网络协议的基础知识，了解 tcp/ip 的工作原理</p>
<h5 id="ip"><a href="#ip" class="headerlink" title="ip"></a>ip</h5><p>现有互联网是基于 tcp/ip 为架构组成的，计算机之间的通信并不是基于域名而是基于 ip 协议。</p>
<p>ip 协议可以理解为是计算机中的电话号码，比如我们想要联系张三的时候，我们可以通过输入张三的电话号码 123456 拨打电话联系到他</p>
<h5 id="dns"><a href="#dns" class="headerlink" title="dns"></a>dns</h5><p>但目前我们访问网站的时候是通过输入域名，为什么计算机之间的通信却基于 ip 协议？他们之间是如何转换的？</p>
<p>这就是 dns 所做的事情。<br>因为 ip 地址不易于记忆以及输入，所以域名被发明出来简化输入，相当于是电话号码的联系人名称，比如我们通常在电话薄里面保存张三的电话号码为 123456，张三就相当于是域名，123456 是 ip，我们想要联系张三的时候只需要在电话薄里面搜索 ‘张三’，而不是输入 123456</p>
<p>dns 的作用就是查询电话薄，比如我想要访问 google.com 的时候，计算机并不知道应该如何访问到 google.com，所以它需要去向电话薄查询 (dns query) google.com 的电话 (ip)</p>
<pre class="mermaid">
sequenceDiagram
    participant User
    participant Computer
    participant DNS Server
    participant Google

User-&gt;&gt;Computer: User open browser and enter &#39;google.com&#39;
Computer-&gt;&gt; DNS Server: Computer send DNS query to DNS Server
DNS Server--&gt;&gt;Computer: DNS Server response 123456 to Computer
Computer-&gt;&gt;Google: Computer send request to Google
Google--&gt;&gt;Computer: Google response to Computer
Computer--&gt;&gt;User: Computer display the response
</pre>

<h5 id="http"><a href="#http" class="headerlink" title="http"></a>http</h5><p>http 协议是普通人最常见的互联网协议，是互联网的基石，我们常见的所有应用几乎都是使用 http 协议进行通信。</p>
<p>http 协议请求结构如下</p>
<pre class="line-numbers language-none"><code class="language-none">GET &#x2F; HTTP&#x2F;1.1
Host: www.baidu.com

xxxxxxx此处即是http请求内容正文xxxxxxx<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>在本地免流中，我们采用的是修改 Host 字段绕过计费系统。<br>在云端免流中，采用的方式其实也类似，区别其实是在于绕过计费系统之后如何访问正确的目标网站？</p>
<p>思路其实很简单，既然本地免流由于运营商的代理服务器和计费系统合并而被彻底修复，那我们如果能够自己实现一个代理服务器不就和之前的方式类似了吗？</p>
<pre class="mermaid">
flowchart LR

subgraph 运营商
    direction LR
    A[&quot;计费系统

    10086.cn
    不计费&quot;]
end

subgraph 云端服务器
    direction LR
    B[&quot;baidu.com&quot;]
end

用户--baidu.com--&gt;运营商--&gt;云端服务器--&gt;百度服务器
</pre>

<h5 id="云免原理"><a href="#云免原理" class="headerlink" title="云免原理"></a>云免原理</h5><p>具体是将原始请求封装到 http body 中并发送给代理服务器，代理服务器解析 http body 之后还原原始请求并进行实际请求</p>
<p><img src="https://cdn.jsdelivr.net/gh/jiz4oh/backups@master/img/2023/08/upgit_20230808_1691510056.png" alt="upgit_20230808_1691510056.png"></p>
<p>即</p>
<pre class="mermaid">
sequenceDiagram
    participant User
    participant 运营商
    participant 云端服务器 as 云端服务器(10.10.10.10)
    participant 百度服务器 as 百度服务器(3.3.3.3)

User--&gt;&gt;运营商: 发送一个数据包到 10.10.10.10 并在 http header 中标注这是发给 10086.cn 的数据包
运营商--&gt;&gt;云端服务器: 计为免费并将数据包发送给 10.10.10.10
云端服务器--&gt;&gt;百度服务器: 解开数据包并发送给实际请求地址 3.3.3.3
百度服务器-&gt;&gt;云端服务器: 返回内容
云端服务器-&gt;&gt;运营商: 返回内容
运营商-&gt;&gt;User: 返回内容
</pre>

<h4 id="直连免流"><a href="#直连免流" class="headerlink" title="直连免流"></a>直连免流</h4><p>定向免流还有一种免流方式，不需要云端服务器。<br>有些定向卡是允许免去某个应用的所有流量而不是某个特定域名的流量，比如腾讯王卡可以免去 qq 浏览器的所有流量，原理是在该应用中内置一个代理服务器，代理服务器产生的流量被运营商计入定向流量。</p>
<p>直连免流就是通过抓包获取应用内置的代理服务器并将其用于所有应用访问。</p>
<p>不过因为现在代理服务器都增加了动态验证，基本都失效了。</p>
<h3 id="停机免流"><a href="#停机免流" class="headerlink" title="停机免流"></a>停机免流</h3><p>电信会在用户停机之后给用户开通一个花费充值的绿色通道，这样即使你停机之后仍然能够通过访问网络缴存话费。所以和定向免流类似，停机免流的实现原理也是通过将 Host 改为绿通的网址就能实现免流了。</p>
<p>对于停机用户来说，每个月只需要缴纳停机保号费用（通常5元）即可实现无限流量。不过电信随即采取了相应的策略，大部分地区对停机用户的上网速度施加了限制，毕竟你充个话费要那么快干嘛。</p>
<h2 id="免流的局限性"><a href="#免流的局限性" class="headerlink" title="免流的局限性"></a>免流的局限性</h2><p>因为运营商的计费系统如何工作其实是一个黑盒，外界很难得知什么时候工作机制就发生了变化，比如除了检查 host 字段，计费系统也可以检查端口。互联网的流量通常使用的都是 80/443，所以云端服务器的端口通常必须使用 80/443。</p>
<p>另一个缺点是为了实现免流必须使用全局代理，也就是不管国内还是国外流量均需通过云端服务器进行代理，如果云端服务器在国外，那么访问国内的服务会变得很慢，并且有些服务只能是国内 ip 才能使用；如果云端服务器在国内，那还需要一台额外的国外节点进行翻墙代理，以及国内服务器商宽其实非常昂贵，一年花销可能需要好几千，买服务器的钱可能比你免的流量贵的多得多。</p>
]]></content>
      <categories>
        <category>网络</category>
      </categories>
      <tags>
        <tag>白嫖</tag>
      </tags>
  </entry>
</search>
