<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>麦麦小家</title>
  
  <subtitle>编程苦旅</subtitle>
  <link href="https://blog.micblo.com/atom.xml" rel="self"/>
  
  <link href="https://blog.micblo.com/"/>
  <updated>2023-04-16T04:33:43.676Z</updated>
  <id>https://blog.micblo.com/</id>
  
  <author>
    <name>Payne</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>用JavaScript解析Android XML(.axml)</title>
    <link href="https://blog.micblo.com/2022/06/14/%E7%94%A8JavaScript%E8%A7%A3%E6%9E%90Android-XML-axml/"/>
    <id>https://blog.micblo.com/2022/06/14/%E7%94%A8JavaScript%E8%A7%A3%E6%9E%90Android-XML-axml/</id>
    <published>2022-06-14T22:08:37.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近搞了个网页解析Android APP 安装包 <code>.apk</code> 中的 <code>AndroidManifest.xml</code> 的小工具。</p><img src="/2022/06/14/%E7%94%A8JavaScript%E8%A7%A3%E6%9E%90Android-XML-axml/parse-apk-manifest.jpg" class="" title="解析AndroidManifest.xml的小工具"><p>其中，整个项目最复杂的地方就是 <code>axml</code> 转换 <code>xml</code> 的实现。<br>因为 <code>AndroidManifest.xml</code> 并非是一个真正的 <code>XML</code>，而是一个 <code>axml</code> 格式的文件，需要借助特殊的工具解析、转换到 <code>XML</code>。</p><p>在 npm 上找了半天，没找到符合我要求的库。</p><blockquote><p>NPM 上 <code>axml</code> 那个库和 Android 的 XML 完全没有关系！</p></blockquote><p>没办法，只能自己动手实现一下了！</p><p>借鉴了 <a href="https://github.com/xgouchet/AXML">https://github.com/xgouchet/AXML</a> 的源代码，我迁移了一个 JavaScript 版本的 <a href="https://www.npmjs.com/package/axml2xml">axml2xml</a>。伸手党可以直接用下面的命令添加这个库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install axml2xml --save</span><br></pre></td></tr></table></figure><p>转换一个 <code>axml</code> 文件为 <code>xml</code> 文件的示例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Axml2Xml</span> = <span class="built_in">require</span>(<span class="string">&#x27;axml2xml&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> buf = fs.<span class="title function_">readFileSync</span>(<span class="string">&#x27;./tests/AndroidManifest.xml&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Axml2Xml</span>.<span class="title function_">convert</span>(buf));</span><br></pre></td></tr></table></figure><p>转换得到的 <code>XML</code> 可以用其他库去解析，非常方便。</p><p>更多的使用方法可以 <a href="https://www.npmjs.com/package/axml2xml">查看项目的NPM 介绍</a>，源代码参见 <a href="https://github.com/micblo/axml2xml">https://github.com/micblo/axml2xml</a>。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近搞了个网页解析Android APP 安装包 &lt;code&gt;.apk&lt;/code&gt; 中的 &lt;code&gt;AndroidManifest.xml&lt;/code&gt; 的小工具。&lt;/p&gt;
&lt;img src=&quot;/2022/06/14/%E7%94%A8JavaScript%E8%A7%A3%E6%9E%90Android-XML-axml/parse-apk-manifest.jpg&quot; class title=&quot;解析AndroidManifest.xml的小工具&quot;&gt;

&lt;p&gt;其中，整个项目最复杂的地方就是 &lt;code&gt;axml&lt;/code&gt; 转换 &lt;code&gt;xml&lt;/code&gt; 的实现。&lt;br&gt;因为 &lt;code&gt;AndroidManifest.xml&lt;/code&gt; 并非是一个真正的 &lt;code&gt;XML&lt;/code&gt;，而是一个 &lt;code&gt;axml&lt;/code&gt; 格式的文件，需要借助特殊的工具解析、转换到 &lt;code&gt;XML&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在 npm 上找了半天，没找到符合我要求的库。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NPM 上 &lt;code&gt;axml&lt;/code&gt; 那个库和 Android 的 XML 完全没有关系！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;没办法，只能自己动手实现一下了！&lt;/p&gt;
&lt;p&gt;借鉴了 &lt;a href=&quot;https://github.com/xgouchet/AXML&quot;&gt;https://github.com/xgouchet/AXML&lt;/a&gt; 的源代码，我迁移了一个 JavaScript 版本的 &lt;a href=&quot;https://www.npmjs.com/package/axml2xml&quot;&gt;axml2xml&lt;/a&gt;。伸手党可以直接用下面的命令添加这个库：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;npm install axml2xml --save&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;转换一个 &lt;code&gt;axml&lt;/code&gt; 文件为 &lt;code&gt;xml&lt;/code&gt; 文件的示例：&lt;/p&gt;
&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Axml2Xml&lt;/span&gt; = &lt;span class=&quot;built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;axml2xml&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; buf = fs.&lt;span class=&quot;title function_&quot;&gt;readFileSync&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;./tests/AndroidManifest.xml&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;title class_&quot;&gt;Axml2Xml&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;convert&lt;/span&gt;(buf));&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Android 10 系统库不可读的坑点</title>
    <link href="https://blog.micblo.com/2022/04/26/Android-10-%E7%B3%BB%E7%BB%9F%E5%BA%93%E4%B8%8D%E5%8F%AF%E8%AF%BB%E7%9A%84%E5%9D%91%E7%82%B9/"/>
    <id>https://blog.micblo.com/2022/04/26/Android-10-%E7%B3%BB%E7%BB%9F%E5%BA%93%E4%B8%8D%E5%8F%AF%E8%AF%BB%E7%9A%84%E5%9D%91%E7%82%B9/</id>
    <published>2022-04-26T20:41:36.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近做项目的时候遇到一个神奇的崩溃。<br>这个崩溃都集中发生在 <code>Android 10</code> 的机器上。</p><p>崩溃堆栈类似下面：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">pid: 2938, tid: 2940, name: crasher64  &gt;&gt;&gt; crasher64 &lt;&lt;&lt;</span><br><span class="line">signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x5f2ced24a8</span><br><span class="line">Cause: execute-only (no-read) memory access error; likely due to data in .text.</span><br><span class="line">    x0  0000000000000000  x1  0000005f2cecf21f  x2  0000000000000078  x3  0000000000000053</span><br><span class="line">    x4  0000000000000074  x5  8000000000000000  x6  ff71646772607162  x7  00000020dcf0d16c</span><br><span class="line">    x8  0000005f2ced24a8  x9  000000781251c55e  x10 0000000000000000  x11 0000000000000000</span><br><span class="line">    x12 0000000000000014  x13 ffffffffffffffff  x14 0000000000000002  x15 ffffffffffffffff</span><br><span class="line">    x16 0000005f2ced52f0  x17 00000078125c0ed8  x18 0000007810e8e000  x19 00000078119fbd50</span><br><span class="line">    x20 00000078125d6020  x21 00000078119fbd50  x22 00000b7a00000b7a  x23 00000078119fbdd8</span><br><span class="line">    x24 00000078119fbd50  x25 00000078119fbd50  x26 00000078119fc018  x27 00000078128ea020</span><br><span class="line">    x28 00000078119fc020  x29 00000078119fbcb0</span><br><span class="line">    sp  00000078119fba40  lr  0000005f2ced1b94  pc  0000005f2ced1ba4</span><br><span class="line"></span><br><span class="line">backtrace:</span><br><span class="line">      #00 pc 0000000000003ba4  /system/bin/crasher64 (do_action+2348)</span><br><span class="line">      #01 pc 0000000000003234  /system/bin/crasher64 (thread_callback+44)</span><br><span class="line">      #02 pc 00000000000e2044  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)</span><br><span class="line">      #03 pc 0000000000083de0  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)</span><br></pre></td></tr></table></figure><p>通过 Bugly 上报的数据发现，这个 <code>SEGV_ACCERR</code> 的崩溃（无访问权限）发生在读取 <code>libc.so</code> 的代码段(<code>.text</code>)内存的时候。<br>观察其他信息发现，<code>libc.so</code>的代码段权限只剩下 <code>--xp</code>。<br>怪不得我们一访问就崩溃，<strong>是因为代码段没有可读权限了！</strong><br>同样的问题也发生在其他系统库上。</p><p>当时的解决方案是，通过读取 <code>/proc/self/maps</code> 避开系统库无可读权限的用户。</p><p>直到我不小心看到了<a href="https://source.android.google.cn/devices/tech/debug/native-crash?hl=zh-cn#xom">这个文档</a>……</p><blockquote><p><strong>只执行内存违规（仅限 Android 10）</strong></p><p>仅对于 Android 10 中的 arm64，二进制文件和库的可执行部分会映射到只执行（不可读取）内存，作为防范代码重用攻击的一种安全强化技术。此缓解方法与其他缓解方法产生了不良互动，后已被移除。</p><p>将代码设为不可读会导致故意或意外读入已标记为只执行的内存段抛出 SIGSEGV，并且代码为 SEGV_ACCERR。这可能是因为错误、漏洞、混合了代码的数据（例如文字池）或故意进行的内存自省导致的。</p><p>编译器会假设代码和数据不是混合在一起的，但是手写程序集会导致出现问题。在许多情况下，只需将常量移动到 .data 部分，即可解决这些问题。如果可执行代码段绝对有必要进行代码内省，应首先调用 mprotect(2) 以将该代码标记为可读，然后在操作完成后重新将该代码标记为不可读。</p></blockquote><p>寻思这不是Android系统你在搞事情啊！<br>Android 官方提供的解决方案是访问之前先 <code>mprotect</code> 一下需要读的内存，给他们加上可读权限。</p><p>提供一个伪代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> lib = <span class="built_in">dlopen</span>(<span class="string">&#x27;libc.so&#x27;</span>);</span><br><span class="line"><span class="keyword">auto</span> fn_open = <span class="built_in">dlsym</span>(lib, <span class="string">&#x27;open&#x27;</span>);</span><br><span class="line"><span class="keyword">auto</span> page_start = (fn_open &gt;&gt; <span class="number">12</span>) &lt;&lt; <span class="number">12</span>;</span><br><span class="line"><span class="built_in">mprotect</span>(page_start, <span class="number">0x1000</span>, PROT_READ | PROT_EXEC);</span><br><span class="line"><span class="type">int</span> *code = fn_open;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;code: 0x%X&quot;</span>, *code);</span><br></pre></td></tr></table></figure><p><strong>亲测该方案是有效的。</strong><br>不过为了保守，我们用完了记得把权限设置回去只可执行，以避免一些未知的意外崩溃。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近做项目的时候遇到一个神奇的崩溃。&lt;br&gt;这个崩溃都集中发生在 &lt;code&gt;Android 10&lt;/code&gt; 的机器上。&lt;/p&gt;
&lt;p&gt;崩溃堆栈类似下面：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pid: 2938, tid: 2940, name: crasher64  &amp;gt;&amp;gt;&amp;gt; crasher64 &amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x5f2ced24a8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Cause: execute-only (no-read) memory access error; likely due to data in .text.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x0  0000000000000000  x1  0000005f2cecf21f  x2  0000000000000078  x3  0000000000000053&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x4  0000000000000074  x5  8000000000000000  x6  ff71646772607162  x7  00000020dcf0d16c&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x8  0000005f2ced24a8  x9  000000781251c55e  x10 0000000000000000  x11 0000000000000000&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x12 0000000000000014  x13 ffffffffffffffff  x14 0000000000000002  x15 ffffffffffffffff&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x16 0000005f2ced52f0  x17 00000078125c0ed8  x18 0000007810e8e000  x19 00000078119fbd50&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x20 00000078125d6020  x21 00000078119fbd50  x22 00000b7a00000b7a  x23 00000078119fbdd8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x24 00000078119fbd50  x25 00000078119fbd50  x26 00000078119fc018  x27 00000078128ea020&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    x28 00000078119fc020  x29 00000078119fbcb0&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    sp  00000078119fba40  lr  0000005f2ced1b94  pc  0000005f2ced1ba4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;backtrace:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      #00 pc 0000000000003ba4  /system/bin/crasher64 (do_action+2348)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      #01 pc 0000000000003234  /system/bin/crasher64 (thread_callback+44)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      #02 pc 00000000000e2044  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      #03 pc 0000000000083de0  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;通过 Bugly 上报的数据发现，这个 &lt;code&gt;SEGV_ACCERR&lt;/code&gt; 的崩溃（无访问权限）发生在读取 &lt;code&gt;libc.so&lt;/code&gt; 的代码段(&lt;code&gt;.text&lt;/code&gt;)内存的时候。&lt;br&gt;观察其他信息发现，&lt;code&gt;libc.so&lt;/code&gt;的代码段权限只剩下 &lt;code&gt;--xp&lt;/code&gt;。&lt;br&gt;怪不得我们一访问就崩溃，&lt;strong&gt;是因为代码段没有可读权限了！&lt;/strong&gt;&lt;br&gt;同样的问题也发生在其他系统库上。&lt;/p&gt;
&lt;p&gt;当时的解决方案是，通过读取 &lt;code&gt;/proc/self/maps&lt;/code&gt; 避开系统库无可读权限的用户。&lt;/p&gt;
&lt;p&gt;直到我不小心看到了&lt;a href=&quot;https://source.android.google.cn/devices/tech/debug/native-crash?hl=zh-cn#xom&quot;&gt;这个文档&lt;/a&gt;……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;只执行内存违规（仅限 Android 10）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;仅对于 Android 10 中的 arm64，二进制文件和库的可执行部分会映射到只执行（不可读取）内存，作为防范代码重用攻击的一种安全强化技术。此缓解方法与其他缓解方法产生了不良互动，后已被移除。&lt;/p&gt;
&lt;p&gt;将代码设为不可读会导致故意或意外读入已标记为只执行的内存段抛出 SIGSEGV，并且代码为 SEGV_ACCERR。这可能是因为错误、漏洞、混合了代码的数据（例如文字池）或故意进行的内存自省导致的。&lt;/p&gt;
&lt;p&gt;编译器会假设代码和数据不是混合在一起的，但是手写程序集会导致出现问题。在许多情况下，只需将常量移动到 .data 部分，即可解决这些问题。如果可执行代码段绝对有必要进行代码内省，应首先调用 mprotect(2) 以将该代码标记为可读，然后在操作完成后重新将该代码标记为不可读。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寻思这不是Android系统你在搞事情啊！&lt;br&gt;Android 官方提供的解决方案是访问之前先 &lt;code&gt;mprotect&lt;/code&gt; 一下需要读的内存，给他们加上可读权限。&lt;/p&gt;
&lt;p&gt;提供一个伪代码:&lt;/p&gt;
&lt;figure class=&quot;highlight c++&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;auto&lt;/span&gt; lib = &lt;span class=&quot;built_in&quot;&gt;dlopen&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;libc.so&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;auto&lt;/span&gt; fn_open = &lt;span class=&quot;built_in&quot;&gt;dlsym&lt;/span&gt;(lib, &lt;span class=&quot;string&quot;&gt;&amp;#x27;open&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;auto&lt;/span&gt; page_start = (fn_open &amp;gt;&amp;gt; &lt;span class=&quot;number&quot;&gt;12&lt;/span&gt;) &amp;lt;&amp;lt; &lt;span class=&quot;number&quot;&gt;12&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;mprotect&lt;/span&gt;(page_start, &lt;span class=&quot;number&quot;&gt;0x1000&lt;/span&gt;, PROT_READ | PROT_EXEC);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; *code = fn_open;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;printf&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;code: 0x%X&amp;quot;&lt;/span&gt;, *code);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>从PPT中提取Flash文件</title>
    <link href="https://blog.micblo.com/2020/04/27/%E4%BB%8EPPT%E4%B8%AD%E6%8F%90%E5%8F%96Flash%E6%96%87%E4%BB%B6/"/>
    <id>https://blog.micblo.com/2020/04/27/%E4%BB%8EPPT%E4%B8%AD%E6%8F%90%E5%8F%96Flash%E6%96%87%E4%BB%B6/</id>
    <published>2020-04-27T03:25:02.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>在远古的过去（Win7之前），Adobe Flash Player 的 ActiveX插件还可以在PowerPoint中使用。当年有不少PPT中内嵌了Flash 的教案。</p><p>在2020的现在，当年的那些PowerPoint里面的Flash已经无法在Office 2016以及更高的版本播放了。更糟糕的是，你想直接导出文件也变得极度困难。</p><p>目前网上常见的招数是使用WinHex之类的软件直接硬导出 <code>.ppt</code> 内的文件。该方法虽然在一定程度上可行，但是过于硬核，实际操作难度非常大。</p><p>为了解决这个问题，我从另一个角度提取PPT中的Flash文件。</p><p>这种方法必须将PowerPoint文件转成 <code>.pptx</code> 格式。原因是 <code>.pptx</code> 格式的本质是一个 <code>.zip</code> 的压缩包，我们可以使用压缩工具解压里面的资源文件。</p><p>使用压缩工具打开 <code>.pptx</code> 文件后，可以在 <code>ppt/activeX</code> 文件夹找到PowerPoint中所有的 ActiveX 插件对应的原始的原始文件。其名称类似于 <code>activeX&#123;数字&#125;.bin</code>。</p><p>那如何识别这个 <code>.bin</code> 文件原本是什么文件呢？很简单，该文件后缀为 <code>.xml</code> 的资源配置文件里面就有答案。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> standalone=<span class="string">&quot;no&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">ax:ocx</span> <span class="attr">ax:classid</span>=<span class="string">&quot;&#123;D27CDB6E-AE6D-11CF-96B8-444553540000&#125;&quot;</span> <span class="attr">ax:persistence</span>=<span class="string">&quot;persistStorage&quot;</span> <span class="attr">r:id</span>=<span class="string">&quot;rId1&quot;</span> <span class="attr">xmlns:ax</span>=<span class="string">&quot;http://schemas.microsoft.com/office/2006/activeX&quot;</span> <span class="attr">xmlns:r</span>=<span class="string">&quot;http://schemas.openxmlformats.org/officeDocument/2006/relationships&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>上面是一个Flash文件的资源配置文件内容，其中 <code>classid</code> 的值是代表该文件被什么 ActiveX 插件使用。</p><p>然后，<code>classid</code> 为 <code>D27CDB6E-AE6D-11CF-96B8-444553540000</code> 时，代表该 <code>.bin</code> 文件内存有的是 Flash 文件（<code>.swf</code>）。</p><p>是的，你没有听错，这个 <code>.bin</code> 文件还不是 <code>.swf</code> 格式的！我们还需要从里面提取文件。</p><p>使用 WinHex 打开这些 <code>.bin</code> 文件，我们可以发现它们的文件头都是 <code>D0CF11E0A1B11AE1</code>。</p><p>通过Google这个文件头，可以很容易发现这些 <code>.bin</code> 文件都是 <code>.cfb</code> 格式的文件 （<a href="https://www.loc.gov/preservation/digital/formats/fdd/fdd000392.shtml">The Microsoft Compound File Binary (CFB) file format</a>）。</p><p>知道了这个线索之后，我们就可以找一些现成的 <code>.cfb</code> 格式的库来提取里面的文件了。</p><p>为了实现的方便，特意使用 GoLang 实现了 <code>.cfb</code> 格式的提取。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/richardlehane/mscfb&quot;</span></span><br><span class="line"><span class="string">&quot;io/ioutil&quot;</span></span><br><span class="line"><span class="string">&quot;os&quot;</span></span><br><span class="line"><span class="string">&quot;strings&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">info, err := ioutil.ReadDir(<span class="string">&quot;./&quot;</span>) <span class="comment">// 搜索当前文件夹里面所有文件</span></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Failed to read path\n&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(info); i++ &#123;</span><br><span class="line"><span class="comment">// 只有 ActiveX&#123;数字&#125;.bin 的文件名才是cfb文件</span></span><br><span class="line"><span class="keyword">if</span> strings.HasPrefix(info[i].Name(), <span class="string">&quot;activeX&quot;</span>) == <span class="literal">false</span> ||</span><br><span class="line">strings.HasSuffix(info[i].Name(), <span class="string">&quot;.bin&quot;</span>) == <span class="literal">false</span> &#123;</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 打开这个CFB文件</span></span><br><span class="line">file, _ := os.Open(<span class="string">&quot;./&quot;</span> + info[i].Name())</span><br><span class="line">doc, err := mscfb.New(file)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">_ = file.Close()</span><br><span class="line">fmt.Printf(<span class="string">&quot;Failed to read file %s\n&quot;</span>, info[i].Name())</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历导出里面的文件</span></span><br><span class="line"><span class="keyword">for</span> entry, err := doc.Next(); err == <span class="literal">nil</span>; entry, err = doc.Next() &#123;</span><br><span class="line">buf, err := ioutil.ReadAll(entry)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Failed to read content from CFB file. filename = %s, contentName = %s\n&quot;</span>,</span><br><span class="line">info[i].Name(), entry.Name)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置导出的文件的文件名，将数据写出到该文件中</span></span><br><span class="line">outputName := info[i].Name() + <span class="string">&quot;_&quot;</span> + info[i].Name() + <span class="string">&quot;.swf&quot;</span></span><br><span class="line"><span class="comment">// 需要从第8个字节开始提取，前八个字节非文件原始数据</span></span><br><span class="line">err = ioutil.WriteFile(outputName, buf[<span class="number">8</span>:], os.ModePerm) </span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Failed to read content from CFB file. filename = %s, contentName = %s, output = %s\n&quot;</span>,</span><br><span class="line">info[i].Name(), entry.Name, outputName)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码可以非常轻易把程序运行目录下全部的 ActiveX 的 <code>.bin</code> 文件里面的真实文件提取出来。此处假设都是Flash文件，所以格式都是 <code>.swf</code>。</p><p>看了一下网上还没有人提出这个方法，所以写出来分享给大家，有兴趣可以试验一下哈哈哈。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在远古的过去（Win7之前），Adobe Flash Player 的 ActiveX插件还可以在PowerPoint中使用。当年有不少PPT中内嵌了Flash 的教案。&lt;/p&gt;
&lt;p&gt;在2020的现在，当年的那些PowerPoint里面的Flash已经无法在Office 2016以及更高的版本播放了。更糟糕的是，你想直接导出文件也变得极度困难。&lt;/p&gt;
&lt;p&gt;目前网上常见的招数是使用WinHex之类的软件直接硬导出 &lt;code&gt;.ppt&lt;/code&gt; 内的文件。该方法虽然在一定程度上可行，但是过于硬核，实际操作难度非常大。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，我从另一个角度提取PPT中的Flash文件。&lt;/p&gt;
&lt;p&gt;这种方法必须将PowerPoint文件转成 &lt;code&gt;.pptx&lt;/code&gt; 格式。原因是 &lt;code&gt;.pptx&lt;/code&gt; 格式的本质是一个 &lt;code&gt;.zip&lt;/code&gt; 的压缩包，我们可以使用压缩工具解压里面的资源文件。&lt;/p&gt;
&lt;p&gt;使用压缩工具打开 &lt;code&gt;.pptx&lt;/code&gt; 文件后，可以在 &lt;code&gt;ppt/activeX&lt;/code&gt; 文件夹找到PowerPoint中所有的 ActiveX 插件对应的原始的原始文件。其名称类似于 &lt;code&gt;activeX&amp;#123;数字&amp;#125;.bin&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;那如何识别这个 &lt;code&gt;.bin&lt;/code&gt; 文件原本是什么文件呢？很简单，该文件后缀为 &lt;code&gt;.xml&lt;/code&gt; 的资源配置文件里面就有答案。&lt;/p&gt;
&lt;figure class=&quot;highlight xml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;lt;?xml version=&lt;span class=&quot;string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt; encoding=&lt;span class=&quot;string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;/span&gt; standalone=&lt;span class=&quot;string&quot;&gt;&amp;quot;no&amp;quot;&lt;/span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;ax:ocx&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;ax:classid&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;#123;D27CDB6E-AE6D-11CF-96B8-444553540000&amp;#125;&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;ax:persistence&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;persistStorage&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;r:id&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;rId1&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;xmlns:ax&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;http://schemas.microsoft.com/office/2006/activeX&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;xmlns:r&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;http://schemas.openxmlformats.org/officeDocument/2006/relationships&amp;quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;上面是一个Flash文件的资源配置文件内容，其中 &lt;code&gt;classid&lt;/code&gt; 的值是代表该文件被什么 ActiveX 插件使用。&lt;/p&gt;
&lt;p&gt;然后，&lt;code&gt;classid&lt;/code&gt; 为 &lt;code&gt;D27CDB6E-AE6D-11CF-96B8-444553540000&lt;/code&gt; 时，代表该 &lt;code&gt;.bin&lt;/code&gt; 文件内存有的是 Flash 文件（&lt;code&gt;.swf&lt;/code&gt;）。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Chromium 语音合成接口 SpeechSynthesis 实现分析</title>
    <link href="https://blog.micblo.com/2020/02/21/Chromium-%E8%AF%AD%E9%9F%B3%E5%90%88%E6%88%90%E6%8E%A5%E5%8F%A3-SpeechSynthesis-%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/"/>
    <id>https://blog.micblo.com/2020/02/21/Chromium-%E8%AF%AD%E9%9F%B3%E5%90%88%E6%88%90%E6%8E%A5%E5%8F%A3-SpeechSynthesis-%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/</id>
    <published>2020-02-21T11:28:16.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>语音合成（Text To Speech，TTS）可以实现网页文章的阅读。通过它，我们可以不需要看文字，就可以听新闻、听小说。</p><p>目前国内的主流的语音合成解决方案无非是以下几种：</p><ol><li>科大讯飞</li><li>Google</li><li>云服务（阿里云、腾讯云……）</li></ol><p>上述的方案，音质不错，语音包选择多。但是都有一定的免费配额或者语言的限制（如科大讯飞的试用语音包），没有完全免费的方案。</p><p>开源免费的方案的效果比较差，而且需要利用自己的服务器资源，需要考虑额外的成本。</p><p>那有没有什么免费可以使用的方案呢？还是有的！</p><p>这里就要搬出来 Web API 里面的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">SpeechSynthesis</a>。这个接口是浏览器原生支持的语音合成。</p><p>基本上现在主流的现代浏览器都支持这个接口。具体的浏览器支持情况可以查阅上面链接里的文档。</p><h2 id="如何使用？"><a href="#如何使用？" class="headerlink" title="如何使用？"></a>如何使用？</h2><p>下面就是一个简单例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ss = <span class="keyword">new</span> <span class="variable language_">window</span>.<span class="title class_">SpeechSynthesisUtterance</span>();</span><br><span class="line">ss.<span class="property">text</span> = <span class="string">&quot;你好，世界！&quot;</span>;</span><br><span class="line">ss.<span class="property">lang</span> = <span class="string">&quot;zh-CN&quot;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">speechSynthesis</span>.<span class="title function_">speak</span>(ss);</span><br></pre></td></tr></table></figure><p><code>SpeechSynthesisUtterance</code> 是配置语音合成内容和参数的类，有以下参数可以配置：</p><ul><li><code>lang</code>：文本所属语言，默认使用 <code>&lt;html&gt;</code> 里面的 <code>lang</code></li><li><code>pitch</code>：音调（<code>0</code>-<code>2.0</code>），默认是 <code>1</code></li><li><code>rate</code>：播放速率（<code>0.1</code>-<code>10.0</code>），默认是 <code>1</code></li><li><code>text</code>：需要说出来的文本</li><li><code>voice</code>：语音包名称</li><li><code>volume</code>：音量大小（<code>0</code>-<code>1.0</code>），默认是 <code>1</code></li></ul><p>同时可以配置以下事件：</p><ul><li><code>onerror</code>：出现错误</li><li><code>onstart</code>：开始播放</li><li><code>onboundary</code>：句子结束时触发</li><li><code>onend</code>：播放完成</li><li><code>onmark</code>：SSML 的 mark 标记触发</li><li><code>onpause</code>：被暂停播放</li><li><code>onresume</code>：被恢复播放</li></ul><p><code>speechSynthesis</code> 类实现语音合成的方法：</p><ul><li><code>SpeechSynthesis.getVoices()</code>：获取全部可以播放的语音包（含语言）</li><li><code>SpeechSynthesis.speak()</code>：按 <code>SpeechSynthesisUtterance</code> 配置播放合成语音</li><li><code>SpeechSynthesis.cancel()</code>：移除 <code>SpeechSynthesis</code> 播放队列里面的全部待播放语音</li><li><code>SpeechSynthesis.pause()</code>：暂停播放某个语音</li><li><code>SpeechSynthesis.resume()</code>：恢复播放某个语音</li></ul><p>具体的文档可以查看 MDN。</p><p>以 macOS 下的 Google Chrome 为例，通过 <code>window.speechSynthesis.getVoices()</code> 可以获取全部语音包，下面是一部分的输出：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;voiceURI&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Ting-Ting&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Ting-Ting&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh-CN&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;localService&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;voiceURI&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google 普通话（中国大陆）&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google 普通话（中国大陆）&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh-CN&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;localService&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>上述数组是 <code>SpeechSynthesisVoice</code> 的实例数组，<code>SpeechSynthesisVoice</code> 的参数如下：</p><ul><li><code>voiceURI</code>：语音合成服务的URL</li><li><code>name</code>：语音包名称</li><li><code>lang</code>：语言（符合BCP 47规范）</li><li><code>localService</code>：是本地语言包（离线）还是远程语言包（网络）</li><li><code>default</code>：是否为默认的语音包</li></ul><p>通过观察，可以发现，Google Chrome 在 macOS 下可以使用的语音包有 macOS 自带的本地语音包和谷歌提供的在线语音包。如苹果的「Ting-Ting」和谷歌的「Google 普通话（中国大陆）」。</p><p>在构建 <code>SpeechSynthesisUtterance</code> 的时候，传入支持的语音包的 <code>lang</code> 和 <code>voice</code> （对应 <code>SpeechSynthesisVoice</code> 的 <code>name</code>）就可以选择语音包了。</p><p>通过这个接口，我们可以给使用 Google Chrome 的浏览器用户提供来自谷歌的语音合成功能。</p><h2 id="探索-SpeechSynthesis-的原理"><a href="#探索-SpeechSynthesis-的原理" class="headerlink" title="探索 SpeechSynthesis 的原理"></a>探索 <code>SpeechSynthesis</code> 的原理</h2><p>下面，我们通过 Chromium 的源代码探索 Google Chrome 的 <code>SpeechSynthesis</code> 是怎么实现的。</p><p>Chromium 里面的 SpeechSynthesis 类都是在 <a href="https://cs.chromium.org/chromium/src/content/browser/speech/">speech 模块</a> 中实现的。其中，该模块包含 <em>语音识别</em>（speechRecognizer）和 <em>语音合成</em> （SpeechSynthesis）两大功能的实现。</p><p>看语音合成的头文件就大概知道有哪些功能：</p><ul><li><a href="https://cs.chromium.org/chromium/src/content/browser/speech/speech_synthesis_impl.h"><code>speech_synthesis_impl.h</code></a>: <code>SpeechSynthesis</code> 实现的后端，分派任务给 <code>TTS Conreoller</code></li><li><a href="https://cs.chromium.org/chromium/src/content/browser/speech/tts_controller_impl.h"><code>tts_controller_impl.h</code></a>: 语音合成的控制器，集成了获取语音包、设置引擎、播放、暂停等功能</li><li><a href="https://cs.chromium.org/chromium/src/content/browser/speech/tts_platform_impl.h"><code>tts_platform_impl.h</code></a>: 管理平台相关的语音包，具体的实现在</li></ul><h3 id="语音包怎么来的？"><a href="#语音包怎么来的？" class="headerlink" title="语音包怎么来的？"></a>语音包怎么来的？</h3><p>我们可以先看 <code>tts_controller_impl</code> 里面的 <code>GetVoices</code> 实现：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TtsControllerImpl::GetVoices</span><span class="params">(BrowserContext* browser_context,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  std::vector&lt;VoiceData&gt;* out_voices)</span> </span>&#123;</span><br><span class="line">  TtsPlatform* tts_platform = <span class="built_in">GetTtsPlatform</span>(); <span class="comment">// 获取操作系统相关的 TTS</span></span><br><span class="line">  <span class="keyword">if</span> (tts_platform) &#123; <span class="comment">// 当前平台存在 TTS</span></span><br><span class="line">    <span class="comment">// Ensure we have all built-in voices loaded. This is a no-op if already</span></span><br><span class="line">    <span class="comment">// loaded.</span></span><br><span class="line">    tts_platform-&gt;<span class="built_in">LoadBuiltInTtsEngine</span>(browser_context); <span class="comment">// 加载本地的 TTS</span></span><br><span class="line">    <span class="keyword">if</span> (tts_platform-&gt;<span class="built_in">PlatformImplAvailable</span>())</span><br><span class="line">      tts_platform-&gt;<span class="built_in">GetVoices</span>(out_voices);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (browser_context) &#123;</span><br><span class="line">    TtsControllerDelegate* delegate = <span class="built_in">GetTtsControllerDelegate</span>();</span><br><span class="line">    <span class="keyword">if</span> (delegate &amp;&amp; delegate-&gt;<span class="built_in">GetTtsEngineDelegate</span>()) <span class="comment">// 浏览器自带的 TTS</span></span><br><span class="line">      delegate-&gt;<span class="built_in">GetTtsEngineDelegate</span>()-&gt;<span class="built_in">GetVoices</span>(browser_context, out_voices);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单来说，<code>SpeechSynthesis</code> 的语音包来自两大地方——<strong>操作系统自带</strong>和<strong>浏览器自带</strong>。所以我们才发现 <code>window.speechSynthesis.getVoices()</code> 拿到的语音包存在一部分是 Apple 提供的，一部分是 Google 提供的。</p><h3 id="本地语音包的实现"><a href="#本地语音包的实现" class="headerlink" title="本地语音包的实现"></a>本地语音包的实现</h3><p>下面分开看不同的语音包是怎么调用的，先看操作系统自带的。</p><p>观察源代码，我们发现 <code>tts_platform_impl.h</code> 的实现是跟操作系统有关，不同的操作系统下的实现不一样：</p><ul><li><code>tts_android.cc</code>：安卓本地语音的实现，本质上是通过JNI调用 <code>android.speech.tts.TextToSpeech</code>，<a href="https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/TtsPlatformImpl.java">具体实现点我查看</a>，该接口与安卓系统「语音引擎」设置有关，原生安卓系统是 <code>Pico TTS</code>，国内大多是科大讯飞引擎</li><li><code>tts_fuchsia.cc</code>：Fuchsia 本地语音的实现</li><li><code>tts_linux.cc</code>：Linux 本地语音的实现，本质上是使用 <code>libspeechd.so.2</code> 库</li><li><code>tts_mac.mm</code>：macOS 本地语音的实现，本质上是调用 <a href="https://developer.apple.com/documentation/appkit/nsspeechsynthesizerdelegate">NSSpeechSynthesizerDelegate</a>，集成了 Apple 的语音包</li></ul><p>上述代码通过不同的操作系统下的本地语音合成调用方法完成了本地语音包的调用，此处不着墨过多，有兴趣的小伙伴可以看源代码深入了解。</p><h3 id="在线语音包的实现"><a href="#在线语音包的实现" class="headerlink" title="在线语音包的实现"></a>在线语音包的实现</h3><p>浏览器自带的在线语音包是通过 <code>TtsEngineDelegate</code> 类封装的。这个东西在 Chromium 里面是由 <a href="https://cs.chromium.org/chromium/src/chrome/browser/speech/extension_api/"><code>tts_extension</code></a> 实现的。因此，在线的语音包是以插件的形式存在于 Chrome 里面。</p><p>我们可以查阅 <a href="https://cs.chromium.org/chromium/src/chrome/browser/speech/extension_api/tts_engine_extension_api.cc">tts_engine_extension_api.cc</a> 的 <code>GetVoicesInternal</code> 实现，发现在线语音包的获取方式。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Get the voices for an extension, checking the preferences first</span></span><br><span class="line"><span class="comment">// (in case the extension has ever called UpdateVoices in the past),</span></span><br><span class="line"><span class="comment">// and the manifest second.</span></span><br><span class="line">std::unique_ptr&lt;std::vector&lt;extensions::TtsVoice&gt;&gt; <span class="built_in">GetVoicesInternal</span>(</span><br><span class="line">    content::BrowserContext* context,</span><br><span class="line">    <span class="type">const</span> extensions::Extension* extension) &#123;</span><br><span class="line">  <span class="comment">// 首先尝试从扩展的prefs里面拿到保存好的语音包数据集</span></span><br><span class="line">  <span class="keyword">auto</span>* extension_prefs = extensions::ExtensionPrefs::<span class="built_in">Get</span>(context);</span><br><span class="line">  <span class="type">const</span> base::ListValue* voices_data = <span class="literal">nullptr</span>;</span><br><span class="line">  <span class="comment">// kPrefTtsVoices = &quot;tts_voices&quot;</span></span><br><span class="line">  <span class="comment">// 从 tts_voices 字段读取一个语音包列表</span></span><br><span class="line">  <span class="keyword">if</span> (extension_prefs-&gt;<span class="built_in">ReadPrefAsList</span>(extension-&gt;<span class="built_in">id</span>(), kPrefTtsVoices,</span><br><span class="line">                                      &amp;voices_data)) &#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>* error = <span class="literal">nullptr</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">ValidateAndConvertToTtsVoiceVector</span>(</span><br><span class="line">        extension, *voices_data,</span><br><span class="line">        <span class="comment">/* return_after_first_error = */</span> <span class="literal">false</span>, &amp;error);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Fall back on the extension manifest.</span></span><br><span class="line">  <span class="keyword">auto</span>* manifest_voices = extensions::TtsVoices::<span class="built_in">GetTtsVoices</span>(extension);</span><br><span class="line">  <span class="keyword">if</span> (manifest_voices)</span><br><span class="line">    <span class="keyword">return</span> std::make_unique&lt;std::vector&lt;extensions::TtsVoice&gt;&gt;(</span><br><span class="line">        *manifest_voices);</span><br><span class="line">  <span class="keyword">return</span> std::make_unique&lt;std::vector&lt;extensions::TtsVoice&gt;&gt;();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">// namespace</span></span><br></pre></td></tr></table></figure><p>大概锁定到这个在线语音包的扩展在 <code>pref</code> 里面存储语音包的信息。</p><p>那么我们可以去 <a href="chrome://prefs-internals/">chrome:&#x2F;&#x2F;prefs-internals&#x2F;</a> 找一下这个扩展。然后就会惊奇发现到这个隐藏的插件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;neajdppkdcdipfabeoofebfddakdcjhd&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;active_permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;api&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;systemPrivate&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ttsEngine&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;explicit_host&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;https://www.google.com/*&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;manifest_permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;events&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;ttsEngine.onPause&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ttsEngine.onResume&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ttsEngine.onSpeak&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ttsEngine.onStop&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;manifest&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;background&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;tts_extension.js&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Component extension providing speech via the Google network text-to-speech service.&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;manifest_version&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google Network Speech&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;systemPrivate&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ttsEngine&quot;</span><span class="punctuation">,</span> <span class="string">&quot;https://www.google.com/&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;tts_engine&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;voices&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;event_types&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;start&quot;</span><span class="punctuation">,</span> <span class="string">&quot;end&quot;</span><span class="punctuation">,</span> <span class="string">&quot;error&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;gender&quot;</span><span class="punctuation">:</span> <span class="string">&quot;female&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh-CN&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;remote&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;voice_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google 普通话（中国大陆）&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;event_types&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;start&quot;</span><span class="punctuation">,</span> <span class="string">&quot;end&quot;</span><span class="punctuation">,</span> <span class="string">&quot;error&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;gender&quot;</span><span class="punctuation">:</span> <span class="string">&quot;female&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh-HK&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;remote&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;voice_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google 粤語（香港）&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;event_types&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;start&quot;</span><span class="punctuation">,</span> <span class="string">&quot;end&quot;</span><span class="punctuation">,</span> <span class="string">&quot;error&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;gender&quot;</span><span class="punctuation">:</span> <span class="string">&quot;female&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;lang&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh-TW&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;remote&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;voice_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Google 國語（臺灣）&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>你会发现这里的 <code>voices</code> 和 <code>window.speechSynthesis.getVoices()</code> 里面的谷歌的语音包一模一样，所以就是这个插件负责了 Google Chrome 的在线语音包。</p><p><strong>Google Network Speech</strong> 这个扩展的源代码可以在 Chromium 的项目里面找到，<a href="https://cs.chromium.org/chromium/src/chrome/browser/resources/network_speech_synthesis/tts_extension.js">源代码在此自取</a>。</p><p>本质上，这个在线语音包调用的是 Google Cloud 的语音合成模块。</p><p>接口是 <a href="https://www.google.com/speech-api/v2/synthesize">https://www.google.com/speech-api/v2/synthesize</a></p><p>谷歌中国 <code>www.google.cn</code> 也是有这个接口可以使用的（明显是为了解决国内用户无法调用语音合成模块的问题）。</p><p>Query 参数分别是：</p><ul><li>key: Google API Key，请到 <a href="https://console.developers.google.com/">Google Developers Console</a> 的凭据页面申请，并开通文字到语音的接口权限</li><li>text: 需要合成语音的文本，对应 <code>SpeechSynthesisUtterance.text</code></li><li>lang: 语言，对应 <code>SpeechSynthesisUtterance.lang</code></li><li>name: 语音包名称，对应 <code>SpeechSynthesisUtterance.name</code>（可选）</li><li>speed: 播放速率，对应 <code>SpeechSynthesisUtterance.rate</code>（可选）</li><li>pitch: 音调，对应 <code>SpeechSynthesisUtterance.pitch</code>（可选）</li><li>enc: 默认是 mpeg</li><li>client: 默认是 chromium</li></ul><p>每个用户每个月可以享受400万字符的免费配额（一个中文字也算一个字符），配额超过后每100万字符收4美元。</p><p>关于这个服务的更多介绍，可以查看 <a href="https://cloud.google.com/text-to-speech/?hl=zh_CN">Google Cloud 上的介绍信息</a>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>对于谷歌浏览器的用户，我们可以使用 <code>window.speechSynthesis</code> 享受原生的免费语音合成</li><li>对于其他浏览器的场景，我们可以使用 Google Cloud 的语音合成模块实现语音合成，有一定的免费配额</li><li>针对苹果用户，苹果系统自带了各种离线语音包，可以免费使用</li><li>针对安卓用户，安卓系统设置的语音包决定了 <code>window.speechSynthesis</code> 的能力，如华为内置的科大讯飞离线语音包</li></ol>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;语音合成（Text To Speech，TTS）可以实现网页文章的阅读。通过它，我们可以不需要看文字，就可以听新闻、听小说。&lt;/p&gt;
&lt;p&gt;目前国内的主流的语音合成解决方案无非是以下几种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;科大讯飞&lt;/li&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;云服务（阿里云、腾讯云……）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上述的方案，音质不错，语音包选择多。但是都有一定的免费配额或者语言的限制（如科大讯飞的试用语音包），没有完全免费的方案。&lt;/p&gt;
&lt;p&gt;开源免费的方案的效果比较差，而且需要利用自己的服务器资源，需要考虑额外的成本。&lt;/p&gt;
&lt;p&gt;那有没有什么免费可以使用的方案呢？还是有的！&lt;/p&gt;
&lt;p&gt;这里就要搬出来 Web API 里面的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis&quot;&gt;SpeechSynthesis&lt;/a&gt;。这个接口是浏览器原生支持的语音合成。&lt;/p&gt;
&lt;p&gt;基本上现在主流的现代浏览器都支持这个接口。具体的浏览器支持情况可以查阅上面链接里的文档。&lt;/p&gt;
&lt;h2 id=&quot;如何使用？&quot;&gt;&lt;a href=&quot;#如何使用？&quot; class=&quot;headerlink&quot; title=&quot;如何使用？&quot;&gt;&lt;/a&gt;如何使用？&lt;/h2&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>记GTX870M显卡在VS2017下写CUDA程序的排雷过程</title>
    <link href="https://blog.micblo.com/2019/05/11/%E8%AE%B0GTX870M%E6%98%BE%E5%8D%A1%E5%9C%A8VS2017%E4%B8%8B%E5%86%99CUDA%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%8E%92%E9%9B%B7%E8%BF%87%E7%A8%8B/"/>
    <id>https://blog.micblo.com/2019/05/11/%E8%AE%B0GTX870M%E6%98%BE%E5%8D%A1%E5%9C%A8VS2017%E4%B8%8B%E5%86%99CUDA%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%8E%92%E9%9B%B7%E8%BF%87%E7%A8%8B/</id>
    <published>2019-05-11T16:35:14.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>我的老战神K670D-i7风光依旧，里面装有了Nvidia的 <code>GTX870M</code> 显卡，算力为 <code>3.0</code>。</p><p>这个算力跑 <code>TensorFlow</code> 是不行的了，但是还是可以干点别的事情。</p><p>想装个 <code>CUDA</code> 调通某个开源的项目，结果就是不断触雷。</p><p>下面就是我的老设备排雷之旅。</p><h1 id="条件"><a href="#条件" class="headerlink" title="条件"></a>条件</h1><p>最开始的软件条件为：</p><ul><li>Windows 10</li><li>VS 2017 Community 15.9.7 (平台工具集: <code>Visual Studio 2017 (v141)</code>)</li><li>CUDA 9.0</li></ul><h1 id="第一坑：折腾旧显卡驱动"><a href="#第一坑：折腾旧显卡驱动" class="headerlink" title="第一坑：折腾旧显卡驱动"></a>第一坑：折腾旧显卡驱动</h1><p>按照套路，先到 NVIDIA 控制中心 里面的“系统信息”查看 <code>CUDA</code> 驱动的版本。</p><p>最开始找到是 <code>CUDA 9.0</code>，所以理所当然的就安装了 <code>CUDA Toolkit 9.0</code>。</p><p>结果就遇到第一个大坑 —— CUDA 9.0 不支持 VS2017！</p><p>一开始，按照套路在 VS2017 创建 CUDA 9.0 的工程。，<br>它会自动按照模板生成一个类似 <code>Hello</code> 的基础工程。</p><p>编译的时候就会出现一个错误 ——</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">fatal error C1189: #error: --</span><br><span class="line">unsupported Microsoft Visual Studio version! Only the versions 2012, 2013, 2015 and 2017 are supported!</span><br></pre></td></tr></table></figure><p>大概的意思就是，VS版本不支持。</p><p>经过Google，很容易找到<a href="https://devtalk.nvidia.com/default/topic/1027299/cuda-setup-and-installation/cuda-9-failed-to-support-the-latest-visual-studio-2017-version-15-5/post/5225807/">魔改的办法</a>。</p><p>简单来说就是把 CUDA 原本拿来检查 VS版本的代码改到可以通过。</p><p>把原本这样的代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">if</span> _MSC_VER <span class="string">&lt; 1600 || _MSC_VER &gt;</span> 1911</span></span><br></pre></td></tr></table></figure><p>改成这样代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">if</span> _MSC_VER <span class="string">&lt; 1600 || _MSC_VER &gt;</span> 1920</span></span><br></pre></td></tr></table></figure><p>就不会因为 VS版本太高被判定不可以编译了。</p><p>改好了之后重新编译，<strong>还是会出错！！！</strong></p><p>错误变成了 <code>expression must have a constant value</code>，<br>报错的位置在 VC Toolkit 里面。</p><p>网上说了两种办法：</p><ol><li>安装 VC 2015 的 Toolkit，改平台工具集</li><li>复制 Toolkit 的 <code>.prop</code> 文件到自己项目目录，然后改配置</li></ol><p>我只能说，以上方法都是错误的！都不可以！！</p><p>唯一适配最新版 VS2017 的办法就是，<strong>升级你的N卡驱动和CUDA！</strong></p><h1 id="第二坑：升级N卡驱动"><a href="#第二坑：升级N卡驱动" class="headerlink" title="第二坑：升级N卡驱动"></a>第二坑：升级N卡驱动</h1><p>我以前的N卡驱动，也经是2017年的了，年代久远。</p><p>记得曾经在 NVIDIA EXPERIENCE 里面升级过，失败了。</p><p>结果这次还是失败，不讲道理那种。</p><p>白费我等待下载和安装的时间。</p><p>遇到这种升级卡BUG的情况，需要做到以下几点：</p><p>1.确保C盘腾出足够多的空间，即便你的临时文件夹不在C盘它也是会回C盘的<br>2.<a href="https://www.geforce.cn/drivers">直接去官网下载</a>，而不要用NVIDIA EXPERIENCE</p><p>我就靠这两点，成功把显卡驱动升级到目前最新的 <code>425.31</code>。</p><p><code>425.31</code> 对应的是 <code>CUDA 10.1</code>，<br>因此我们要去 <a href="https://developer.nvidia.com/cuda-downloads">CUDA Toolkit 10.1 的下载界面下载这个版本</a>。</p><blockquote><p><code>CUDA Toolkit</code> 是向下兼容 CUDA 驱动的，但是因为我们的驱动是 <code>CUDA 10.1</code> 的，<br>所以被迫也要用最新版的 <code>CUDA Toolkit</code>。</p></blockquote><p>安装好这些东西就意味着你已经快要成功了。</p><h1 id="第三坑：VS2017配置"><a href="#第三坑：VS2017配置" class="headerlink" title="第三坑：VS2017配置"></a>第三坑：VS2017配置</h1><p>装完了全部必要的软件，就要去 VS2017 亲自试一下项目编译啦。</p><p>注意！此处有个坑！</p><p>你在 <code>CUDA 9.0</code> 创建的项目不一定是可以编译的，<br>此时需要重新创一个 <code>CUDA 10.1</code> 的项目。</p><p><strong>直接去编译是不一定可以的！</strong></p><p>你可以见到下面的错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">addKernel launch failed: no kernel image is available for execution on the device</span><br></pre></td></tr></table></figure><p>这是因为项目配置中的算力配置和显卡不匹配导致的。</p><p>请到“项目”菜单的“设置”里面的 <code>CUDA C/C++</code> 里面的 <code>Code Generation</code> 设置与你的显卡相匹配的算力信息。</p><p>好比说，GTX 870M 就是 <code>compute_30,sm_30</code>。</p><p><a href="https://github.com/tpruvot/ccminer/wiki/Compatibility">在这个地方，可以找到你的算力配置对照表</a>，但是需要注意实际写上去的时候一定要 <code>computer</code> 在前，<code>sm</code> 在后。</p><p>弄完了这些东西，编译就真的可以通过，运行也Ok啦~</p><p>当你看到下面的输出，就说明测试的DEMO跑通了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;1,2,3,4,5&#125; + &#123;10,20,30,40,50&#125; = &#123;11,22,33,44,55&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h1&gt;&lt;p&gt;我的老战神K670D-i7风光依旧，里面装有了Nvidia的 &lt;code&gt;GTX870M&lt;/code&gt; 显卡，算力为 &lt;code&gt;3.0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这个算力跑 &lt;code&gt;TensorFlow&lt;/code&gt; 是不行的了，但是还是可以干点别的事情。&lt;/p&gt;
&lt;p&gt;想装个 &lt;code&gt;CUDA&lt;/code&gt; 调通某个开源的项目，结果就是不断触雷。&lt;/p&gt;
&lt;p&gt;下面就是我的老设备排雷之旅。&lt;/p&gt;
&lt;h1 id=&quot;条件&quot;&gt;&lt;a href=&quot;#条件&quot; class=&quot;headerlink&quot; title=&quot;条件&quot;&gt;&lt;/a&gt;条件&lt;/h1&gt;&lt;p&gt;最开始的软件条件为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10&lt;/li&gt;
&lt;li&gt;VS 2017 Community 15.9.7 (平台工具集: &lt;code&gt;Visual Studio 2017 (v141)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CUDA 9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;第一坑：折腾旧显卡驱动&quot;&gt;&lt;a href=&quot;#第一坑：折腾旧显卡驱动&quot; class=&quot;headerlink&quot; title=&quot;第一坑：折腾旧显卡驱动&quot;&gt;&lt;/a&gt;第一坑：折腾旧显卡驱动&lt;/h1&gt;&lt;p&gt;按照套路，先到 NVIDIA 控制中心 里面的“系统信息”查看 &lt;code&gt;CUDA&lt;/code&gt; 驱动的版本。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>ENVI + IDL 的常见错误清单</title>
    <link href="https://blog.micblo.com/2019/05/11/ENVI-IDL-%E7%9A%84%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%E6%B8%85%E5%8D%95/"/>
    <id>https://blog.micblo.com/2019/05/11/ENVI-IDL-%E7%9A%84%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%E6%B8%85%E5%8D%95/</id>
    <published>2019-05-11T02:14:11.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>最近迫于无奈，帮忙研究了一下 <code>ESTARFM</code> 和 <code>STARFM</code> 算法，<br>需要用到一个处理遥感图像的软件 —— <code>ENVI</code>。</p><p>为了怕自己再遇到类似情况不会解决，因此写这篇小文章记录一下。</p><blockquote><p><code>ENVI</code>（The Environment for Visualizing Images）是一个完整的遥感图像处理平台，应用汇集中的软件处理技术覆盖了图像数据的输入&#x2F;输出、图像定标、图像增强、纠正、正射校正、镶嵌、数据融合以及各种变换、信息提取、图像分类、基于知识的决策树分类、与GIS的整合、DEM及地形信息提取、雷达数据处理、三维立体显示分析。</p></blockquote><h1 id="错误清单"><a href="#错误清单" class="headerlink" title="错误清单"></a>错误清单</h1><h2 id="envi-open-file-等函数不存在-或者-提示-Fid-fid-之类的语法不对"><a href="#envi-open-file-等函数不存在-或者-提示-Fid-fid-之类的语法不对" class="headerlink" title="envi_open_file 等函数不存在 或者 提示 Fid = fid 之类的语法不对"></a><code>envi_open_file</code> 等函数不存在 或者 提示 <code>Fid = fid</code> 之类的语法不对</h2><p>这是因为 <code>ENVI</code> 程序没启动，<br>或者被关掉了。</p><p>启动的时候一定要启动那个 <code>ENVI + IDL</code>，<br>它会自动打开 <code>ENVI</code> 和 <code>IDL</code>。</p><h2 id="WIDGET-BASE-Invalid-widget-identifier-19"><a href="#WIDGET-BASE-Invalid-widget-identifier-19" class="headerlink" title="WIDGET_BASE: Invalid widget identifier: 19"></a><code>WIDGET_BASE: Invalid widget identifier: 19</code></h2><p>也是因为 <code>ENVI</code> 程序没启动，同前一个错误。</p><h2 id="Error-quot-OPENW-Error-opening-file-Unit-100-File-XXXXX-quot-THe-result-may-be-invalid"><a href="#Error-quot-OPENW-Error-opening-file-Unit-100-File-XXXXX-quot-THe-result-may-be-invalid" class="headerlink" title="Error: &quot;OPENW:Error opening file. Unit: 100, File: XXXXX&quot; THe result may be invalid"></a><code>Error: &quot;OPENW:Error opening file. Unit: 100, File: XXXXX&quot; THe result may be invalid</code></h2><p>这个错误说明打开文件不存在，或者保存的文件路径不合法（ENVI不会自己创建文件夹，或者盘符不存在等）</p><h1 id="坑人按钮"><a href="#坑人按钮" class="headerlink" title="坑人按钮"></a>坑人按钮</h1><p>千万要注意！这个“停止调试”千万别按！！</p><p>按了的话，<code>ENVI</code>就会被关掉，<br>就会遇到前面错误清单所看到的，<br>第一种和第二种错误。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h1&gt;&lt;p&gt;最近迫于无奈，帮忙研究了一下 &lt;code&gt;ESTARFM&lt;/code&gt; 和 &lt;code&gt;STARFM&lt;/code&gt; 算法，&lt;br&gt;需要用到一个处理遥感图像的软件 —— &lt;code&gt;ENVI&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为了怕自己再遇到类似情况不会解决，因此写这篇小文章记录一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ENVI&lt;/code&gt;（The Environment for Visualizing Images）是一个完整的遥感图像处理平台，应用汇集中的软件处理技术覆盖了图像数据的输入&amp;#x2F;输出、图像定标、图像增强、纠正、正射校正、镶嵌、数据融合以及各种变换、信息提取、图像分类、基于知识的决策树分类、与GIS的整合、DEM及地形信息提取、雷达数据处理、三维立体显示分析。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;错误清单&quot;&gt;&lt;a href=&quot;#错误清单&quot; class=&quot;headerlink&quot; title=&quot;错误清单&quot;&gt;&lt;/a&gt;错误清单&lt;/h1&gt;&lt;h2 id=&quot;envi-open-file-等函数不存在-或者-提示-Fid-fid-之类的语法不对&quot;&gt;&lt;a href=&quot;#envi-open-file-等函数不存在-或者-提示-Fid-fid-之类的语法不对&quot; class=&quot;headerlink&quot; title=&quot;envi_open_file 等函数不存在 或者 提示 Fid = fid 之类的语法不对&quot;&gt;&lt;/a&gt;&lt;code&gt;envi_open_file&lt;/code&gt; 等函数不存在 或者 提示 &lt;code&gt;Fid = fid&lt;/code&gt; 之类的语法不对&lt;/h2&gt;&lt;p&gt;这是因为 &lt;code&gt;ENVI&lt;/code&gt; 程序没启动，&lt;br&gt;或者被关掉了。&lt;/p&gt;
&lt;p&gt;启动的时候一定要启动那个 &lt;code&gt;ENVI + IDL&lt;/code&gt;，&lt;br&gt;它会自动打开 &lt;code&gt;ENVI&lt;/code&gt; 和 &lt;code&gt;IDL&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&quot;WIDGET-BASE-Invalid-widget-identifier-19&quot;&gt;&lt;a href=&quot;#WIDGET-BASE-Invalid-widget-identifier-19&quot; class=&quot;headerlink&quot; title=&quot;WIDGET_BASE: Invalid widget identifier: 19&quot;&gt;&lt;/a&gt;&lt;code&gt;WIDGET_BASE: Invalid widget identifier: 19&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;也是因为 &lt;code&gt;ENVI&lt;/code&gt; 程序没启动，同前一个错误。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>多文件 GBK 批量转换 UTF-8</title>
    <link href="https://blog.micblo.com/2019/04/30/%E5%A4%9A%E6%96%87%E4%BB%B6-GBK-%E6%89%B9%E9%87%8F%E8%BD%AC%E6%8D%A2-UTF-8/"/>
    <id>https://blog.micblo.com/2019/04/30/%E5%A4%9A%E6%96%87%E4%BB%B6-GBK-%E6%89%B9%E9%87%8F%E8%BD%AC%E6%8D%A2-UTF-8/</id>
    <published>2019-04-30T17:52:13.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>因为历史的原因，论坛还是用了 <code>GBK</code> 的编码，因此全套的程序需要做 <code>UTF-8</code> 的转换。</p><p>为了方便批量转换源代码，写了下面的 <code>Node.js</code> 脚本，可以批量对 Discuz、UCenter 等PHP程序转换 <code>UTF-8</code>。</p><blockquote><p>使用前请 <code>npm install iconv-lite</code> 来安装依赖。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> iconv = <span class="built_in">require</span>(<span class="string">&#x27;iconv-lite&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Path</span> = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">INPUT_ROOT</span> = <span class="string">&#x27;./wwwroot&#x27;</span>; <span class="comment">// 等待转换的文件夹</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">OUTPUT_ROOT</span> = <span class="string">&#x27;./dist/&#x27;</span>; <span class="comment">// 输出到哪个文件夹</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(<span class="variable constant_">OUTPUT_ROOT</span>)) &#123;</span><br><span class="line">    fs.<span class="title function_">mkdirSync</span>(<span class="variable constant_">OUTPUT_ROOT</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以下的格式将会转换编码</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">FORMATS</span> = [<span class="string">&#x27;.htm&#x27;</span>, <span class="string">&#x27;.php&#x27;</span>, <span class="string">&#x27;.js&#x27;</span>, <span class="string">&#x27;.txt&#x27;</span>, <span class="string">&#x27;.html&#x27;</span>, <span class="string">&#x27;.css&#x27;</span>, <span class="string">&#x27;.xml&#x27;</span>, <span class="string">&#x27;.json&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="title function_">tree</span>(<span class="variable constant_">INPUT_ROOT</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">tree</span>(<span class="params">dir</span>) &#123;</span><br><span class="line">    fs.<span class="title function_">mkdirSync</span>(<span class="variable constant_">OUTPUT_ROOT</span> + dir);</span><br><span class="line">    fs.<span class="title function_">readdirSync</span>(dir).<span class="title function_">forEach</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> path = dir + <span class="string">&#x27;/&#x27;</span> + v;</span><br><span class="line">        <span class="keyword">const</span> stat = fs.<span class="title function_">statSync</span>(path);</span><br><span class="line">        <span class="keyword">if</span> (stat.<span class="title function_">isDirectory</span>()) &#123;</span><br><span class="line">            <span class="title function_">tree</span>(path);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> format = <span class="title class_">Path</span>.<span class="title function_">extname</span>(v);</span><br><span class="line">            <span class="keyword">if</span> (<span class="variable constant_">FORMATS</span>.<span class="title function_">indexOf</span>(format) === -<span class="number">1</span>) &#123;</span><br><span class="line">                fs.<span class="title function_">copyFileSync</span>(path, <span class="variable constant_">OUTPUT_ROOT</span> + path);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            fs.<span class="title function_">createReadStream</span>(path)</span><br><span class="line">                .<span class="title function_">pipe</span>(iconv.<span class="title function_">decodeStream</span>(<span class="string">&#x27;gbk&#x27;</span>))</span><br><span class="line">                .<span class="title function_">pipe</span>(iconv.<span class="title function_">encodeStream</span>(<span class="string">&#x27;utf8&#x27;</span>))</span><br><span class="line">                .<span class="title function_">pipe</span>(fs.<span class="title function_">createWriteStream</span>(<span class="variable constant_">OUTPUT_ROOT</span> + path));</span><br><span class="line"></span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(path);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;因为历史的原因，论坛还是用了 &lt;code&gt;GBK&lt;/code&gt; 的编码，因此全套的程序需要做 &lt;code&gt;UTF-8&lt;/code&gt; 的转换。&lt;/p&gt;
&lt;p&gt;为了方便批量转换源代码，写了下面的 &lt;code&gt;Node.js&lt;/code&gt; 脚本，可以批量对 Discuz、UCenter 等PHP程序转换 &lt;code&gt;UTF-8&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用前请 &lt;code&gt;npm install iconv-lite&lt;/code&gt; 来安装依赖。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; iconv = &lt;span class=&quot;built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;iconv-lite&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; fs = &lt;span class=&quot;built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;fs&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Path&lt;/span&gt; = &lt;span class=&quot;built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;path&amp;#x27;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;variable constant_&quot;&gt;INPUT_ROOT&lt;/span&gt; = &lt;span class=&quot;string&quot;&gt;&amp;#x27;./wwwroot&amp;#x27;&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// 等待转换的文件夹&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt; = &lt;span class=&quot;string&quot;&gt;&amp;#x27;./dist/&amp;#x27;&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// 输出到哪个文件夹&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (!fs.&lt;span class=&quot;title function_&quot;&gt;existsSync&lt;/span&gt;(&lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt;)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    fs.&lt;span class=&quot;title function_&quot;&gt;mkdirSync&lt;/span&gt;(&lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 以下的格式将会转换编码&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;variable constant_&quot;&gt;FORMATS&lt;/span&gt; = [&lt;span class=&quot;string&quot;&gt;&amp;#x27;.htm&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.php&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.js&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.txt&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.html&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.css&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.xml&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;.json&amp;#x27;&lt;/span&gt;];&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;title function_&quot;&gt;tree&lt;/span&gt;(&lt;span class=&quot;variable constant_&quot;&gt;INPUT_ROOT&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;tree&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;dir&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    fs.&lt;span class=&quot;title function_&quot;&gt;mkdirSync&lt;/span&gt;(&lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt; + dir);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    fs.&lt;span class=&quot;title function_&quot;&gt;readdirSync&lt;/span&gt;(dir).&lt;span class=&quot;title function_&quot;&gt;forEach&lt;/span&gt;(&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;params&quot;&gt;v&lt;/span&gt; =&amp;gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; path = dir + &lt;span class=&quot;string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt; + v;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; stat = fs.&lt;span class=&quot;title function_&quot;&gt;statSync&lt;/span&gt;(path);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (stat.&lt;span class=&quot;title function_&quot;&gt;isDirectory&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_&quot;&gt;tree&lt;/span&gt;(path);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125; &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; format = &lt;span class=&quot;title class_&quot;&gt;Path&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;extname&lt;/span&gt;(v);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;variable constant_&quot;&gt;FORMATS&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;indexOf&lt;/span&gt;(format) === -&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                fs.&lt;span class=&quot;title function_&quot;&gt;copyFileSync&lt;/span&gt;(path, &lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt; + path);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            fs.&lt;span class=&quot;title function_&quot;&gt;createReadStream&lt;/span&gt;(path)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .&lt;span class=&quot;title function_&quot;&gt;pipe&lt;/span&gt;(iconv.&lt;span class=&quot;title function_&quot;&gt;decodeStream&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;gbk&amp;#x27;&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .&lt;span class=&quot;title function_&quot;&gt;pipe&lt;/span&gt;(iconv.&lt;span class=&quot;title function_&quot;&gt;encodeStream&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;utf8&amp;#x27;&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .&lt;span class=&quot;title function_&quot;&gt;pipe&lt;/span&gt;(fs.&lt;span class=&quot;title function_&quot;&gt;createWriteStream&lt;/span&gt;(&lt;span class=&quot;variable constant_&quot;&gt;OUTPUT_ROOT&lt;/span&gt; + path));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(path);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Matlab 下 libsvm 的编译与坑</title>
    <link href="https://blog.micblo.com/2019/04/22/Matlab-%E4%B8%8B-libsvm-%E7%9A%84%E7%BC%96%E8%AF%91%E4%B8%8E%E5%9D%91/"/>
    <id>https://blog.micblo.com/2019/04/22/Matlab-%E4%B8%8B-libsvm-%E7%9A%84%E7%BC%96%E8%AF%91%E4%B8%8E%E5%9D%91/</id>
    <published>2019-04-22T17:58:48.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.csie.ntu.edu.tw/~cjlin/index.html">LIBSVM</a> 是台湾大学林智仁(Lin Chih-Jen)教授等开发设计的一个简单、易于使用和快速有效的SVM模式识别与回归的软件包。</p><p>不少做机器学习的小伙伴需要把它编译并用在 Matlab 的环境，尤其是在 Windows 系统下，特别多麻烦。</p><p>下面我归纳出来的安装方法，适用于 <code>Windows</code> + <code>GCC</code>。</p><h1 id="安装-TDM-GCC"><a href="#安装-TDM-GCC" class="headerlink" title="安装 TDM-GCC"></a>安装 <code>TDM-GCC</code></h1><p><code>LIBSVM</code> 毕竟是一个 C语言 写的库，因此需要使用编译器编译出接口让 <code>Matlab</code> 调用。</p><p><code>Matlab</code> 的 <code>mex</code> 支持多种编译器。在 <code>Windows</code> 上支持 <code>Visual Studio</code>，但是我已经安装了最新的 <code>Visual Studio</code>，不想再去折腾旧版的VS。因此我选择使用 <code>GCC</code> 的方案。</p><p>在 <code>Windows</code> 下可以使用的 <code>GCC</code> 编译链 有 <code>MinGW64</code>、<code>CygWin</code>、<code>TDM-GCC</code>。<br>很可惜的是，老是折腾不上 <code>MinGW64</code>，最终就选择了 <code>Matlab</code> 推荐的 <code>TDM-GCC</code>。</p><p><strong>如果你是正版的 <code>Matlab</code> （好比说我），直接可以去 <code>Mathworks</code> 官网下载，一键安装</strong>：<br><a href="https://www.mathworks.com/matlabcentral/fileexchange/52848-matlab-support-for-mingw-w64-c-c-compiler">https://www.mathworks.com/matlabcentral/fileexchange/52848-matlab-support-for-mingw-w64-c-c-compiler</a></p><p><strong>如果你是盗版的 <code>Matlab</code>，就需要手动来安装啦。</strong> 直接去 <code>TDM-GCC</code> 的官网下载: <a href="http://tdm-gcc.tdragon.net/download">http://tdm-gcc.tdragon.net/download</a>。<br>最新版也是可以用于 <code>Matlab</code> 的。下载好了，直接按流程安装就OK了。</p><blockquote><p>此处有个需要注意的地方，如果你需要使用 <code>OpenMP</code> 做并行计算，请记得在安装功能的时候勾上！！！</p></blockquote><p>安装完了还没结束，请到系统的环境变量那里，变量名设置 <code>MW_MINGW64_LOC</code>，变量值为你的 <code>TDM-GCC</code> 的安装目录，好比说：<code>C:/TDM-GCC-64</code>。设置完了重开一次 <code>Matlab</code> 就可以使用 <code>mex</code> 编译啦。</p><h1 id="测试-mex"><a href="#测试-mex" class="headerlink" title="测试 mex"></a>测试 <code>mex</code></h1><p>在 <code>Matlab</code> 的命令行窗口（Command Window）处输入：<code>mex -setup</code></p><p>看到输出 <code>MEX configured to use &#39;MinGW64 Compiler (C)</code> 差不多的话，就说明安装成功啦。</p><h1 id="编译-LIBSVM"><a href="#编译-LIBSVM" class="headerlink" title="编译 LIBSVM"></a>编译 <code>LIBSVM</code></h1><p>到 <a href="https://github.com/cjlin1/libsvm/releases">LIBSVM</a> 的 Github 页面，在 Release 处下载最新版的源代码压缩包。</p><p>下载完成后，建议把压缩包解压到 <code>Matlab</code> 的安装目录下的 <code>toolbox</code> 目录。<br>然后使用 <strong>主页 - 设置路径</strong> 里面的 <strong>添加文件夹</strong>，把刚刚我们解压到 <code>toolbox</code> 的 <code>LIBSVM</code> 文件夹下的 <code>matlab</code> 文件夹加入。<br>最后加入的搜索路径大概长这个样子：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">C:\Matlab\toolbox\libsvm\matlab</span><br></pre></td></tr></table></figure><p>完成这个事情之后，就可以切换 <code>Matlab</code> 的工作目录到上面的目录了。只需要一个命令就可以完成编译：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make</span><br></pre></td></tr></table></figure><blockquote><p><code>make</code> 其实运行的是 <code>make.m</code> 那个编译脚本</p></blockquote><p>如果你是 <code>Windows</code> 系统，这样是肯定编译不了的，会出现下面的错误。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">使用 &#x27;MinGW64 Compiler (C)&#x27; 编译。</span><br><span class="line">Error: C:\matlab\toolbox\libsvm\matlab\make.m failed (line 13)</span><br><span class="line">gcc: error: \-fexceptions: No such file or directory</span><br><span class="line"></span><br><span class="line">=&gt; Please check README for detailed instructions.</span><br></pre></td></tr></table></figure><p>怎么解决呢？网上写的解决方法都是不好的，那个改 <code>COMPFLAGS</code> 的 <a href="https://github.com/cjlin1/libsvm/pull/56">Issue</a> 并不是最好的，因为丢了 <code>CFLAGS</code>。</p><p>下面是我总结出来的独家的解决方法：</p><p>其实问题在于 <code>Windows</code> 下的 <code>$</code> 是没有语义的，因此不需要 <code>\$</code> 做转义。因此！只需要把 <code>make.m</code> 下面的 <code>\$CFLAGS</code> 全部替换为 <code>$CFLAGS</code>，也就是删掉 <code>\</code> 就可以在保留 <code>CFLAGS</code> 的基础上编译通过了。</p><p>下面是可以直接编译成功的 <code>make.m</code>：</p><figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">% This make.m is for MATLAB and OCTAVE under Windows, Mac, and Unix</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">make</span><span class="params">()</span></span></span><br><span class="line"><span class="keyword">try</span></span><br><span class="line">    <span class="comment">% This part is for OCTAVE</span></span><br><span class="line">    <span class="keyword">if</span> (exist (<span class="string">&#x27;OCTAVE_VERSION&#x27;</span>, <span class="string">&#x27;builtin&#x27;</span>))</span><br><span class="line">        mex libsvmread.c</span><br><span class="line">        mex libsvmwrite.c</span><br><span class="line">        mex -I.. svmtrain.c ../svm.cpp svm_model_matlab.c</span><br><span class="line">        mex -I.. svmpredict.c ../svm.cpp svm_model_matlab.c</span><br><span class="line">    <span class="comment">% This part is for MATLAB</span></span><br><span class="line">    <span class="comment">% Add -largeArrayDims on 64-bit machines of MATLAB</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        mex CFLAGS=<span class="string">&quot;$CFLAGS -std=c99&quot;</span> -largeArrayDims libsvmread.c</span><br><span class="line">        mex CFLAGS=<span class="string">&quot;$CFLAGS -std=c99&quot;</span> -largeArrayDims libsvmwrite.c</span><br><span class="line">        mex CFLAGS=<span class="string">&quot;$CFLAGS -std=c99&quot;</span> -I.. -largeArrayDims svmtrain.c ../svm.cpp svm_model_matlab.c</span><br><span class="line">        mex CFLAGS=<span class="string">&quot;$CFLAGS -std=c99&quot;</span> -I.. -largeArrayDims svmpredict.c ../svm.cpp svm_model_matlab.c</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">catch</span> err</span><br><span class="line">    fprintf(<span class="string">&#x27;Error: %s failed (line %d)\n&#x27;</span>, err.stack(<span class="number">1</span>).file, err.stack(<span class="number">1</span>).line);</span><br><span class="line">    <span class="built_in">disp</span>(err.message);</span><br><span class="line">    fprintf(<span class="string">&#x27;=&gt; Please check README for detailed instructions.\n&#x27;</span>);</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>改完之后再运行一次 <code>make</code> 就可以啦。</p><h1 id="测试-LIBSVM"><a href="#测试-LIBSVM" class="headerlink" title="测试 LIBSVM"></a>测试 <code>LIBSVM</code></h1><p>将 LIBSVM 文件夹下的测试数据集 <code>heart_scale</code> <code>libsvm\matlab</code> 下。</p><p>切换工作目录到 <code>libsvm\matlab</code> 目录，在命令窗口（Command Window）运行以下命令。</p><figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[heart_scale_label,heart_scale_inst] = libsvmread(<span class="string">&#x27;heart_scale&#x27;</span>);</span><br><span class="line">model = svmtrain(heart_scale_label, heart_scale_inst) ;</span><br><span class="line">[predict_label,accuracy,dec_values] = svmpredict(heart_scale_label, heart_scale_inst, model);</span><br></pre></td></tr></table></figure><p>看到输出 <code>Accuracy = 86.6667%</code> 类似的文字就说明你的 <code>LIBSVM</code> 成功编译并可以使用啦！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.csie.ntu.edu.tw/~cjlin/index.html&quot;&gt;LIBSVM&lt;/a&gt; 是台湾大学林智仁(Lin Chih-Jen)教授等开发设计的一个简单、易于使用和快速有效的SVM模式识别与回归的软件包。&lt;/p&gt;
&lt;p&gt;不少做机器学习的小伙伴需要把它编译并用在 Matlab 的环境，尤其是在 Windows 系统下，特别多麻烦。&lt;/p&gt;
&lt;p&gt;下面我归纳出来的安装方法，适用于 &lt;code&gt;Windows&lt;/code&gt; + &lt;code&gt;GCC&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id=&quot;安装-TDM-GCC&quot;&gt;&lt;a href=&quot;#安装-TDM-GCC&quot; class=&quot;headerlink&quot; title=&quot;安装 TDM-GCC&quot;&gt;&lt;/a&gt;安装 &lt;code&gt;TDM-GCC&lt;/code&gt;&lt;/h1&gt;&lt;p&gt;&lt;code&gt;LIBSVM&lt;/code&gt; 毕竟是一个 C语言 写的库，因此需要使用编译器编译出接口让 &lt;code&gt;Matlab&lt;/code&gt; 调用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Matlab&lt;/code&gt; 的 &lt;code&gt;mex&lt;/code&gt; 支持多种编译器。在 &lt;code&gt;Windows&lt;/code&gt; 上支持 &lt;code&gt;Visual Studio&lt;/code&gt;，但是我已经安装了最新的 &lt;code&gt;Visual Studio&lt;/code&gt;，不想再去折腾旧版的VS。因此我选择使用 &lt;code&gt;GCC&lt;/code&gt; 的方案。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Windows&lt;/code&gt; 下可以使用的 &lt;code&gt;GCC&lt;/code&gt; 编译链 有 &lt;code&gt;MinGW64&lt;/code&gt;、&lt;code&gt;CygWin&lt;/code&gt;、&lt;code&gt;TDM-GCC&lt;/code&gt;。&lt;br&gt;很可惜的是，老是折腾不上 &lt;code&gt;MinGW64&lt;/code&gt;，最终就选择了 &lt;code&gt;Matlab&lt;/code&gt; 推荐的 &lt;code&gt;TDM-GCC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是正版的 &lt;code&gt;Matlab&lt;/code&gt; （好比说我），直接可以去 &lt;code&gt;Mathworks&lt;/code&gt; 官网下载，一键安装&lt;/strong&gt;：&lt;br&gt;&lt;a href=&quot;https://www.mathworks.com/matlabcentral/fileexchange/52848-matlab-support-for-mingw-w64-c-c-compiler&quot;&gt;https://www.mathworks.com/matlabcentral/fileexchange/52848-matlab-support-for-mingw-w64-c-c-compiler&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是盗版的 &lt;code&gt;Matlab&lt;/code&gt;，就需要手动来安装啦。&lt;/strong&gt; 直接去 &lt;code&gt;TDM-GCC&lt;/code&gt; 的官网下载: &lt;a href=&quot;http://tdm-gcc.tdragon.net/download&quot;&gt;http://tdm-gcc.tdragon.net/download&lt;/a&gt;。&lt;br&gt;最新版也是可以用于 &lt;code&gt;Matlab&lt;/code&gt; 的。下载好了，直接按流程安装就OK了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;此处有个需要注意的地方，如果你需要使用 &lt;code&gt;OpenMP&lt;/code&gt; 做并行计算，请记得在安装功能的时候勾上！！！&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>OpenSSL 自建CA 和 自颁证书</title>
    <link href="https://blog.micblo.com/2019/03/31/OpenSSL-%E8%87%AA%E5%BB%BACA-%E5%92%8C-%E8%87%AA%E9%A2%81%E8%AF%81%E4%B9%A6/"/>
    <id>https://blog.micblo.com/2019/03/31/OpenSSL-%E8%87%AA%E5%BB%BACA-%E5%92%8C-%E8%87%AA%E9%A2%81%E8%AF%81%E4%B9%A6/</id>
    <published>2019-03-31T14:02:08.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>做仿真的TLS网络的时候需要自己动手建立CA和颁发证书，下面是一些笔记：</p><h1 id="生成-CA-私钥"><a href="#生成-CA-私钥" class="headerlink" title="生成 CA 私钥"></a>生成 CA 私钥</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl genrsa -out ca.key 4096</span><br></pre></td></tr></table></figure><blockquote><p>此处用到的是 <code>4096</code>位的RSA私钥，可以选择 <code>2048</code>位，不建议使用 <code>1024</code>位</p></blockquote><h1 id="生成-CA-证书请求"><a href="#生成-CA-证书请求" class="headerlink" title="生成 CA 证书请求"></a>生成 CA 证书请求</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl req -new -days 365 -key cb.key -out cb.csr</span><br></pre></td></tr></table></figure><h1 id="CA-自签名"><a href="#CA-自签名" class="headerlink" title="CA 自签名"></a>CA 自签名</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl ca -selfsign -<span class="keyword">in</span> ca.csr -out ca.crt -keyfile ca.key -cert ca.crt</span><br></pre></td></tr></table></figure><h1 id="生成服务器私钥"><a href="#生成服务器私钥" class="headerlink" title="生成服务器私钥"></a>生成服务器私钥</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl genrsa -out server.key 2048</span><br></pre></td></tr></table></figure><h1 id="生成服务器证书请求"><a href="#生成服务器证书请求" class="headerlink" title="生成服务器证书请求"></a>生成服务器证书请求</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl req -new -days 365 -key server.key -out server.csr</span><br></pre></td></tr></table></figure><h1 id="使用-CA-签名服务器证书"><a href="#使用-CA-签名服务器证书" class="headerlink" title="使用 CA 签名服务器证书"></a>使用 CA 签名服务器证书</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl ca -<span class="keyword">in</span> server.csr -out server.crt -keyfile ca.key -cert ca.crt</span><br></pre></td></tr></table></figure><h1 id="可能遇到的问题"><a href="#可能遇到的问题" class="headerlink" title="可能遇到的问题"></a>可能遇到的问题</h1><ol><li><code>I am unable to access the ./demoCA/newcerts directory</code></li></ol><p>解决方法：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p ./demoCA/&#123;private,newcerts&#125; &amp;&amp; \</span><br><span class="line"><span class="built_in">touch</span> ./demoCA/index.txt &amp;&amp; \</span><br><span class="line"><span class="built_in">touch</span> ./demoCA/serial &amp;&amp; \</span><br><span class="line"><span class="built_in">echo</span> 00 &gt; ./demoCA/serial</span><br></pre></td></tr></table></figure><ol start="2"><li><code>The organizationName field needed to be the same in the CA certificate</code></li></ol><p>解决方法：</p><p>到 <code>/usr/lib/ssl/openssl.cnf</code>，搜索 <code>policy_match</code> ，将内容改为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[ policy_match ]</span><br><span class="line">countryName             = supplied</span><br><span class="line">stateOrProvinceName     = supplied</span><br><span class="line">organizationName        = supplied</span><br><span class="line">organizationalUnitName  = optional</span><br><span class="line">commonName              = supplied</span><br><span class="line">emailAddress            = optional</span><br></pre></td></tr></table></figure><blockquote><p><code>countryName</code>、<code>stateOrProvinceName</code>、<code>organizationName</code> 都设置为 <code>supplied</code></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;做仿真的TLS网络的时候需要自己动手建立CA和颁发证书，下面是一些笔记：&lt;/p&gt;
&lt;h1 id=&quot;生成-CA-私钥&quot;&gt;&lt;a href=&quot;#生成-CA-私钥&quot; class=&quot;headerlink&quot; title=&quot;生成 CA 私钥&quot;&gt;&lt;/a&gt;生成 CA 私钥&lt;/h1&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;openssl genrsa -out ca.key 4096&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;此处用到的是 &lt;code&gt;4096&lt;/code&gt;位的RSA私钥，可以选择 &lt;code&gt;2048&lt;/code&gt;位，不建议使用 &lt;code&gt;1024&lt;/code&gt;位&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;生成-CA-证书请求&quot;&gt;&lt;a href=&quot;#生成-CA-证书请求&quot; class=&quot;headerlink&quot; title=&quot;生成 CA 证书请求&quot;&gt;&lt;/a&gt;生成 CA 证书请求&lt;/h1&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;openssl req -new -days 365 -key cb.key -out cb.csr&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h1 id=&quot;CA-自签名&quot;&gt;&lt;a href=&quot;#CA-自签名&quot; class=&quot;headerlink&quot; title=&quot;CA 自签名&quot;&gt;&lt;/a&gt;CA 自签名&lt;/h1&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;openssl ca -selfsign -&lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; ca.csr -out ca.crt -keyfile ca.key -cert ca.crt&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h1 id=&quot;生成服务器私钥&quot;&gt;&lt;a href=&quot;#生成服务器私钥&quot; class=&quot;headerlink&quot; title=&quot;生成服务器私钥&quot;&gt;&lt;/a&gt;生成服务器私钥&lt;/h1&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;openssl genrsa -out server.key 2048&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>.so not found 的坑与解决方案</title>
    <link href="https://blog.micblo.com/2019/03/22/so-not-found-%E7%9A%84%E5%9D%91%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.micblo.com/2019/03/22/so-not-found-%E7%9A%84%E5%9D%91%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2019-03-22T16:14:42.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间用 <code>Go</code> 写一个仿真测试，需要用到 <code>cgo</code> 来使用一些 <code>C/C++</code> 写的库。</p><p>在我的 MacBook 里面，可以很顺利跑测试。但是，改到 <code>Ubuntu 16.04</code> 的服务器上就炸了。</p><p>老是提示 <code>XXXX.so not found</code>。</p><p>这就奇怪了！明明用 <code>go</code> 编译和链接库都成功了，但是实际上运行还是不行。</p><p>试过了加 <code>CGO_ENABLED=&quot;1&quot;</code> 环境变量也不可以，程序放在 <code>.so</code> 的相同目录下运行也不行，<code>ldd</code> 也是提示 <code>not found</code>。</p><p>最后发现问题之所在，<strong>一定要运行一次</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ldconfig</span><br></pre></td></tr></table></figure><p>更新一下动态链接库名字列表，程序在运行的时候才可以顺利找到SO库。</p><blockquote><p>注意！编译链接和运行时候的链接是两码事儿，运行的时候还是得看 <code>/etc/ld.so.cache</code> 的！</p></blockquote><p>哎，真是坑惨了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前段时间用 &lt;code&gt;Go&lt;/code&gt; 写一个仿真测试，需要用到 &lt;code&gt;cgo&lt;/code&gt; 来使用一些 &lt;code&gt;C/C++&lt;/code&gt; 写的库。&lt;/p&gt;
&lt;p&gt;在我的 MacBook 里面，可以很顺利跑测试。但是，改到 &lt;code&gt;Ubuntu 16.04&lt;/code&gt; 的服务器上就炸了。&lt;/p&gt;
&lt;p&gt;老是提示 &lt;code&gt;XXXX.so not found&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这就奇怪了！明明用 &lt;code&gt;go&lt;/code&gt; 编译和链接库都成功了，但是实际上运行还是不行。&lt;/p&gt;
&lt;p&gt;试过了加 &lt;code&gt;CGO_ENABLED=&amp;quot;1&amp;quot;&lt;/code&gt; 环境变量也不可以，程序放在 &lt;code&gt;.so&lt;/code&gt; 的相同目录下运行也不行，&lt;code&gt;ldd&lt;/code&gt; 也是提示 &lt;code&gt;not found&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;最后发现问题之所在，&lt;strong&gt;一定要运行一次&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;ldconfig&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;更新一下动态链接库名字列表，程序在运行的时候才可以顺利找到SO库。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意！编译链接和运行时候的链接是两码事儿，运行的时候还是得看 &lt;code&gt;/etc/ld.so.cache&lt;/code&gt; 的！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;哎，真是坑惨了。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Invoke-customs are only supported starting with Android O (--min-api 26) 解决方案</title>
    <link href="https://blog.micblo.com/2019/03/19/Invoke-customs-are-only-supported-starting-with-Android-O-min-api-26-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.micblo.com/2019/03/19/Invoke-customs-are-only-supported-starting-with-Android-O-min-api-26-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2019-03-19T16:32:09.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近写一个安卓的小Demo的时候，要用到比较新的 <code>OKhttp3</code>。</p><p>装上去之后，按照标准例子，怎么搞都编译不了，提示下面的错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Invoke-customs are only supported starting with Android O (--min-api 26)</span><br></pre></td></tr></table></figure><p>字面意思就是只有 Android O 以及以上的API才支持。</p><p>那不可能啊！<code>OKhttp3</code> 明明说了，<code>&gt;= Android 5</code> 都支持，怎么可能要我设置更高的API版本。</p><p>最后查了一下，是JDK的版本问题，得设置 <code>1.8</code> 才可以正常使用。</p><p>用 Gradle 的小伙伴只需要在自己用到了 <code>OKhttp3</code> 的项目中的 <code>build.gradle</code> 加上下面的 <code>compileOptions</code> 配置就好了。</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">    compileOptions &#123;</span><br><span class="line">        <span class="keyword">sourceCompatibility</span> JavaVersion.VERSION_1_8</span><br><span class="line">        <span class="keyword">targetCompatibility</span> JavaVersion.VERSION_1_8</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近写一个安卓的小Demo的时候，要用到比较新的 &lt;code&gt;OKhttp3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;装上去之后，按照标准例子，怎么搞都编译不了，提示下面的错误：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Invoke-customs are only supported starting with Android O (--min-api 26)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;字面意思就是只有 Android O 以及以上的API才支持。&lt;/p&gt;
&lt;p&gt;那不可能啊！&lt;code&gt;OKhttp3&lt;/code&gt; 明明说了，&lt;code&gt;&amp;gt;= Android 5&lt;/code&gt; 都支持，怎么可能要我设置更高的API版本。&lt;/p&gt;
&lt;p&gt;最后查了一下，是JDK的版本问题，得设置 &lt;code&gt;1.8&lt;/code&gt; 才可以正常使用。&lt;/p&gt;
&lt;p&gt;用 Gradle 的小伙伴只需要在自己用到了 &lt;code&gt;OKhttp3&lt;/code&gt; 的项目中的 &lt;code&gt;build.gradle&lt;/code&gt; 加上下面的 &lt;code&gt;compileOptions&lt;/code&gt; 配置就好了。&lt;/p&gt;
&lt;figure class=&quot;highlight gradle&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;android &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    compileOptions &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;sourceCompatibility&lt;/span&gt; JavaVersion.VERSION_1_8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;targetCompatibility&lt;/span&gt; JavaVersion.VERSION_1_8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Xcode卡在Xcode And IOS SDK License Agreement解决方案</title>
    <link href="https://blog.micblo.com/2019/03/16/Xcode%E5%8D%A1%E5%9C%A8Xcode-And-IOS-SDK-License-Agreement%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://blog.micblo.com/2019/03/16/Xcode%E5%8D%A1%E5%9C%A8Xcode-And-IOS-SDK-License-Agreement%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2019-03-16T16:21:45.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>刚刚装上的 Xcode11，第一次启动的时候弹出“Xcode And IOS SDK License Agreement”。<br>点击“Accept”的时候，系统会让你输入管理员的用户和密码。<br>正常来说，应该就可以进入主界面了。</p><p>但是！吊诡的是，直接卡在这个界面了。<br>多次点击“Accept”，都没有反应。<br>只剩下“Disagree”，可以选，按了就会自动退出Xcode。</p><p>而且，<a href="http://mac.pcbeta.com/thread-164282-1-1.html">在这里</a>也有老哥遇到一样的问题，重装也是完全无解。</p><p>解决方法很粗暴，在终端直接接受协议就可以了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo xcodebuild -license accept</span><br></pre></td></tr></table></figure><p>真的是太坑了！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;刚刚装上的 Xcode11，第一次启动的时候弹出“Xcode And IOS SDK License Agreement”。&lt;br&gt;点击“Accept”的时候，系统会让你输入管理员的用户和密码。&lt;br&gt;正常来说，应该就可以进入主界面了。&lt;/p&gt;
&lt;p&gt;但是！吊诡的是，直接卡在这个界面了。&lt;br&gt;多次点击“Accept”，都没有反应。&lt;br&gt;只剩下“Disagree”，可以选，按了就会自动退出Xcode。&lt;/p&gt;
&lt;p&gt;而且，&lt;a href=&quot;http://mac.pcbeta.com/thread-164282-1-1.html&quot;&gt;在这里&lt;/a&gt;也有老哥遇到一样的问题，重装也是完全无解。&lt;/p&gt;
&lt;p&gt;解决方法很粗暴，在终端直接接受协议就可以了。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;sudo xcodebuild -license accept&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;真的是太坑了！&lt;/p&gt;
</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>以太坊专题 - 钱包地址</title>
    <link href="https://blog.micblo.com/2019/02/26/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E4%B8%93%E9%A2%98-%E9%92%B1%E5%8C%85%E5%9C%B0%E5%9D%80/"/>
    <id>https://blog.micblo.com/2019/02/26/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E4%B8%93%E9%A2%98-%E9%92%B1%E5%8C%85%E5%9C%B0%E5%9D%80/</id>
    <published>2019-02-26T01:40:12.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<h1 id="生成钱包"><a href="#生成钱包" class="headerlink" title="生成钱包"></a>生成钱包</h1><p>以太坊的钱包是基于椭圆曲线数字签名算法ECDSA保护的。</p><p>因此每一个钱包对应的是一个ECDSA的密钥对。</p><p>这个密钥对在 <code>go-ethereum</code> 项目的 <code>crypto/crypto.go</code> 的 <code>GenerateKey()</code> 函数生成，<br>生成后得到一个ECDSA的私钥 (<code>ecdsa.PrivateKey</code>)。</p><p>以太坊所采用的椭圆曲线是 <code>secp256k1</code>。由于其特殊构造的特殊性，其优化后的实现比其他曲线性能上可以提高30%，有明显以下两个优点：</p><ol><li>占用很少的带宽和存储资源，密钥的长度很短；</li><li>让所有的用户都可以使用同样的操作完成域运算。</li></ol><p>生成钱包私钥代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GenerateKey</span><span class="params">()</span></span> (*ecdsa.PrivateKey, <span class="type">error</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> ecdsa.GenerateKey(S256(), rand.Reader)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>得到私钥后，可以使用 <code>ecdsa.PrivateKey</code> 的 <code>Public()</code> 方法取得公钥 <code>ecdsa.PublicKey</code>。</p><h1 id="生成一般的钱包地址"><a href="#生成一般的钱包地址" class="headerlink" title="生成一般的钱包地址"></a>生成一般的钱包地址</h1><p>以太坊钱包地址长度为20字节，来源于前面我们生成的钱包公钥 <code>ecdsa.PublicKey</code>。</p><p>简单来说就是计算一下钱包公钥的 <code>Keccak256</code> 哈希值，再取出末尾20字节作为钱包地址（本身 <code>Keccak256</code> 输出32字节）。</p><p>生成钱包地址源代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FromECDSAPub</span><span class="params">(pub *ecdsa.PublicKey)</span></span> []<span class="type">byte</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> pub == <span class="literal">nil</span> || pub.X == <span class="literal">nil</span> || pub.Y == <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> elliptic.Marshal(S256(), pub.X, pub.Y)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PubkeyToAddress</span><span class="params">(p ecdsa.PublicKey)</span></span> common.Address &#123;</span><br><span class="line">  pubBytes := FromECDSAPub(&amp;p)</span><br><span class="line">  <span class="keyword">return</span> common.BytesToAddress(Keccak256(pubBytes[<span class="number">1</span>:])[<span class="number">12</span>:])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="生成智能合约的地址"><a href="#生成智能合约的地址" class="headerlink" title="生成智能合约的地址"></a>生成智能合约的地址</h1><p>智能合约的地址其实与拥有者用户钱包有关。但由于我们不可能直接拿钱包的公钥作为智能合约地址（这样就会与钱包地址冲突），所以就需要在原钱包地址的基础上增加一个唯一的独立数 <code>Nonce</code>。<br>这样就可以确保某个钱包的拥有者可以生成出不同的智能合约地址啦。</p><p>注意，智能合约的 <code>Nonce</code> 与生成该智能合约的交易 <code>Tx</code> 的交易 <code>nonce</code> 有关。因为每一笔交易的nonce都是独立的，所以可以智能合约的nonce也是可以视为独立的。</p><p>生成智能合约的地址源码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CreateAddress</span><span class="params">(b common.Address, nonce <span class="type">uint64</span>)</span></span> common.Address &#123;</span><br><span class="line">  data, _ := rlp.EncodeToBytes([]<span class="keyword">interface</span>&#123;&#125;&#123;b, nonce&#125;)</span><br><span class="line">  <span class="keyword">return</span> common.BytesToAddress(Keccak256(data)[<span class="number">12</span>:])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成智能合约地址使用参考：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The contract address can be derived from the transaction itself</span></span><br><span class="line"><span class="keyword">if</span> transactions[j].To() == <span class="literal">nil</span> &#123;</span><br><span class="line">  <span class="comment">// Deriving the signer is expensive, only do if it&#x27;s actually needed</span></span><br><span class="line">  from, _ := types.Sender(signer, transactions[j]) <span class="comment">// 获取一下发送者的公钥</span></span><br><span class="line">  receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce()) <span class="comment">// 直接将Tx的Nonce作为智能合约的Nonce</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上是以太坊专题第一部分钱包地址的内容。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;生成钱包&quot;&gt;&lt;a href=&quot;#生成钱包&quot; class=&quot;headerlink&quot; title=&quot;生成钱包&quot;&gt;&lt;/a&gt;生成钱包&lt;/h1&gt;&lt;p&gt;以太坊的钱包是基于椭圆曲线数字签名算法ECDSA保护的。&lt;/p&gt;
&lt;p&gt;因此每一个钱包对应的是一个ECDSA的密钥对。&lt;/p&gt;
&lt;p&gt;这个密钥对在 &lt;code&gt;go-ethereum&lt;/code&gt; 项目的 &lt;code&gt;crypto/crypto.go&lt;/code&gt; 的 &lt;code&gt;GenerateKey()&lt;/code&gt; 函数生成，&lt;br&gt;生成后得到一个ECDSA的私钥 (&lt;code&gt;ecdsa.PrivateKey&lt;/code&gt;)。&lt;/p&gt;
&lt;p&gt;以太坊所采用的椭圆曲线是 &lt;code&gt;secp256k1&lt;/code&gt;。由于其特殊构造的特殊性，其优化后的实现比其他曲线性能上可以提高30%，有明显以下两个优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;占用很少的带宽和存储资源，密钥的长度很短；&lt;/li&gt;
&lt;li&gt;让所有的用户都可以使用同样的操作完成域运算。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;生成钱包私钥代码：&lt;/p&gt;
&lt;figure class=&quot;highlight go&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;GenerateKey&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;&lt;/span&gt; (*ecdsa.PrivateKey, &lt;span class=&quot;type&quot;&gt;error&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; ecdsa.GenerateKey(S256(), rand.Reader)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;得到私钥后，可以使用 &lt;code&gt;ecdsa.PrivateKey&lt;/code&gt; 的 &lt;code&gt;Public()&lt;/code&gt; 方法取得公钥 &lt;code&gt;ecdsa.PublicKey&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id=&quot;生成一般的钱包地址&quot;&gt;&lt;a href=&quot;#生成一般的钱包地址&quot; class=&quot;headerlink&quot; title=&quot;生成一般的钱包地址&quot;&gt;&lt;/a&gt;生成一般的钱包地址&lt;/h1&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Android Native库混用C和C++的 undefined reference to XXX 解决方法</title>
    <link href="https://blog.micblo.com/2019/02/18/Android-Native%E5%BA%93%E6%B7%B7%E7%94%A8C%E5%92%8CC-%E7%9A%84-undefined-reference-to-XXX-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/"/>
    <id>https://blog.micblo.com/2019/02/18/Android-Native%E5%BA%93%E6%B7%B7%E7%94%A8C%E5%92%8CC-%E7%9A%84-undefined-reference-to-XXX-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</id>
    <published>2019-02-18T14:03:58.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>因为业务的需要，需要在C++写的Native库中调用由C语言写的SO库。</p><p>之前就已经知道，如果想在Native层上混用C语言和C++所写的代码，就必须要在C++的函数声明上增加 <code>extern &quot;C&quot;</code> 的标识，或者使用 <code>extern &quot;C&quot; &#123;&#125;</code> 包含多个C++的函数声明。</p><p>如果不这么做，会导致编译时出现链接器无法链接函数的问题，所提示的错误类似于<br><code>error: undefined reference to &#39;init(int)&#39;</code>。</p><p>除此之外，还有几个坑点需要注意！</p><p>使用 <code>extern &quot;C&quot; &#123;&#125;</code> 时，务必要把你需要链接到的C语言的库的头文件也包含在里面，如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span> &#123;</span><br><span class="line">    <span class="comment">// C语言所写的库</span></span><br><span class="line">    <span class="meta">#<span class="keyword">include</span> <span class="string">&lt;openssl/xxx.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">(<span class="type">int</span> id)</span></span>; <span class="comment">// C++函数声明</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>并且，需要把类似于 <code>map</code> 这样的使用了模板的C++库放在 <code>extern &quot;C&quot;</code> 的外部。下面是错误示范：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span> &#123;</span><br><span class="line">    <span class="comment">// C++使用了模板的库</span></span><br><span class="line">    <span class="meta">#<span class="keyword">include</span> <span class="string">&lt;map&gt;</span></span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// C语言所写的库</span></span><br><span class="line">    <span class="meta">#<span class="keyword">include</span> <span class="string">&lt;openssl/xxx.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">(<span class="type">int</span> id)</span></span>; <span class="comment">// C++函数声明</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这样会引发类似下面的错误： <code>error: templates must have C++ linkage</code></p><p>下面是正确示范：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// C++使用了模板的库</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;map&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// C语言所写的库</span></span><br><span class="line">    <span class="meta">#<span class="keyword">include</span> <span class="string">&lt;openssl/xxx.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">(<span class="type">int</span> id)</span></span>; <span class="comment">// C++函数声明</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>希望大家注意这些问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;因为业务的需要，需要在C++写的Native库中调用由C语言写的SO库。&lt;/p&gt;
&lt;p&gt;之前就已经知道，如果想在Native层上混用C语言和C++所写的代码，就必须要在C++的函数声明上增加 &lt;code&gt;extern &amp;quot;C&amp;quot;&lt;/code&gt; 的标识，或者使用 &lt;code&gt;extern &amp;quot;C&amp;quot; &amp;#123;&amp;#125;&lt;/code&gt; 包含多个C++的函数声明。&lt;/p&gt;
&lt;p&gt;如果不这么做，会导致编译时出现链接器无法链接函数的问题，所提示的错误类似于&lt;br&gt;&lt;code&gt;error: undefined reference to &amp;#39;init(int)&amp;#39;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;除此之外，还有几个坑点需要注意！&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;extern &amp;quot;C&amp;quot; &amp;#123;&amp;#125;&lt;/code&gt; 时，务必要把你需要链接到的C语言的库的头文件也包含在里面，如：&lt;/p&gt;
&lt;figure class=&quot;highlight c++&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// C语言所写的库&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;openssl/xxx.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; id)&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// C++函数声明&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;并且，需要把类似于 &lt;code&gt;map&lt;/code&gt; 这样的使用了模板的C++库放在 &lt;code&gt;extern &amp;quot;C&amp;quot;&lt;/code&gt; 的外部。下面是错误示范：&lt;/p&gt;
&lt;figure class=&quot;highlight c++&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// C++使用了模板的库&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;map&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// C语言所写的库&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;openssl/xxx.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; id)&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// C++函数声明&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这样会引发类似下面的错误： &lt;code&gt;error: templates must have C++ linkage&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面是正确示范：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>ELF学习日记-Android SO库文件头分析</title>
    <link href="https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/"/>
    <id>https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/</id>
    <published>2018-02-10T14:22:48.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>因为项目的需要，我对Android系统加载<code>.so</code>文件有一些些研究，<br>把最近看过的一些大牛的分析和现状结合一下，<br>写篇东西做一下笔记。</p><h1 id="so-文件是什么"><a href="#so-文件是什么" class="headerlink" title=".so 文件是什么"></a><code>.so</code> 文件是什么</h1><p><code>.so</code> 文件是 <code>Shared Object</code> 文件的后缀，<br>直白的说就是Linux系统中的“动态链接库” ，<br>就和Windows系统下的 <code>.dll</code> 文件（Dynamic Link Library）类似。</p><p><code>.so</code> 文件实则是 <code>ELF</code> 格式的文件，和Linux的可执行文件的格式一致。</p><h1 id="ELF-文件结构"><a href="#ELF-文件结构" class="headerlink" title="ELF 文件结构"></a><code>ELF</code> 文件结构</h1><p><code>ELF</code> 格式的文件中的“数据”实际上是以“段”（节，英文：Section）的形式存储的。<br>其顺序大致符合下面的排序：</p><ul><li>ELF Header：ELF头部</li><li><code>.dynsym</code>：保存动态链接相关符号，记录其偏移值</li><li><code>.dynstr</code>：<code>.dynsym</code> 的辅助段</li><li><code>.hash</code>：快速查找符号的哈希表，类似 <code>.dynstr</code></li><li><code>.rel.got</code>：数据引用修正，修正到 <code>.got</code></li><li><code>.rel.plt</code>：函数引用修正，修正到 <code>.got.plt</code></li><li><code>.text</code>：代码段</li><li>其他自定义的代码段</li><li><code>.rodata</code>：字符串常量段</li><li><code>.fini_array</code>：终止函数段</li><li><code>.init_array</code>：初始化函数段</li><li><code>.dynamic</code>：动态链接库特有，存储动态链接用到的表信息</li><li><code>.got</code>：函数的绝对地址</li><li><code>.data</code>：存放已经初始化的全局变量，静态内存分配相关</li><li><code>.bss</code>：存放未初始化的全局变量，静态内存分配相关</li></ul><h1 id="ELF-头部：ELF-Header"><a href="#ELF-头部：ELF-Header" class="headerlink" title="ELF 头部：ELF Header"></a>ELF 头部：<code>ELF Header</code></h1><p>其中，一切的起点都在ELF头部，其偏移量（offset）为 0。<br>ELF头部的结构体为 <code>elf32_hdr</code> 或 <code>elf64_hdr</code>，<br>在Android系统源代码的 <code>/bionic/libc/kernel/uapi/linux/elf.h</code> 可以找到。</p><p>以32位程序的ELF头部为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">06#define EI_NIDENT 16</span><br><span class="line">typedef struct elf32_hdr &#123;</span><br><span class="line">  unsigned char e_ident[EI_NIDENT];</span><br><span class="line">  Elf32_Half e_type;</span><br><span class="line">  Elf32_Half e_machine;</span><br><span class="line">  Elf32_Word e_version;</span><br><span class="line">  Elf32_Addr e_entry;</span><br><span class="line">  Elf32_Off e_phoff;</span><br><span class="line">  Elf32_Off e_shoff;</span><br><span class="line">  Elf32_Word e_flags;</span><br><span class="line">  Elf32_Half e_ehsize;</span><br><span class="line">  Elf32_Half e_phentsize;</span><br><span class="line">  Elf32_Half e_phnum;</span><br><span class="line">  Elf32_Half e_shentsize;</span><br><span class="line">  Elf32_Half e_shnum;</span><br><span class="line">  Elf32_Half e_shstrndx;</span><br><span class="line">&#125; Elf32_Ehdr;</span><br></pre></td></tr></table></figure><h2 id="e-ident"><a href="#e-ident" class="headerlink" title="e_ident"></a><code>e_ident</code></h2><p>ELF格式文件的识别区域，固定为 16Bytes 的字符串。<br>下面是这串字符串各个字节的含义。</p><h4 id="EI-MAG"><a href="#EI-MAG" class="headerlink" title="EI_MAG"></a><code>EI_MAG</code></h4><ul><li>Offset: 0 - 3 (<code>EI_MAG0</code>)</li><li>Length: 4 (<code>SELFMAG</code>)</li><li>Type: <code>String</code></li></ul><p>ELF Identification 的前四个字节为魔数（Magic Number），<br>内容为 <code>&#123;0x7F, &#39;E&#39;, &#39;L&#39;, &#39;F&#39;&#125;</code>。</p><p>如果魔数不一致，在Android系统会报错“has bad ELF magic”，<br>终止 <code>load_library</code>并APP闪退。</p><blockquote><p>相关代码可以查询源码 <a href="http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker_phdr.cpp">&#x2F;xref&#x2F;bionic&#x2F;linker&#x2F;linker_phdr.cpp</a></p></blockquote><h4 id="EI-CLASS"><a href="#EI-CLASS" class="headerlink" title="EI_CLASS"></a><code>EI_CLASS</code></h4><ul><li>Offset: 4 (<code>EI_CLASS</code>)</li><li>Length: 1</li><li>Type: Number</li></ul><p>判断 <code>ELF</code> 文件是 32位的 还是 64位的。</p><p>常量定义：</p><ul><li><code>ELFCLASSNONE</code> &#x3D; 0：无定义【非法】</li><li><code>ELFCLASS32</code> &#x3D; 1：32位</li><li><code>ELFCLASS64</code> &#x3D; 2：64位</li><li><code>ELFCLASSNUM</code> &#x3D; 3：未知【非法】</li></ul><p>在Android <code>&lt;5</code> 的系统上，由于当年的系统不支持64位的指令集，<br>因此只要不是32位，就输出错误 “not 32-bit”，并APP闪退。</p><p>在Android <code>&gt;=5</code> 系统上，已经出现了64位的指令集<br>，如 <code>arm64_v8a</code>、<code>x86_64</code>。<br>若32位的指令集遇到64位的SO库，<br>会输出错误 “is 64-bit instead of 32-bit”，<br>并APP闪退；<br>若32位的指令集遇到64位的SO库，<br>会输出错误 “is 32-bit instead of 64-bit”，<br>并APP闪退；<br>若出现非法的 <code>ELFCLASS</code>，<br>会输出错误 “has unknown ELF class: ?”，<br>并APP闪退。</p><h4 id="EI-DATA"><a href="#EI-DATA" class="headerlink" title="EI_DATA"></a><code>EI_DATA</code></h4><ul><li>Offset: 5 (<code>EI_DATA</code>)</li><li>Length: 1</li><li>Type: Number (unsigned char)</li></ul><p>这个是判断 <code>ELF</code>文件是 <code>LSB</code>(Little-endian，低字节序)<br>还是 <code>MSB</code>(Big-endian，高字节序)。</p><p>常量定义：</p><ul><li><code>ELFDATANONE</code> &#x3D; 0：无定义【非法】</li><li><code>ELFDATA2LSB</code> &#x3D; 1：<code>LSB</code></li><li><code>ELFDATA2MSB</code> &#x3D; 2：<code>MSB</code>【非法】</li></ul><p>安卓系统只允许 <code>LSB</code>，因此只要不是 <code>1</code>，<br>就输出错误 “not little-endian”，<br>并APP闪退。</p><h4 id="EI-VERSION"><a href="#EI-VERSION" class="headerlink" title="EI_VERSION"></a><code>EI_VERSION</code></h4><ul><li>Offset: 6</li><li>Length: 1</li><li>Type: Number (unsigned char)</li></ul><p>顾名思义，是 <code>ELF</code> 文件格式的版本号，默认是 <code>EV_CURRENT</code>（&#x3D; 1）。</p><p>Android系统不从这里检测 <code>Version</code>，而在 <code>e_version</code> 上检测，因此修改无影响。</p><h4 id="EI-OSABI"><a href="#EI-OSABI" class="headerlink" title="EI_OSABI"></a><code>EI_OSABI</code></h4><ul><li>Offset: 7</li><li>Length: 1</li><li>Type: Number (unsigned char)</li></ul><p>指出来该 <code>ELF</code> 文件可以在什么操作系统运行，参考：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">#define ELFOSABI_NONE       0   /* UNIX System V ABI */</span><br><span class="line">#define ELFOSABI_SYSV       0   /* Alias.  */</span><br><span class="line">#define ELFOSABI_HPUX       1   /* HP-UX */</span><br><span class="line">#define ELFOSABI_NETBSD     2   /* NetBSD.  */</span><br><span class="line">#define ELFOSABI_LINUX      3   /* Linux.  */</span><br><span class="line">#define ELFOSABI_SOLARIS    6   /* Sun Solaris.  */</span><br><span class="line">#define ELFOSABI_AIX        7   /* IBM AIX.  */</span><br><span class="line">#define ELFOSABI_IRIX       8   /* SGI Irix.  */</span><br><span class="line">#define ELFOSABI_FREEBSD    9   /* FreeBSD.  */</span><br><span class="line">#define ELFOSABI_TRU64      10  /* Compaq TRU64 UNIX.  */</span><br><span class="line">#define ELFOSABI_MODESTO    11  /* Novell Modesto.  */</span><br><span class="line">#define ELFOSABI_OPENBSD    12  /* OpenBSD.  */</span><br><span class="line">#define ELFOSABI_ARM_AEABI  64  /* ARM EABI */</span><br><span class="line">#define ELFOSABI_ARM        97  /* ARM */</span><br><span class="line">#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */</span><br></pre></td></tr></table></figure><p>Android系统下的 SO文件 此处的值默认为 <code>0</code>，<br>而且加载时不检测，修改无影响。</p><h4 id="EI-ABIVERSION"><a href="#EI-ABIVERSION" class="headerlink" title="EI_ABIVERSION"></a><code>EI_ABIVERSION</code></h4><ul><li>Offset: 8</li><li>Length: 1</li><li>Type: Number (unsigned char)</li></ul><p>指出该 <code>ELF</code> 文件可以在哪个API版本下运行，Android下的默认值是 <code>0</code>。</p><p>此处加载时不检测，修改无影响。</p><h4 id="EL-PAD"><a href="#EL-PAD" class="headerlink" title="EL_PAD"></a><code>EL_PAD</code></h4><ul><li>Offset: 9</li><li>Length: 7</li></ul><p>填充位，无检测，修改无影响。</p><h4 id="技巧"><a href="#技巧" class="headerlink" title="技巧"></a>技巧</h4><p>Android <code>&gt;= 6</code> 的系统版本只针对 <code>0x00 - 0x05</code> 之间的字节进行检测，<br><code>0x06 - 0x0F</code> 之间的字节（10 Bytes）无检测，<br>因此可以任意修改，存放想要存放的数据。</p><h2 id="e-type"><a href="#e-type" class="headerlink" title="e_type"></a><code>e_type</code></h2><ul><li>Offset: 0x10</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>ELF文件类型，如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#define ET_NONE 0 /* 无定义【非法】 */</span><br><span class="line">#define ET_REL 1 /* 已编译未链接 */</span><br><span class="line">#define ET_EXEC 2 /* 已编译已链接的可执行程序 */</span><br><span class="line">#define ET_DYN 3 /* 已编译已链接的动态链接库 */</span><br></pre></td></tr></table></figure><p><code>ET_REL</code> 指的是 Relocatable file，<br>缺少 Program Header，<br>不可以加载到内存中，<br><code>gcc</code> 编译时候生成的 <code>.o</code> 文件就是 REL文件。</p><p><code>ET_EXEC</code> 指的是可执行程序，<br>存在程序入口，<br>有 Program Header，<br>可以加载到内存中运行，<br>在 <code>Linux</code> 下的可执行程序都是这样的。</p><p><code>ET_DYN</code> 特指动态链接库。</p><p>由于Android的SO库本质就是动态链接库，<br>因此SO库编译后 <code>e_type = ET_DYN</code>。</p><p>Android系统会检测 <code>e_type</code>。<br>若不为 <code>ET_DYN</code>，则抛出错误 <code>has unexpected e_type</code>，<br>并APP闪退。</p><h2 id="e-machine"><a href="#e-machine" class="headerlink" title="e_machine"></a><code>e_machine</code></h2><ul><li>Offset: 0x12</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p><code>ELF</code> 文件的CPU平台属性（指令集）。</p><p>参考：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#if defined(__arm__)</span><br><span class="line">  return EM_ARM;</span><br><span class="line">#elif defined(__aarch64__)</span><br><span class="line">  return EM_AARCH64;</span><br><span class="line">#elif defined(__i386__)</span><br><span class="line">  return EM_386;</span><br><span class="line">#elif defined(__mips__)</span><br><span class="line">  return EM_MIPS;</span><br><span class="line">#elif defined(__x86_64__)</span><br><span class="line">  return EM_X86_64;</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>Android系统会使用 <code>GetTargetElfMachine</code> 函数<br>检测SO库的 <code>e_machine</code> 和现在的系统是否一致。<br>若不一致，则抛出错误 <code>has unexpected e_machine</code>，<br>并APP闪退。</p><h2 id="e-version"><a href="#e-version" class="headerlink" title="e_version"></a><code>e_version</code></h2><ul><li>Offset: 0x14</li><li>Length: 4</li><li>Type: <code>unsigned int</code></li></ul><p>顾名思义，是 <code>ELF</code> 文件格式的版本号，默认是 <code>EV_CURRENT</code>（&#x3D; 1）。</p><p>Android系统会检测 <code>e_version</code>。<br>若不为 <code>EV_CURRENT</code>，则抛出错误 <code>has unexpected e_version</code>，<br>并APP闪退。</p><h2 id="e-entry"><a href="#e-entry" class="headerlink" title="e_entry"></a><code>e_entry</code></h2><ul><li>Offset: 0x18</li><li>Length: 4 (32bits)</li><li>Type: <code>unsigned int</code></li></ul><p>ELF程序的入口虚拟地址。仅用于可执行程序加载完成后，从此处开始执行进程指令。</p><p>动态链接库不存在入口地址，所以Android系统不检测。</p><h2 id="e-phoff"><a href="#e-phoff" class="headerlink" title="e_phoff"></a><code>e_phoff</code></h2><ul><li>Offset: 0x1C</li><li>Length: 4 (32bits)</li><li>Type: <code>unsigned int</code></li></ul><p>程序头表的偏移，涉及“连接视图”和“执行视图”。</p><p>与实际SO库中代码的指令执行相关，不允许修改。</p><h2 id="e-shoff"><a href="#e-shoff" class="headerlink" title="e_shoff"></a><code>e_shoff</code></h2><ul><li>Offset: 0x20</li><li>Length: 4 (32bits)</li><li>Type: <code>unsigned int</code></li></ul><p>段表在文件中的偏移，涉及读取段表。</p><p>Android <code>&lt;7</code> 时，读取段表依靠视图，使用的是 <code>e_phoff</code>，而非 <code>e_shoff</code>，<br>因此可以随意修改。</p><blockquote><p>涉及 <code>linker_phdr.cpp</code> 的 <code>phdr_table_get_dynamic_section</code> 函数</p></blockquote><p>Android <code>&gt;=7</code> 时，读取段表依靠 <code>e_shoff</code>，<br>修改为其他值会导致无法定位 <code>.dynamic</code> 段，<br>抛出错误 “.dynamic section header was not found”。</p><blockquote><p>涉及 <code>linker_phdr.cpp</code> 的 <code>ElfReader::ReadDynamicSection</code> 函数</p></blockquote><p>为什么要强调这个？因为<a href="https://bbs.pediy.com/thread-191649-1.htm">看雪里面的这个文章</a>是不兼容新的OS的，内容过时了。</p><h2 id="e-flags"><a href="#e-flags" class="headerlink" title="e_flags"></a><code>e_flags</code></h2><ul><li>Offset: 0x24</li><li>Length: 4 (32bits)</li><li>Type: <code>unsigned int</code></li></ul><p>ELF标志位，用来标志一些ELF文件平台相关的属性。</p><p>Android 系统不使用也不检测此参数。</p><h2 id="e-ehsize"><a href="#e-ehsize" class="headerlink" title="e_ehsize"></a><code>e_ehsize</code></h2><ul><li>Offset: 0x28</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>ELF头部的长度。</p><p>Android 系统不使用也不检测此参数。</p><h2 id="e-phentsize"><a href="#e-phentsize" class="headerlink" title="e_phentsize"></a><code>e_phentsize</code></h2><ul><li>Offset: 0x2A</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>程序头的大小。</p><p>Android 系统不使用也不检测此参数。</p><h2 id="e-phnum"><a href="#e-phnum" class="headerlink" title="e_phnum"></a><code>e_phnum</code></h2><ul><li>Offset: 0x2C</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>在执行视图中，Segments的数量。</p><p>Android 系统对其进行检测，并且严格到实际的数目。<br>若不一致，则抛出错误 “has invalid e_phnum”、“has invalid phdr offset&#x2F;size”<br>或者 “phdr mmap failed”等。</p><blockquote><p>涉及函数 <code>ElfReader::ReadProgramHeaders</code> 和 <code>ElfReader::ReadProgramHeader</code>。</p></blockquote><h2 id="e-shentsize"><a href="#e-shentsize" class="headerlink" title="e_shentsize"></a><code>e_shentsize</code></h2><ul><li>Offset: 0x2E</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>段表描述符的大小，<code>= sizeof(ElfW(Shdr))</code>。</p><p>Android <code>&lt;= 6</code> 系统不使用也不检测此参数。</p><p>Android <code>&gt;= 7</code> 系统检测此参数。<br>若与 <code>sizeof(ElfW(Shdr))</code> 不相等，<br>则抛出错误 “has unsupported e_shentsize”。</p><h2 id="e-shnum"><a href="#e-shnum" class="headerlink" title="e_shnum"></a><code>e_shnum</code></h2><ul><li>Offset: 0x30</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>段表描述符的数量。这个值等于ELF文件中拥有的段（section）的数量。</p><p>Android <code>&lt;= 6</code> 系统不使用也不检测此参数。</p><p>Android <code>&gt;= 7</code> 系统检测并使用此参数。<br>若为0，则抛出错误 “has no section headers”。<br>若超出文件大小范围，则抛出错误 “has invalid shdr offset&#x2F;size”。</p><blockquote><p>参考函数 <code>ElfReader::ReadSectionHeaders</code>。</p></blockquote><h2 id="e-shstrndx"><a href="#e-shstrndx" class="headerlink" title="e_shstrndx"></a><code>e_shstrndx</code></h2><ul><li>Offset: 0x32</li><li>Length: 2</li><li>Type: <code>unsigned short</code></li></ul><p>段表字符串表所在的段在段表中的下标，一般是 <code>= e_shnum - 1</code>。</p><p>Android <code>&lt;= 6</code> 系统不使用也不检测此参数。</p><p>Android <code>&gt;= 7</code> 系统检测此参数。<br>若为0，则抛出错误 “has invalid e_shstrndx”。</p><blockquote><p>参考函数 <code>ElfReader::VerifyElfHeader</code>。</p></blockquote><h1 id="偷就完事了"><a href="#偷就完事了" class="headerlink" title="偷就完事了"></a>偷就完事了</h1><p>在ELF头部，有这些地方可以疯狂偷空间：</p><ol><li><code>0x06 - 0x0F</code> 合计 10Bytes</li><li><code>0x18 - 0x1B</code> 合计 4Bytes</li><li><code>0x24 - 0x2B</code> 合计 8Bytes</li></ol><p>无限制存储空间合计为 22Bytes。</p><p>以下地方，只有Android <code>&lt;=6</code> 才可以偷空间存东西：</p><ol><li><code>0x20 - 0x23</code> 合计 4Bytes</li><li><code>0x2E - 0x33</code> 合计 6Bytes</li></ol><p>存在版本限制存储空间合计为 10Bytes。</p><p>为什么要偷空间出来存数据？<br>主要还是为了方便SO库的打包工具做一些附加数据的存储，<br>偷出来的地方是随SO库加载而加载，可以在内存直接读取的。</p><h1 id="值得学习"><a href="#值得学习" class="headerlink" title="值得学习"></a>值得学习</h1><p>Android 系统源码提供了很多很好玩的设计思路，下面展示一些：</p><h2 id="Code-A"><a href="#Code-A" class="headerlink" title="Code A"></a>Code A</h2><p>根据不同的指令集，使用不同的结构体：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 如果是 64bits 的系统</span><br><span class="line">#if defined(__LP64__)</span><br><span class="line">#define ElfW(type) Elf64_ ## type</span><br><span class="line">#else</span><br><span class="line">#define ElfW(type) Elf32_ ## type</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">// ElfW(Ehdr) === Elf32_Ehdr</span><br></pre></td></tr></table></figure><h2 id="Code-B"><a href="#Code-B" class="headerlink" title="Code B"></a>Code B</h2><p>一些关于指令集的宏定义：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// __arm__ -&gt; armeabi, armeabi-v7a [32bits]</span><br><span class="line">// __aarch64__ -&gt; arm64-v8a [64bits]</span><br><span class="line">// __i386__ -&gt; x86 [32bits]</span><br><span class="line">// __mips__ -&gt; mips, mips64 [32 / 64bits]</span><br><span class="line">// __x86_64__ -&gt; x86_64 [64bits]</span><br><span class="line"></span><br><span class="line">#if defined(__arm__)</span><br><span class="line">  printf(&quot;This is ARM Architecture!&quot;);</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;因为项目的需要，我对Android系统加载&lt;code&gt;.so&lt;/code&gt;文件有一些些研究，&lt;br&gt;把最近看过的一些大牛的分析和现状结合一下，&lt;br&gt;写篇东西做一下笔记。&lt;/p&gt;
&lt;h1 id=&quot;so-文件是什么&quot;&gt;&lt;a href=&quot;#so-文件是什么&quot; class=&quot;headerlink&quot; title=&quot;.so 文件是什么&quot;&gt;&lt;/a&gt;&lt;code&gt;.so&lt;/code&gt; 文件是什么&lt;/h1&gt;&lt;p&gt;&lt;code&gt;.so&lt;/code&gt; 文件是 &lt;code&gt;Shared Object&lt;/code&gt; 文件的后缀，&lt;br&gt;直白的说就是Linux系统中的“动态链接库” ，&lt;br&gt;就和Windows系统下的 &lt;code&gt;.dll&lt;/code&gt; 文件（Dynamic Link Library）类似。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.so&lt;/code&gt; 文件实则是 &lt;code&gt;ELF&lt;/code&gt; 格式的文件，和Linux的可执行文件的格式一致。&lt;/p&gt;
&lt;h1 id=&quot;ELF-文件结构&quot;&gt;&lt;a href=&quot;#ELF-文件结构&quot; class=&quot;headerlink&quot; title=&quot;ELF 文件结构&quot;&gt;&lt;/a&gt;&lt;code&gt;ELF&lt;/code&gt; 文件结构&lt;/h1&gt;&lt;p&gt;&lt;code&gt;ELF&lt;/code&gt; 格式的文件中的“数据”实际上是以“段”（节，英文：Section）的形式存储的。&lt;br&gt;其顺序大致符合下面的排序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELF Header：ELF头部&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynsym&lt;/code&gt;：保存动态链接相关符号，记录其偏移值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynstr&lt;/code&gt;：&lt;code&gt;.dynsym&lt;/code&gt; 的辅助段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.hash&lt;/code&gt;：快速查找符号的哈希表，类似 &lt;code&gt;.dynstr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.got&lt;/code&gt;：数据引用修正，修正到 &lt;code&gt;.got&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.plt&lt;/code&gt;：函数引用修正，修正到 &lt;code&gt;.got.plt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.text&lt;/code&gt;：代码段&lt;/li&gt;
&lt;li&gt;其他自定义的代码段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rodata&lt;/code&gt;：字符串常量段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.fini_array&lt;/code&gt;：终止函数段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.init_array&lt;/code&gt;：初始化函数段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynamic&lt;/code&gt;：动态链接库特有，存储动态链接用到的表信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.got&lt;/code&gt;：函数的绝对地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.data&lt;/code&gt;：存放已经初始化的全局变量，静态内存分配相关&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.bss&lt;/code&gt;：存放未初始化的全局变量，静态内存分配相关&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;ELF-头部：ELF-Header&quot;&gt;&lt;a href=&quot;#ELF-头部：ELF-Header&quot; class=&quot;headerlink&quot; title=&quot;ELF 头部：ELF Header&quot;&gt;&lt;/a&gt;ELF 头部：&lt;code&gt;ELF Header&lt;/code&gt;&lt;/h1&gt;&lt;p&gt;其中，一切的起点都在ELF头部，其偏移量（offset）为 0。&lt;br&gt;ELF头部的结构体为 &lt;code&gt;elf32_hdr&lt;/code&gt; 或 &lt;code&gt;elf64_hdr&lt;/code&gt;，&lt;br&gt;在Android系统源代码的 &lt;code&gt;/bionic/libc/kernel/uapi/linux/elf.h&lt;/code&gt; 可以找到。&lt;/p&gt;
&lt;p&gt;以32位程序的ELF头部为例：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>批量替换PPT中的颜色</title>
    <link href="https://blog.micblo.com/2017/11/14/%E6%89%B9%E9%87%8F%E6%9B%BF%E6%8D%A2PPT%E4%B8%AD%E7%9A%84%E9%A2%9C%E8%89%B2/"/>
    <id>https://blog.micblo.com/2017/11/14/%E6%89%B9%E9%87%8F%E6%9B%BF%E6%8D%A2PPT%E4%B8%AD%E7%9A%84%E9%A2%9C%E8%89%B2/</id>
    <published>2017-11-14T11:25:04.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近一直在忙安卓的事情，感觉博客就要荒废掉了一样。</p><p>趁着有一点点空的时间把之前的一些idea都记录下来。</p><p>这个idea来源于PPT的浏览和打印。</p><p>有些PPT非常的“直男设计”，配色异常奇葩。</p><p>好比说黄色字白色底，黑色底棕色字，灰常不利于阅读。</p><p>那怎么办呢？第一时间想到的就是在Powerpoint上批量换色。</p><p>但是问题来了，Powerpoint似乎是没有选择全部“对象”的功能的。</p><p>上百页的PPT，这么多的文本框，怎么搞啊。</p><p>这时我就想到了 <code>.pptx</code> 是一个<code>Zip</code>压缩文件，用<code>XML</code>描述一个PPT。</p><p>讲道理，<br>可以对PPT里面的每一页里面的颜色的节点做一个字符串的替换，<br>这样就可以实现批量换色了。</p><p>那就动手吧~</p><h2 id="基础装备"><a href="#基础装备" class="headerlink" title="基础装备"></a>基础装备</h2><p>你需要一个压缩和解压 <code>Zip</code> 的工具，我选择 <code>BandiZip</code>。</p><p>你还需要一个脚本语言，我选择无敌的 <code>Node.js</code>。</p><p>当然，最关键的是需要一个Powerpoint，至少也要2007以后的吧。</p><h2 id="开始动手"><a href="#开始动手" class="headerlink" title="开始动手"></a>开始动手</h2><h4 id="保存-pptx"><a href="#保存-pptx" class="headerlink" title="保存 .pptx"></a>保存 <code>.pptx</code></h4><p>首先，我们需要把PPT另存为为 <code>.pptx</code> 格式的，<br>因为 <code>.ppt</code> 不是用 <code>XML</code> 描述一个PPT的。</p><blockquote><p>如果本来就是 <code>.pptx</code> 那就无视上面那一步。</p></blockquote><h4 id="确认要替换的颜色的RGB值"><a href="#确认要替换的颜色的RGB值" class="headerlink" title="确认要替换的颜色的RGB值"></a>确认要替换的颜色的RGB值</h4><p>第二，我们确认一下需要替换的颜色和替换到什么颜色。</p><p>这个需要我们在Powerpoint中选中有目标颜色的文本，<br>在“字体颜色”那儿选择“其他颜色”，<br>弹出的框框选自定义就可以看到颜色的RGB值了。</p><p>在这里颜色是红绿蓝的RGB的十进制数。<br>所以你会看到类似“红色 255 绿色 128 蓝色 0”这样的东西。</p><p>但是我们得转化成十六进制的RGB颜色值呀~</p><p>所以下面我提供了一个小函数方便大家转换（用控制台即可使用）：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">toRGBCode</span>(<span class="params">r, g, b</span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`#<span class="subst">$&#123;pad(r)&#125;</span><span class="subst">$&#123;pad(g)&#125;</span><span class="subst">$&#123;pad(b)&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">pad</span>(<span class="params">num</span>) &#123; </span><br><span class="line">    <span class="keyword">return</span> (<span class="title class_">Array</span>(<span class="number">2</span>).<span class="title function_">join</span>(<span class="string">&quot;0&quot;</span>) + num.<span class="title function_">toString</span>(<span class="number">16</span>)).<span class="title function_">slice</span>(-<span class="number">2</span>); </span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接调用 <code>toRGBCode</code> 这个函数，分别传入红色、蓝色、绿色的值即可。</p><p>如：<code>toRGBCode(255, 0, 0)</code> 会输出 <code>&quot;#ff0000&quot;</code></p><h4 id="提取每一页的XML"><a href="#提取每一页的XML" class="headerlink" title="提取每一页的XML"></a>提取每一页的XML</h4><p>使用解压工具把你要搞的 <code>XXXX.pptx</code> 解压了，<br>找到 <code>ppt/slides</code> 这个文件夹，<br>把整个文件夹解压出来，<br>这个就是每一页的PPT的 <code>XML</code>文件。</p><p>解压完毕之后，请看一下最后一个文件的需要是多少。</p><p>好比说，<code>slide27.xml</code> 是序号最大的文件，就说明这个PPT有 27 页。</p><p>好啦，记下来多少页之后就可以上脚本了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> start = <span class="number">1</span>; <span class="comment">// 开始的页码</span></span><br><span class="line"><span class="keyword">const</span> end = <span class="number">44</span>; <span class="comment">// 需要替换颜色的最大页码</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> path = <span class="string">&#x27;./slides/&#x27;</span>; <span class="comment">// Slide的XML文件夹位置</span></span><br><span class="line"><span class="title function_">run</span>(path, start, end);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">run</span>(<span class="params">path, start, end</span>) &#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = start; i &lt;= end; i++) &#123;</span><br><span class="line">    <span class="title function_">worker</span>(path, i);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;ok!&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">worker</span>(<span class="params">path, page</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> data = fs.<span class="title function_">readFileSync</span>(<span class="string">`<span class="subst">$&#123;path&#125;</span>slide<span class="subst">$&#123;page&#125;</span>.xml`</span>).<span class="title function_">toString</span>();</span><br><span class="line">  data = <span class="title function_">replaceColor</span>(data, <span class="string">&#x27;FF0000&#x27;</span>, <span class="string">&#x27;000000&#x27;</span>); <span class="comment">// 替换红色为黑色</span></span><br><span class="line">  data = <span class="title function_">replaceColor</span>(data, <span class="string">&#x27;FFFF00&#x27;</span>, <span class="string">&#x27;000000&#x27;</span>); <span class="comment">// 替换黄色为黑色</span></span><br><span class="line">  fs.<span class="title function_">writeFileSync</span>(<span class="string">`<span class="subst">$&#123;path&#125;</span>slide<span class="subst">$&#123;page&#125;</span>.xml`</span>, data);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">replaceColor</span>(<span class="params">data, srcColor, targetColor</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> data.<span class="title function_">replace</span>(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">`&lt;a:srgbClr val=&quot;<span class="subst">$&#123;srcColor.toUpperCase()&#125;</span>&quot;/&gt;`</span>, <span class="string">&#x27;g&#x27;</span>),</span><br><span class="line">    <span class="string">`&lt;a:srgbClr val=&quot;<span class="subst">$&#123;targetColor.toUpperCase()&#125;</span>&quot;/&gt;`</span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>记得看一下脚本的注释，把该修改的地方都给改了。</p><p>好比说页码、Slides文件夹路径、需要替换的颜色和目标颜色，一定一定要改！！</p><p>脚本保存后运行一下就可以啦~</p><h4 id="打包回去"><a href="#打包回去" class="headerlink" title="打包回去"></a>打包回去</h4><p>还是用 <code>BandiZip</code>，我们把刚刚处理完的Slides文件夹整个压缩回去。</p><p>用 <code>BandiZip</code> 打开 <code>.pptx</code>文件后，用“添加”功能就可以啦~</p><p>Ok！ 赶紧打开 Powerpoint 看一下效果吧！ </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近一直在忙安卓的事情，感觉博客就要荒废掉了一样。&lt;/p&gt;
&lt;p&gt;趁着有一点点空的时间把之前的一些idea都记录下来。&lt;/p&gt;
&lt;p&gt;这个idea来源于PPT的浏览和打印。&lt;/p&gt;
&lt;p&gt;有些PPT非常的“直男设计”，配色异常奇葩。&lt;/p&gt;
&lt;p&gt;好比说黄色字白色底，黑色底棕色字，灰常不利于阅读。&lt;/p&gt;
&lt;p&gt;那怎么办呢？第一时间想到的就是在Powerpoint上批量换色。&lt;/p&gt;
&lt;p&gt;但是问题来了，Powerpoint似乎是没有选择全部“对象”的功能的。&lt;/p&gt;
&lt;p&gt;上百页的PPT，这么多的文本框，怎么搞啊。&lt;/p&gt;
&lt;p&gt;这时我就想到了 &lt;code&gt;.pptx&lt;/code&gt; 是一个&lt;code&gt;Zip&lt;/code&gt;压缩文件，用&lt;code&gt;XML&lt;/code&gt;描述一个PPT。&lt;/p&gt;
&lt;p&gt;讲道理，&lt;br&gt;可以对PPT里面的每一页里面的颜色的节点做一个字符串的替换，&lt;br&gt;这样就可以实现批量换色了。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>MongoDB自动备份小脚本</title>
    <link href="https://blog.micblo.com/2017/10/03/MongoDB%E8%87%AA%E5%8A%A8%E5%A4%87%E4%BB%BD%E5%B0%8F%E8%84%9A%E6%9C%AC/"/>
    <id>https://blog.micblo.com/2017/10/03/MongoDB%E8%87%AA%E5%8A%A8%E5%A4%87%E4%BB%BD%E5%B0%8F%E8%84%9A%E6%9C%AC/</id>
    <published>2017-10-03T20:22:20.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近怼一个老项目，需要做<code>MongoDB</code>的备份，就找了一下脚本，大家可以参考一下。</p><h2 id="备份脚本"><a href="#备份脚本" class="headerlink" title="备份脚本"></a>备份脚本</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash  </span><br><span class="line">sourcepath=&#x27;&#x27;</span><br><span class="line">targetpath=&#x27;/home/ubuntu/backup_db&#x27;</span><br><span class="line"># 改成你自己的备份目录</span><br><span class="line">nowtime=$(date +%Y%m%d)</span><br><span class="line"></span><br><span class="line">start()</span><br><span class="line">&#123;</span><br><span class="line">  $&#123;sourcepath&#125;mongodump --host 127.0.0.1 --port 27017 --out $&#123;targetpath&#125;/$&#123;nowtime&#125;</span><br><span class="line">  # 备份 127.0.0.1:27017 的整个MongoDB数据库</span><br><span class="line">  # 输出到目标目录下的当前日期目录</span><br><span class="line">  # 如：/backup_db/20171003/</span><br><span class="line">&#125;</span><br><span class="line">execute()  </span><br><span class="line">&#123;</span><br><span class="line">  start</span><br><span class="line">  if [ $? -eq 0 ]</span><br><span class="line">  then</span><br><span class="line">    echo &quot;back successfully!&quot;  </span><br><span class="line">  else</span><br><span class="line">    echo &quot;back failure!&quot;  </span><br><span class="line">  fi</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if [ ! -d &quot;$&#123;targetpath&#125;/$&#123;nowtime&#125;/&quot; ]</span><br><span class="line">then</span><br><span class="line"> mkdir $&#123;targetpath&#125;/$&#123;nowtime&#125;</span><br><span class="line">fi</span><br><span class="line">execute</span><br><span class="line">echo &quot;============== back end $&#123;nowtime&#125; ==============&quot;</span><br></pre></td></tr></table></figure><blockquote><p>使用前记得改一下脚本里面的参数，按照需求调整目录</p></blockquote><h2 id="删除过期备份"><a href="#删除过期备份" class="headerlink" title="删除过期备份"></a>删除过期备份</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash  </span><br><span class="line">targetpath=&#x27;/home/ubuntu/backup_db&#x27;</span><br><span class="line">nowtime=$(date -d &#x27;-7 days&#x27; &quot;+%Y%m%d&quot;)</span><br><span class="line">if [ -d &quot;$&#123;targetpath&#125;/$&#123;nowtime&#125;/&quot; ]</span><br><span class="line">then</span><br><span class="line">  rm -rf &quot;$&#123;targetpath&#125;/$&#123;nowtime&#125;/&quot;</span><br><span class="line">  echo &quot;=======$&#123;targetpath&#125;/$&#123;nowtime&#125;/===删除完毕==&quot;  </span><br><span class="line">fi</span><br><span class="line">echo &quot;===$nowtime ===&quot;</span><br></pre></td></tr></table></figure><blockquote><p>哈哈，只是把命令改成 <code>rm -rf</code> 了而已。记得按照需求调整参数里面的目录</p></blockquote><h2 id="自动备份和删除备份"><a href="#自动备份和删除备份" class="headerlink" title="自动备份和删除备份"></a>自动备份和删除备份</h2><p>不得不说我们的主角 <code>crontab -e</code></p><p><code>Cron</code> 可以实现定时任务的功能。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">10 04 * * * /home/ubuntu/cron-shell/mongobak 1 &gt; /var/log/cron/crontab_mongo_back.log &amp;</span><br><span class="line">10 02 * * * /home/ubuntu/cron-shell/mongobak_delete 1 &gt; /var/log/cron/crontab_mongo_delete.log &amp;</span><br></pre></td></tr></table></figure><p>半夜三更的时候，定时任务就会执行 <code>mongobak</code> 和 <code>mongobak_delete</code> 两个脚本啦~</p><p>并且两个脚本的输出也会保存到 <code>/var/log/cron/</code> 的日志文件里面，方便你后期检查。</p><p>上面的命令请根据实际需要调整喔</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近怼一个老项目，需要做&lt;code&gt;MongoDB&lt;/code&gt;的备份，就找了一下脚本，大家可以参考一下。&lt;/p&gt;
&lt;h2 id=&quot;备份脚本&quot;&gt;&lt;a href=&quot;#备份脚本&quot; class=&quot;headerlink&quot; title=&quot;备份脚本&quot;&gt;&lt;/a&gt;备份脚本&lt;/h2&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;#!/bin/bash  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sourcepath=&amp;#x27;&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;targetpath=&amp;#x27;/home/ubuntu/backup_db&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;# 改成你自己的备份目录&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nowtime=$(date +%Y%m%d)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;start()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  $&amp;#123;sourcepath&amp;#125;mongodump --host 127.0.0.1 --port 27017 --out $&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  # 备份 127.0.0.1:27017 的整个MongoDB数据库&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  # 输出到目标目录下的当前日期目录&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  # 如：/backup_db/20171003/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;execute()  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  start&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  if [ $? -eq 0 ]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  then&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    echo &amp;quot;back successfully!&amp;quot;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  else&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    echo &amp;quot;back failure!&amp;quot;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  fi&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;if [ ! -d &amp;quot;$&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;/&amp;quot; ]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;then&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; mkdir $&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;fi&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;execute&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;echo &amp;quot;============== back end $&amp;#123;nowtime&amp;#125; ==============&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;使用前记得改一下脚本里面的参数，按照需求调整目录&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;删除过期备份&quot;&gt;&lt;a href=&quot;#删除过期备份&quot; class=&quot;headerlink&quot; title=&quot;删除过期备份&quot;&gt;&lt;/a&gt;删除过期备份&lt;/h2&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;#!/bin/bash  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;targetpath=&amp;#x27;/home/ubuntu/backup_db&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nowtime=$(date -d &amp;#x27;-7 days&amp;#x27; &amp;quot;+%Y%m%d&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;if [ -d &amp;quot;$&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;/&amp;quot; ]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;then&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  rm -rf &amp;quot;$&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;/&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  echo &amp;quot;=======$&amp;#123;targetpath&amp;#125;/$&amp;#123;nowtime&amp;#125;/===删除完毕==&amp;quot;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;fi&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;echo &amp;quot;===$nowtime ===&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;哈哈，只是把命令改成 &lt;code&gt;rm -rf&lt;/code&gt; 了而已。记得按照需求调整参数里面的目录&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;自动备份和删除备份&quot;&gt;&lt;a href=&quot;#自动备份和删除备份&quot; class=&quot;headerlink&quot; title=&quot;自动备份和删除备份&quot;&gt;&lt;/a&gt;自动备份和删除备份&lt;/h2&gt;&lt;p&gt;不得不说我们的主角 &lt;code&gt;crontab -e&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cron&lt;/code&gt; 可以实现定时任务的功能。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>ES6 下实现静态方法间的相互调用</title>
    <link href="https://blog.micblo.com/2017/03/17/ES6-%E4%B8%8B%E5%AE%9E%E7%8E%B0%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E9%97%B4%E7%9A%84%E7%9B%B8%E4%BA%92%E8%B0%83%E7%94%A8/"/>
    <id>https://blog.micblo.com/2017/03/17/ES6-%E4%B8%8B%E5%AE%9E%E7%8E%B0%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E9%97%B4%E7%9A%84%E7%9B%B8%E4%BA%92%E8%B0%83%E7%94%A8/</id>
    <published>2017-03-17T23:24:28.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>成龙大哥突然来问我一个前端问题，他问：</p><blockquote><p>使用 ES6 的类，类中有两个方法彼此会相互调用，那怎么办?</p></blockquote><p>听到这个问题的时候，我是一头雾水的，不是用 <code>this</code> 就好了吗。</p><p>然后他就给了个场景，让我来怼。</p><h2 id="场景伪代码"><a href="#场景伪代码" class="headerlink" title="场景伪代码"></a>场景伪代码</h2><p>照着大哥的想法，我撸了一个伪代码。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class Fetch&#123;</span><br><span class="line">  static 检查返值() &#123;</span><br><span class="line">    检查;</span><br><span class="line">    如果签名过期，get（）</span><br><span class="line">  &#125;</span><br><span class="line">  static get() &#123;</span><br><span class="line">    请求</span><br><span class="line">    .then(检查返回值)</span><br><span class="line">    </span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Fetch.get();</span><br></pre></td></tr></table></figure><p>可以看到就一个类 <code>Fetch</code>，里面有两个静态方法——<code>检查返值</code> 和 <code>get</code>。</p><p>类的外面只有一行代码——<code>Fetch.get()</code>，用于调用 <code>Fetch</code> 的静态方法 <code>get</code>。</p><p>先从 <code>Fetch</code> 的静态方法 <code>get</code> 入手，我们可以看到 <code>get</code> 方法内只有一行代码，里面的 <code>then</code> 暴露了“请求”是一个 <code>Promise</code> 的实例。</p><p>可以发现，“请求”接收到 <code>resolved</code> 信号时，需要执行  <code>Fetch</code> 的静态方法 <code>检查返值</code>。</p><p>在检查反值中，程序系那个会判定“请求”<code>resolve</code> 时带上的参数。如果里面的参数不符合规定，则重新进行一次 <code>Fetch.get()</code>。</p><h2 id="锁定毒瘤"><a href="#锁定毒瘤" class="headerlink" title="锁定毒瘤"></a>锁定毒瘤</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class Fetch&#123;</span><br><span class="line">  static check(value) &#123;</span><br><span class="line">    if (!value.isCert) &#123;</span><br><span class="line">      this.get();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      console.log(&quot;Get it!&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  static get() &#123;</span><br><span class="line">    request()</span><br><span class="line">      .then(this.check)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// Start to fetch</span><br><span class="line">Fetch.get();</span><br></pre></td></tr></table></figure><p>大哥的代码有毒，问题发生在 <code>request().then()</code> 上。</p><p>大哥的做法是 <code>request().then(this.check)</code>。</p><p>虽然 <code>check</code> 方法还是可以执行，但是它只能执行一次。因为 <code>Promise</code> 的 <code>then</code> 内的作用域默认是全局 (<code>global/windows</code>)，而且在严格模式下是 <code>undefined</code>，因此，<code>this</code> 指向(<code>call</code>)的不是 <code>Fetch</code>。所以在执行 <code>this.check</code> 函数时，<code>check</code> 内的 <code>this</code> 并不是 <code>Fetch</code> 对象，<code>this.get()</code> 取出来的是 <code>undefined</code>，故 <code>this.get();</code> 是无法执行的，达不到大哥的需求。</p><h2 id="动手修改"><a href="#动手修改" class="headerlink" title="动手修改"></a>动手修改</h2><p>既然 <code>this.check</code> 它 <code>call</code> 的不是 <code>Fetch</code> 本身，那么我们可以用 <code>.call</code>、<code>.apply</code>、<code>.bind</code>等可以调整函数的 <code>this</code> 的方式实现作用域的传递。</p><h4 id="call"><a href="#call" class="headerlink" title=".call"></a><code>.call</code></h4><p>我们知道，<code>call()</code> 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">request()</span><br><span class="line">    .then((v) =&gt; this.check.call(this, v))</span><br></pre></td></tr></table></figure><h4 id="apply"><a href="#apply" class="headerlink" title=".apply"></a><code>.apply</code></h4><p>类似于 <code>.call</code> ，只是它传递参数值靠数组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">request()</span><br><span class="line">    .then((v) =&gt; this.check.apply(this, [v]))</span><br></pre></td></tr></table></figure><h4 id="bind"><a href="#bind" class="headerlink" title=".bind"></a><code>.bind</code></h4><p><code>bind()</code> 方法会创建一个新函数。</p><p>当这个新函数被调用时，bind()的第一个参数将作为它运行时的 this, 之后的一序列参数将会在传递的实参前传入作为它的参数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">request()</span><br><span class="line">    .then((v) =&gt; this.check.bind(this)(v))</span><br><span class="line">    </span><br><span class="line">request()</span><br><span class="line">    .then((v) =&gt; this.check.bind(this, v)())</span><br></pre></td></tr></table></figure><h4 id="不用-this"><a href="#不用-this" class="headerlink" title="不用 this"></a>不用 <code>this</code></h4><p>不用 <code>this</code> 就相当于直接指向 <code>Fetch</code> 本身。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">static check(value) &#123;</span><br><span class="line">    if (!value.isCert) &#123;</span><br><span class="line">        Fetch.get();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        console.log(&quot;Get it!&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>但是这样的做法，不是很好，可维护性比较差。</p><h2 id="修改后的代码"><a href="#修改后的代码" class="headerlink" title="修改后的代码"></a>修改后的代码</h2><p>最后，我们选择了最简洁的 <code>.call</code> 来实现这个需求。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">function request() &#123;</span><br><span class="line">  return new Promise(function (resolve, reject) &#123;</span><br><span class="line">    setTimeout(() =&gt; resolve(Math.round(Math.random() * 5)), 500)</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class Fetch&#123;</span><br><span class="line">  static cnt = 1;</span><br><span class="line">  static check(value) &#123;</span><br><span class="line">    if (value) &#123;</span><br><span class="line">      console.log(`Fail!`);</span><br><span class="line">      this.get();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      console.log(&quot;Get it!&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  static get() &#123;</span><br><span class="line">    console.log(`Get now! count: $&#123;this.cnt++&#125;`);</span><br><span class="line">    request()</span><br><span class="line">      .then((v) =&gt; this.check.call(this, v)); //here</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// Start to fetch</span><br><span class="line">Fetch.get();</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;成龙大哥突然来问我一个前端问题，他问：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 ES6 的类，类中有两个方法彼此会相互调用，那怎么办?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;听到这个问题的时候，我是一头雾水的，不是用 &lt;code&gt;this&lt;/code&gt; 就好了吗。&lt;/p&gt;
&lt;p&gt;然后他就给了个场景，让我来怼。&lt;/p&gt;
&lt;h2 id=&quot;场景伪代码&quot;&gt;&lt;a href=&quot;#场景伪代码&quot; class=&quot;headerlink&quot; title=&quot;场景伪代码&quot;&gt;&lt;/a&gt;场景伪代码&lt;/h2&gt;&lt;p&gt;照着大哥的想法，我撸了一个伪代码。&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;class Fetch&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  static 检查返值() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    检查;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    如果签名过期，get（）&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  static get() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    请求&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .then(检查返回值)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Fetch.get();&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;可以看到就一个类 &lt;code&gt;Fetch&lt;/code&gt;，里面有两个静态方法——&lt;code&gt;检查返值&lt;/code&gt; 和 &lt;code&gt;get&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;类的外面只有一行代码——&lt;code&gt;Fetch.get()&lt;/code&gt;，用于调用 &lt;code&gt;Fetch&lt;/code&gt; 的静态方法 &lt;code&gt;get&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;先从 &lt;code&gt;Fetch&lt;/code&gt; 的静态方法 &lt;code&gt;get&lt;/code&gt; 入手，我们可以看到 &lt;code&gt;get&lt;/code&gt; 方法内只有一行代码，里面的 &lt;code&gt;then&lt;/code&gt; 暴露了“请求”是一个 &lt;code&gt;Promise&lt;/code&gt; 的实例。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>nginx + Lua 设定网站定时关闭</title>
    <link href="https://blog.micblo.com/2017/03/12/nginx-Lua-%E8%AE%BE%E5%AE%9A%E7%BD%91%E7%AB%99%E5%AE%9A%E6%97%B6%E5%85%B3%E9%97%AD/"/>
    <id>https://blog.micblo.com/2017/03/12/nginx-Lua-%E8%AE%BE%E5%AE%9A%E7%BD%91%E7%AB%99%E5%AE%9A%E6%97%B6%E5%85%B3%E9%97%AD/</id>
    <published>2017-03-12T20:58:55.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>因为业务的需要，两会期间需要定时开关 <code>nginx</code>服务器上的网站。</p><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h2><ul><li>可以定时开关网站, 默认设定为早8点~晚21点开放；</li><li>被关闭的网站访问时会被强行中断连接；</li><li>设定 <code>geo</code> 的 <code>white_list_ips</code> 可以允许白名单IP不受闭站影响；</li><li>设定 <code>map</code> 的 <code>white_list_websites</code> 可以允许特定的网站(域名)不受闭站影响。</li></ul><h2 id="条件"><a href="#条件" class="headerlink" title="条件"></a>条件</h2><ul><li>需要一个 <code>nginx</code> 服务器软件</li><li><code>nginx</code> 需要编译带上 <code>ngx_http_lua_module</code> 模块</li></ul><p>由于我们的服务器使用 <code>openresty</code>，因此符合以上条件。</p><h2 id="Lua-脚本"><a href="#Lua-脚本" class="headerlink" title="Lua 脚本"></a><code>Lua</code> 脚本</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">--</span><br><span class="line">-- 定时关闭服务器脚本</span><br><span class="line">-- Uer: Micblo</span><br><span class="line">-- Date: 2017/3/3</span><br><span class="line">-- Time: 14:26</span><br><span class="line">--</span><br><span class="line"></span><br><span class="line">local time = os.time()</span><br><span class="line"></span><br><span class="line">-- 两会: 2017-03-12 解封 --</span><br><span class="line">if (</span><br><span class="line">    1489276800 &gt; time and</span><br><span class="line">    ngx.var.white_list_ips == &quot;0&quot; and</span><br><span class="line">    ngx.var.white_list_websites == &quot;0&quot;</span><br><span class="line">) then</span><br><span class="line">    local now_hour = tonumber(os.date(&quot;%H&quot;, time))</span><br><span class="line">    -- 每日 8 ~ 21 点开放 --</span><br><span class="line">    if (now_hour &lt; 8 or now_hour &gt;= 21) then</span><br><span class="line">        ngx.exit(444)</span><br><span class="line">    end</span><br><span class="line">end</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在需要管控的网站的 <code>server &#123;&#125;</code> 下附上 <code>access_by_lua_file lua/cron-block.lua;</code> 调用这个Lua文件。</p><p>注意文件路径需要改成你实际存放这个脚本的位置，相对路径的起始目录为 <code>nginx</code> 的根目录。</p><h2 id="white-list-ips-设置白名单IP"><a href="#white-list-ips-设置白名单IP" class="headerlink" title="white_list_ips 设置白名单IP"></a><code>white_list_ips</code> 设置白名单IP</h2><p>由于业务需要，两会期间依然允许一部分内网IP访问这些关闭的网站。</p><p>我们使用 <code>geo</code> 来存放这些IP。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">geo $remote_addr $white_list_ips &#123;</span><br><span class="line">    default 0;</span><br><span class="line">    10.0.0.0/8 1;</span><br><span class="line">    192.168.0.0/16 1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，在 <code>10.0.0.0/8</code> 和 <code>192.168.0.0/16</code> 网段的小伙伴就可以不受限制访问到网站了。</p><h2 id="white-list-websites-设置域名白名单"><a href="#white-list-websites-设置域名白名单" class="headerlink" title="white_list_websites 设置域名白名单"></a><code>white_list_websites</code> 设置域名白名单</h2><p>由于业务需要，两会期间依然允许一部分网站可以被访问。</p><p>我们使用 <code>map</code> 来存放这些网站的域名。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">map $server_name $white_list_websites &#123;</span><br><span class="line">    default 0;</span><br><span class="line">    example.com 1;</span><br><span class="line">    system.example.com 1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，在 <code>example.com</code> 和 <code>system.example.com</code> 这两个网站可以不被关闭。</p><h2 id="综合"><a href="#综合" class="headerlink" title="综合"></a>综合</h2><p>最后我们把代码揉在一起：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">geo $remote_addr $white_list_ips &#123;</span><br><span class="line">    default 0;</span><br><span class="line">    10.0.0.0/8 1;</span><br><span class="line">    192.168.0.0/16 1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">map $server_name $white_list_websites &#123;</span><br><span class="line">    default 0;</span><br><span class="line">    example.com 1;</span><br><span class="line">system.example.com 1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">access_by_lua_file lua/cron-block.lua;</span><br></pre></td></tr></table></figure><p>建议把这个代码单独放到一个通用的配置文件里面，让全部的 <code>server</code> 都 <code>include</code> 它。</p><p>这样全部的 <code>server</code> 都可以适用这个规则。</p><p>如果不需要用这个脚本，直接注释掉 <code>access_by_lua_file</code> 那行即可。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;因为业务的需要，两会期间需要定时开关 &lt;code&gt;nginx&lt;/code&gt;服务器上的网站。&lt;/p&gt;
&lt;h2 id=&quot;功能&quot;&gt;&lt;a href=&quot;#功能&quot; class=&quot;headerlink&quot; title=&quot;功能&quot;&gt;&lt;/a&gt;功能&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;可以定时开关网站, 默认设定为早8点~晚21点开放；&lt;/li&gt;
&lt;li&gt;被关闭的网站访问时会被强行中断连接；&lt;/li&gt;
&lt;li&gt;设定 &lt;code&gt;geo&lt;/code&gt; 的 &lt;code&gt;white_list_ips&lt;/code&gt; 可以允许白名单IP不受闭站影响；&lt;/li&gt;
&lt;li&gt;设定 &lt;code&gt;map&lt;/code&gt; 的 &lt;code&gt;white_list_websites&lt;/code&gt; 可以允许特定的网站(域名)不受闭站影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;条件&quot;&gt;&lt;a href=&quot;#条件&quot; class=&quot;headerlink&quot; title=&quot;条件&quot;&gt;&lt;/a&gt;条件&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;需要一个 &lt;code&gt;nginx&lt;/code&gt; 服务器软件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx&lt;/code&gt; 需要编译带上 &lt;code&gt;ngx_http_lua_module&lt;/code&gt; 模块&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于我们的服务器使用 &lt;code&gt;openresty&lt;/code&gt;，因此符合以上条件。&lt;/p&gt;
&lt;h2 id=&quot;Lua-脚本&quot;&gt;&lt;a href=&quot;#Lua-脚本&quot; class=&quot;headerlink&quot; title=&quot;Lua 脚本&quot;&gt;&lt;/a&gt;&lt;code&gt;Lua&lt;/code&gt; 脚本&lt;/h2&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;--&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-- 定时关闭服务器脚本&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-- Uer: Micblo&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-- Date: 2017/3/3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-- Time: 14:26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;--&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;local time = os.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-- 两会: 2017-03-12 解封 --&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;if (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    1489276800 &amp;gt; time and&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ngx.var.white_list_ips == &amp;quot;0&amp;quot; and&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ngx.var.white_list_websites == &amp;quot;0&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;) then&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    local now_hour = tonumber(os.date(&amp;quot;%H&amp;quot;, time))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    -- 每日 8 ~ 21 点开放 --&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    if (now_hour &amp;lt; 8 or now_hour &amp;gt;= 21) then&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ngx.exit(444)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    end&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;end&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;在需要管控的网站的 &lt;code&gt;server &amp;#123;&amp;#125;&lt;/code&gt; 下附上 &lt;code&gt;access_by_lua_file lua/cron-block.lua;&lt;/code&gt; 调用这个Lua文件。&lt;/p&gt;
&lt;p&gt;注意文件路径需要改成你实际存放这个脚本的位置，相对路径的起始目录为 &lt;code&gt;nginx&lt;/code&gt; 的根目录。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>软链接的妙用</title>
    <link href="https://blog.micblo.com/2017/02/10/%E8%BD%AF%E9%93%BE%E6%8E%A5%E7%9A%84%E5%A6%99%E7%94%A8/"/>
    <id>https://blog.micblo.com/2017/02/10/%E8%BD%AF%E9%93%BE%E6%8E%A5%E7%9A%84%E5%A6%99%E7%94%A8/</id>
    <published>2017-02-10T15:54:52.000Z</published>
    <updated>2023-04-16T04:33:43.676Z</updated>
    
    <content type="html"><![CDATA[<p>现在我们开发网站大多数情况都是分离前端和后端的嘛。</p><p>前端一般就是 <code>React</code>、 <code>Vue</code> + <code>webpack</code> + <code>gulp</code> 之类的单页网站。</p><p>后端就丰富多啦，<code>Node.js</code> + <code>Express</code> 或者 <code>Python</code> + <code>flask</code> 甚至 <code>PHP</code> 之类的。</p><p>这样就会带来一个问题，就是前端的代码和后端的代码本身就是拆开开发的。</p><p>因此要实现前端和后端优雅地结合在一起是一个很关键的事情。</p><h2 id="传统法-Copy-amp-amp-Paste-战胜一切"><a href="#传统法-Copy-amp-amp-Paste-战胜一切" class="headerlink" title="传统法 - Copy &amp;&amp; Paste 战胜一切"></a>传统法 - <code>Copy &amp;&amp; Paste</code> 战胜一切</h2><p>好比说 <code>Node.js</code>，有的人习惯直接把前端的静态文件全部丢到 <code>public</code> 目录下面。</p><p>那么问题来了，这样不可避免就会在后端的文件夹出现前端的东西，其实很不优雅啊。</p><h2 id="软链接-跨平台的选择"><a href="#软链接-跨平台的选择" class="headerlink" title="软链接 - 跨平台的选择"></a>软链接 - 跨平台的选择</h2><p>熟悉 Linux 系统的小伙伴应该知道“软链接”(<em>Symbolic Link</em>)。</p><p>它是一个文件，可以实现把这个文件和另一个文件或者文件夹“虚拟”绑定，比硬链接更加灵活。</p><p>好比说，我在 <code>/home/payne/</code> 下创建了软链接文件 <code>www</code>，该软链接文件和 <code>/var/www</code> 绑定。</p><p>那么，我访问 <code>/home/payne/www/</code> 就相当于访问 <code>/var/www</code>。</p><p>我用 <code>cat /home/payne/www/apple/index.html</code> 就相当于执行 <code>cat /var/www/apple/index.html</code>。</p><h2 id="软链接的建立方式"><a href="#软链接的建立方式" class="headerlink" title="软链接的建立方式"></a>软链接的建立方式</h2><h4 id="nix-系统"><a href="#nix-系统" class="headerlink" title="*nix 系统"></a><code>*nix</code> 系统</h4><p><code>ln -s &#123;sourse&#125; &#123;target&#125;</code></p><ul><li><code>sourse</code>: 就是源文件或者文件夹的路径</li><li><code>target</code>: 要生成的软链接文件的路径，不填就默认在当前目录下创建与源文件或者文件夹的名字相同的软链接文件。</li></ul><p>下面打个比方：</p><p>我的 <code>Nginx</code> 配置目录在：<code>/usr/local/openresty/nginx/conf</code></p><p>我想在 <code>/etc/nginx</code> 就可以访问到这个目录，你可以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /etc</span><br><span class="line">ln -s /usr/local/openresty/nginx/conf nginx</span><br></pre></td></tr></table></figure><p>你也可以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ln -s /usr/local/openresty/nginx/conf /etc/nginx</span><br></pre></td></tr></table></figure><p>操作完了就可以直接 <code>vi /etc/nginx/nginx.conf</code> 更改默认的 <code>Nginx</code> 配置文件啦~~</p><h4 id="Windows-系统"><a href="#Windows-系统" class="headerlink" title="Windows 系统"></a><code>Windows</code> 系统</h4><p><code>mklink &#123;sourse&#125; &#123;target&#125;</code></p><p>和 <code>*nix</code> 差不多，就是不用加上 <code>flag</code> 而已。</p><h2 id="项目实操"><a href="#项目实操" class="headerlink" title="项目实操"></a>项目实操</h2><p>我有一个 <code>Node.js</code> 的项目，叫 <code>RedPacket</code>。</p><p>在 <code>Express</code> 上，已经绑定静态目录是 <code>public</code>，代码如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app.use(express.static(path.join(__dirname, &#x27;public&#x27;)));</span><br></pre></td></tr></table></figure><p>假设在服务器上，<code>Node.js</code> 的后端在: <code>/var/www/redpacket/backend</code></p><p>前端项目目录在 <code>/var/www/redpacket/frontend</code>。</p><p>那么，我们就需要在 <code>/var/www/redpacket/backend</code> 下做一个软链接文件 <code>public</code> 绑定到 <code>/var/www/redpacket/frontend</code>。</p><p>很简单，执行下面的命令就可以了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ln -s /var/www/redpacket/frontend /var/www/redpacket/backend/public</span><br></pre></td></tr></table></figure><p>如果你在 <code>/var/www/redpacket/backend</code> 目录下，就更简单了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ln -s ../frontend public</span><br></pre></td></tr></table></figure><p>很关键，在 <code>Windows</code> 的开发环境下，也做一样的操作，并且把 <code>public</code> 加入 <code>.gitignore</code> 就可以防止 <code>Git</code> 带上这个软链接文件。</p><p>这样就很顺利地把前后端的目录绑定好啦，而且条理清晰，优雅地分离前后端地代码。</p><p>你学会了吗?</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;现在我们开发网站大多数情况都是分离前端和后端的嘛。&lt;/p&gt;
&lt;p&gt;前端一般就是 &lt;code&gt;React&lt;/code&gt;、 &lt;code&gt;Vue&lt;/code&gt; + &lt;code&gt;webpack&lt;/code&gt; + &lt;code&gt;gulp&lt;/code&gt; 之类的单页网站。&lt;/p&gt;
&lt;p&gt;后端就丰富多啦，&lt;code&gt;Node.js&lt;/code&gt; + &lt;code&gt;Express&lt;/code&gt; 或者 &lt;code&gt;Python&lt;/code&gt; + &lt;code&gt;flask&lt;/code&gt; 甚至 &lt;code&gt;PHP&lt;/code&gt; 之类的。&lt;/p&gt;
&lt;p&gt;这样就会带来一个问题，就是前端的代码和后端的代码本身就是拆开开发的。&lt;/p&gt;
&lt;p&gt;因此要实现前端和后端优雅地结合在一起是一个很关键的事情。&lt;/p&gt;
&lt;h2 id=&quot;传统法-Copy-amp-amp-Paste-战胜一切&quot;&gt;&lt;a href=&quot;#传统法-Copy-amp-amp-Paste-战胜一切&quot; class=&quot;headerlink&quot; title=&quot;传统法 - Copy &amp;amp;&amp;amp; Paste 战胜一切&quot;&gt;&lt;/a&gt;传统法 - &lt;code&gt;Copy &amp;amp;&amp;amp; Paste&lt;/code&gt; 战胜一切&lt;/h2&gt;&lt;p&gt;好比说 &lt;code&gt;Node.js&lt;/code&gt;，有的人习惯直接把前端的静态文件全部丢到 &lt;code&gt;public&lt;/code&gt; 目录下面。&lt;/p&gt;
&lt;p&gt;那么问题来了，这样不可避免就会在后端的文件夹出现前端的东西，其实很不优雅啊。&lt;/p&gt;
&lt;h2 id=&quot;软链接-跨平台的选择&quot;&gt;&lt;a href=&quot;#软链接-跨平台的选择&quot; class=&quot;headerlink&quot; title=&quot;软链接 - 跨平台的选择&quot;&gt;&lt;/a&gt;软链接 - 跨平台的选择&lt;/h2&gt;&lt;p&gt;熟悉 Linux 系统的小伙伴应该知道“软链接”(&lt;em&gt;Symbolic Link&lt;/em&gt;)。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
</feed>
