<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>龍昌博客</title>
  <subtitle>从Pythoneer转向Rubist</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://www.xefan.com/"/>
  <updated>2026-06-20T07:49:20.486Z</updated>
  <id>http://www.xefan.com/</id>
  
  <author>
    <name>龍昌</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>通过实例学习 MCP Server 开发</title>
    <link href="http://www.xefan.com//archives/84188.html"/>
    <id>http://www.xefan.com//archives/84188.html</id>
    <published>2026-06-20T15:16:33.000Z</published>
    <updated>2026-06-20T07:49:20.486Z</updated>
    
    <content type="html"><![CDATA[<p>MCP 是一个连接了大语言模型与工具的协议。本文通过一个简单的实例项目来讲解一下如何开发一个自己的 MCP Server 。</p>
<h3 id="项目背景"><a href="#项目背景" class="headerlink" title="项目背景"></a>项目背景</h3><p>Quip 云笔记将于2027年3月31号停止服务，因此我本人使用了近10年的笔记数据需要导出并迁移到其他笔记工具。<br>因为 Quip 有提供 api 的功能，最简单的一个办法就是直接通过一个脚本来导出数据。<br>然而最近 AI 这么火，所以就打算想自己写一个 MCP Server 练手，用 AI 来导出。</p>
<h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><ol>
<li>创建 API Token: <a href="https://quip.com/dev/token">https://quip.com/dev/token</a></li>
<li>查看 API 文档： <a href="https://quip.com/api">https://quip.com/api</a></li>
<li>下载 Python SDK: <a href="https://github.com/quip/quip-api/tree/master/python">https://github.com/quip/quip-api/tree/master/python</a></li>
<li>安装 gofastmcp 框架： <a href="https://gofastmcp.com/getting-started/installation">https://gofastmcp.com/getting-started/installation</a></li>
</ol>
<h3 id="创建-MCP-Server"><a href="#创建-MCP-Server" class="headerlink" title="创建 MCP Server"></a>创建 MCP Server</h3><p>首先创建一个 MCP Server 并包含一个简单的 tool。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"><span class="keyword">import</span> quip</span><br><span class="line"></span><br><span class="line">client = quip.QuipClient(api_token)</span><br><span class="line">mcp = FastMCP(<span class="string">&quot;Quip&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@mcp.tool(<span class="params">output_schema=&#123;</span></span></span><br><span class="line"><span class="params"><span class="meta">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;object&quot;</span>,</span></span></span><br><span class="line"><span class="params"><span class="meta">    <span class="string">&quot;properties&quot;</span>: &#123;</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;id&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;用户ID&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;name&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;用户名称&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;desktop_folder_id&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;桌面文件夹ID&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;private_folder_id&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;私有文件夹ID&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;trash_folder_id&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;回收站文件夹ID&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;shared_folder_ids&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;array&quot;</span>, <span class="string">&quot;items&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>&#125;, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;共享文件夹ID列表&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;group_folder_ids&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;array&quot;</span>, <span class="string">&quot;items&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>&#125;, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;群组文件夹ID列表&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;subdomain&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;用户子域&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">        <span class="string">&quot;url&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;用户URL&quot;</span>&#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">    &#125;,</span></span></span><br><span class="line"><span class="params"><span class="meta">    <span class="string">&quot;required&quot;</span>: [<span class="string">&quot;id&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;private_folder_id&quot;</span>, <span class="string">&quot;trash_folder_id&quot;</span>],</span></span></span><br><span class="line"><span class="params"><span class="meta">&#125;</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_current_user</span>() -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;获取当前用户信息&quot;&quot;&quot;</span></span><br><span class="line">    data = client.get_authenticated_user()</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    port = <span class="built_in">int</span>(os.environ.get(<span class="string">&quot;PORT&quot;</span>, <span class="number">5000</span>))</span><br><span class="line">    mcp.run(transport=<span class="string">&quot;sse&quot;</span>, host=<span class="string">&quot;127.0.0.1&quot;</span>, port=port)</span><br></pre></td></tr></table></figure>
<p>接着运行该服务，然后通过 MCP Inspector 来进行调试： <code>npx @modelcontextprotocol/inspector</code><br>在 MCP Inspector 界面内，<code>Transport Type</code> 选 <code>SSE</code>，<code>URL</code> 选 <code>http://127.0.0.1:5000/sse</code>，再点击 <code>Connect</code> 看看能否连接成功。<br>然后在右边的 <code>Tools</code> 标签点击 <code>List Tools</code> 看看能否显示出刚刚定义的这个 <code>get_current_user</code> 工具。<br>选择 <code>get_current_user</code> 然后点击 <code>Run Tool</code> 看看能否正常返回用户信息。</p>
<p>接着通过 AI 工具连接上该 MCP 服务，然后进行对话操作。</p>
<p>添加 MCP 服务：<br><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">&#123;</span><br><span class="line">  &quot;mcp&quot;: &#123;</span><br><span class="line">    &quot;quip&quot;: &#123;</span><br><span class="line">      &quot;type&quot;: &quot;remote&quot;,</span><br><span class="line">      &quot;url&quot;: &quot;http://126.0.0.1:5000/sse&quot;,</span><br><span class="line">      &quot;enabled&quot;: true</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>
<p>开启对话：<br><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">通过 Quip MCP 获取当前用户信息</span><br></pre></td></tr></table></figure></p>
<p>看看能否正常返回信息。</p>
<p>以上只是一个简单的例子，完整的代码： <a href="https://github.com/wusuopu/QuipNoteExporter">https://github.com/wusuopu/QuipNoteExporter</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;MCP 是一个连接了大语言模型与工具的协议。本文通过一个简单的实例项目来讲解一下如何开发一个自己的 MCP Server 。&lt;/p&gt;
&lt;h3 id=&quot;项目背景&quot;&gt;&lt;a href=&quot;#项目背景&quot; class=&quot;headerlink&quot; title=&quot;项目背景&quot;&gt;&lt;/a&gt;项目背景
    
    </summary>
    
    
      <category term="AI" scheme="http://www.xefan.com/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>不写一行代码，在 Solana 上进行 SPL Token 发币</title>
    <link href="http://www.xefan.com//archives/84187.html"/>
    <id>http://www.xefan.com//archives/84187.html</id>
    <published>2026-02-04T10:22:16.000Z</published>
    <updated>2026-02-04T02:39:14.820Z</updated>
    
    <content type="html"><![CDATA[<p>按照之前的文章，本地安装 Solana CLI 之后，会有一个 spl-token 的命令。<br>在发币之后需要先了解一下 Solana SPL 相关的概念。</p>
<p>类似于 Ethereum 的 ERC20 程序， Solana 官方提供了一个 SPL Token 程序，专门是用于发币的，对应的 Program ID 是 <code>TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA</code>。</p>
<h3 id="创建-Mint-Account"><a href="#创建-Mint-Account" class="headerlink" title="创建 Mint Account"></a>创建 Mint Account</h3><p>先创建一个 Mint Account，用于后续的铸币。这里我们为自己的 token 设置精度为6位小数。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ spl-token create-token --decimals=6</span><br><span class="line">Creating token CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA</span><br><span class="line"></span><br><span class="line">Address:  CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk</span><br><span class="line">Decimals:  6</span><br><span class="line"></span><br><span class="line">Signature: 3f8q7Dserwzf4NHeCbmgCepGfBYLtuimi8tznTBunbKTwdFqHRMqr6iR1kTc8hfKF5DPtAeveQ4EjBzmQWbXLCjb</span><br></pre></td></tr></table></figure>
<h3 id="创建-Associated-Token-Account"><a href="#创建-Associated-Token-Account" class="headerlink" title="创建 Associated Token Account"></a>创建 Associated Token Account</h3><p>再创建一个ATA(Associated Token Account)，用于接收铸造的币。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 这里为刚刚的 Mint Account 创建 ATA</span></span><br><span class="line">$ spl-token create-account CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk</span><br><span class="line">Creating account EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v</span><br><span class="line"></span><br><span class="line">Signature: 3mRP9mMtTZZSFJnWXAvCffHVgQF3jdgecAqnmUTSTVoi51HyfWoe3fvWWeC3v71XML9h3mwjwrnpG8zquXrgy1b2</span><br></pre></td></tr></table></figure>
<h2 id="铸币"><a href="#铸币" class="headerlink" title="铸币"></a>铸币</h2><p>往刚刚创建的 ATA 账号发10个币；因为我们的 token 有6位小数，所以实际发的币需要乘以 10*6。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ spl-token mint CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk 10000000 EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v</span><br><span class="line">Minting 10000000 tokens</span><br><span class="line">  Token: CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk</span><br><span class="line">  Recipient: EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v</span><br><span class="line"></span><br><span class="line">Signature: 87psf5aZak7BCQriScEkioqTW2ozvJn5K6mmjTibstVTWofwiKom3FvWGkkiddJ6oq8KUa2vLkYSKku1r87wdst</span><br></pre></td></tr></table></figure>
<p>至此 Solana 的基本发币操作就完成了。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;按照之前的文章，本地安装 Solana CLI 之后，会有一个 spl-token 的命令。&lt;br&gt;在发币之后需要先了解一下 Solana SPL 相关的概念。&lt;/p&gt;
&lt;p&gt;类似于 Ethereum 的 ERC20 程序， Solana 官方提供了一个 SPL Token
    
    </summary>
    
    
      <category term="web3" scheme="http://www.xefan.com/tags/web3/"/>
    
      <category term="solana" scheme="http://www.xefan.com/tags/solana/"/>
    
  </entry>
  
  <entry>
    <title>Solana Rust 开发环境搭建</title>
    <link href="http://www.xefan.com//archives/84186.html"/>
    <id>http://www.xefan.com//archives/84186.html</id>
    <published>2026-01-19T11:07:07.000Z</published>
    <updated>2026-02-04T02:18:23.457Z</updated>
    
    <content type="html"><![CDATA[<h2 id="开发环境安装"><a href="#开发环境安装" class="headerlink" title="开发环境安装"></a>开发环境安装</h2><h3 id="rust"><a href="#rust" class="headerlink" title="rust"></a>rust</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --proto <span class="string">&#x27;=https&#x27;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y</span><br></pre></td></tr></table></figure>
<p>我是用的 <a href="https://github.com/jdx/mise">mise</a> 工具来安装的<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mise install rust@1.92.0</span><br></pre></td></tr></table></figure></p>
<h3 id="surfpool"><a href="#surfpool" class="headerlink" title="surfpool"></a>surfpool</h3><p>solana-test-validator 的一个替代方案，用于在本地运行开发测试链。<br><a href="https://github.com/txtx/surfpool">https://github.com/txtx/surfpool</a></p>
<h3 id="Anchor-CLI-可选"><a href="#Anchor-CLI-可选" class="headerlink" title="Anchor CLI (可选)"></a>Anchor CLI (可选)</h3><p>solana 的一个开发框架，也可以直接使用 rust 的 原生 sdk 进行开发。<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo install anchor-cli</span><br></pre></td></tr></table></figure></p>
<h3 id="Solana-CLI"><a href="#Solana-CLI" class="headerlink" title="Solana CLI"></a>Solana CLI</h3><p>用于构建、部署程序<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c <span class="string">&quot;<span class="subst">$(curl -sSfL https://release.anza.xyz/stable/install)</span>&quot;</span></span><br></pre></td></tr></table></figure></p>
<p>然后还需要配置一下环境变量<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;<span class="variable">$HOME</span>/.local/share/solana/install/active_release/bin:<span class="variable">$PATH</span>&quot;</span></span><br></pre></td></tr></table></figure></p>
<p>查看当前的账号：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana address</span><br></pre></td></tr></table></figure></p>
<p>若当前还没有账号，则需要先创建一个：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana-keygen new -o ~/.config/solana/id.json</span><br></pre></td></tr></table></figure></p>
<p>配置当前使用本地网络：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana config <span class="built_in">set</span> --url http://localhost:8899</span><br></pre></td></tr></table></figure></p>
<p>查询当前的配置：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana config get</span><br></pre></td></tr></table></figure></p>
<p>给自己的账户空投一些 SOL 用于后续的测试:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana airdrop 10</span><br></pre></td></tr></table></figure></p>
<h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cargo new --lib &lt;name&gt;</span><br><span class="line"><span class="built_in">cd</span> &lt;name&gt;</span><br><span class="line">cargo add pinocchio</span><br></pre></td></tr></table></figure>
<p>修改 <code>Cargo.toml</code>，添加内容：<br><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[package]</span></span><br><span class="line"><span class="attr">edition</span> = <span class="string">&quot;2021&quot;</span>       <span class="comment"># &lt;-- 这个要改为 2021</span></span><br><span class="line"></span><br><span class="line"><span class="section">[lib]</span></span><br><span class="line"><span class="attr">crate-type</span> = [<span class="string">&quot;cdylib&quot;</span>, <span class="string">&quot;lib&quot;</span>]</span><br></pre></td></tr></table></figure></p>
<p>然后修改 <code>src/lib.rs</code>，编写程序逻辑。</p>
<h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo build-bpf</span><br></pre></td></tr></table></figure>
<p>编译成功会生成 target/deploy/xxx.so、 target/deploy/xxx.json 私钥两个文件</p>
<h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">solana program deploy target/deploy/&lt;name&gt;.so</span><br></pre></td></tr></table></figure>
<p>执行成功会输出对应的 Program Id 和 Transaction Id</p>
]]></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;h3 id=&quot;rust&quot;&gt;&lt;a href=&quot;#rust&quot; class=&quot;headerlink&quot; title=&quot;r
    
    </summary>
    
    
      <category term="web3" scheme="http://www.xefan.com/tags/web3/"/>
    
      <category term="solana" scheme="http://www.xefan.com/tags/solana/"/>
    
  </entry>
  
  <entry>
    <title>Arbitrum Stylus 开发环境搭建</title>
    <link href="http://www.xefan.com//archives/84185.html"/>
    <id>http://www.xefan.com//archives/84185.html</id>
    <published>2026-01-14T09:44:17.000Z</published>
    <updated>2026-02-04T02:09:53.963Z</updated>
    
    <content type="html"><![CDATA[<h2 id="开发环境安装"><a href="#开发环境安装" class="headerlink" title="开发环境安装"></a>开发环境安装</h2><h3 id="rust"><a href="#rust" class="headerlink" title="rust"></a>rust</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --proto <span class="string">&#x27;=https&#x27;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y</span><br></pre></td></tr></table></figure>
<p>我是用的 <a href="https://github.com/jdx/mise">mise</a> 工具来安装的<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mise install rust@1.92.0</span><br></pre></td></tr></table></figure></p>
<h3 id="Stylus-CLI"><a href="#Stylus-CLI" class="headerlink" title="Stylus CLI"></a>Stylus CLI</h3><p>Arbitrum 的开发框架<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo install cargo-stylus</span><br></pre></td></tr></table></figure></p>
<p>因为 Arbitrum 的合约程序最终会被编译为 webassembly，所以还需要安装 wasm 相关的编译工具</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rustup target add wasm32-unknown-unknown</span><br></pre></td></tr></table></figure>
<h3 id="foundry"><a href="#foundry" class="headerlink" title="foundry"></a>foundry</h3><p>foundry 是一个 EVM 智能合约开发测试的工具。<br>下载 foundry 相关的命令行工具： <a href="https://github.com/foundry-rs/foundry/releases">https://github.com/foundry-rs/foundry/releases</a></p>
<h3 id="solc"><a href="#solc" class="headerlink" title="solc"></a>solc</h3><p>下载 solc 的二进制文件： <a href="https://github.com/argotorg/solidity/releases">https://github.com/argotorg/solidity/releases</a></p>
<h3 id="本地开发结点"><a href="#本地开发结点" class="headerlink" title="本地开发结点"></a>本地开发结点</h3><p>需要先安装 docker 环境<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/OffchainLabs/nitro-devnode.git</span><br><span class="line"><span class="built_in">cd</span> nitro-devnode</span><br><span class="line">./run-dev-node.sh</span><br></pre></td></tr></table></figure></p>
<p>本地结点的账号、密钥：<br><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">Address: 0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E</span><br><span class="line">Private key: 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659</span><br></pre></td></tr></table></figure></p>
<p>如果想要外部也能够连接本地的结点，那么需要修改一个 <code>run-dev-node.sh</code> 这个脚本，在 <code>docker run</code> 这条命令后面再加上 <code>--http.vhosts=*</code> 参数。</p>
<h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus new &lt;name&gt;</span><br></pre></td></tr></table></figure>
<h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus build</span><br></pre></td></tr></table></figure>
<p>编译成功会生成 target/wasm32-unknown-unknown/release/xxx.wasm 文件</p>
<h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><p>验证是否能够部署<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus check -e http://127.0.0.1:8547</span><br></pre></td></tr></table></figure></p>
<p>仅计算部署所需的 gas 费<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus deploy \</span><br><span class="line">  --endpoint=http://127.0.0.1:8547 \</span><br><span class="line">  --private-key=&lt;YOUR_PRIVATE_KEY&gt; \</span><br><span class="line">  --estimate-gas-only</span><br></pre></td></tr></table></figure></p>
<p>部署合约<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus deploy \</span><br><span class="line">  --endpoint=http://127.0.0.1:8547 \</span><br><span class="line">  --private-key=&lt;YOUR_PRIVATE_KEY&gt;</span><br></pre></td></tr></table></figure></p>
<p>导出合约 contract ABI，需要先安装 solc<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo stylus export-abi --output=./abi.json --json</span><br></pre></td></tr></table></figure></p>
<h3 id="合约测试"><a href="#合约测试" class="headerlink" title="合约测试"></a>合约测试</h3><p>调用合约程序查询数据：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cast call --rpc-url http://127.0.0.1:8547  &lt;合约地址&gt; <span class="string">&quot;合约函数签名&quot;</span> [&lt;函数参数&gt;]</span><br></pre></td></tr></table></figure>
<p>例如，对于一个 ERC20 的程序，查询账户余额：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cast call --rpc-url <span class="variable">$ARB_RPC_URL</span> <span class="variable">$CONTRACT_TOKEN</span> <span class="string">&quot;balanceOf(address)(uint256)&quot;</span> <span class="variable">$PUBLIC_KEY</span></span><br></pre></td></tr></table></figure></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">cast send --rpc-url http://127.0.0.1:8547 --private-key &lt;自己的私钥&gt; &lt;合约地址&gt; <span class="string">&quot;合约函数签名&quot;</span> [&lt;函数参数&gt;]</span><br></pre></td></tr></table></figure>
<p>例如，一个 ERC20 的转账操作：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cast send --rpc-url <span class="variable">$ARB_RPC_URL</span> --private-key <span class="variable">$PRIVATE_KEY</span> <span class="variable">$CONTRACT_TOKEN</span> <span class="string">&quot;transfer(address, uint256)&quot;</span> <span class="variable">$TARGET_ADDRESS</span> <span class="variable">$AMOUNT</span></span><br></pre></td></tr></table></figure></p>
]]></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;h3 id=&quot;rust&quot;&gt;&lt;a href=&quot;#rust&quot; class=&quot;headerlink&quot; title=&quot;r
    
    </summary>
    
    
      <category term="web3" scheme="http://www.xefan.com/tags/web3/"/>
    
      <category term="evm" scheme="http://www.xefan.com/tags/evm/"/>
    
  </entry>
  
  <entry>
    <title>Bitbucket Runner 配置</title>
    <link href="http://www.xefan.com//archives/84184.html"/>
    <id>http://www.xefan.com//archives/84184.html</id>
    <published>2023-10-14T20:45:42.000Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>以前有介绍过 Github Runner 和 Gitlab Runner 的配置，现在再来介绍一下 Bitbucket Runner 的配置。</p>
<p>参考官方文档： <a href="https://support.atlassian.com/bitbucket-cloud/docs/runners/">https://support.atlassian.com/bitbucket-cloud/docs/runners/</a></p>
<h2 id="添加-Runner"><a href="#添加-Runner" class="headerlink" title="添加 Runner"></a>添加 Runner</h2><p>首先禁用 Linux 系统的 swap 功能。若不禁用 swap，在 runner 进行 builder 时会使用 swap 内存，可能会出现内存不足的错误。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo swapoff -sv</span><br></pre></td></tr></table></figure>
<p>编辑 <code>/etc/sysctl.conf</code> 添加 <code>vm.swappiness = 1</code> 再重启系统。</p>
<p>接着在 Bitbucket 的设置界面添加 Runner，然后执行界面给出的命令。<br>然后在目标机器上执行命令：</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">docker run-it -d \</span><br><span class="line">  --name bitbucket-runner \</span><br><span class="line">  -v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">  -e ACCOUNT_UUID=&lt;accountUuid&gt; \</span><br><span class="line">  -e REPOSITORY_UUID=&lt;repositoryUuid&gt; \</span><br><span class="line">  -e RUNNER_UUID=&lt;runnerUuid&gt; \</span><br><span class="line">  -e OAUTH_CLIENT_ID=&lt;OAuthClientId&gt; \</span><br><span class="line">  -e OAUTH_CLIENT_SECRET=&lt;OAuthClientSecret&gt; \</span><br><span class="line">  wusuopu/bitbucket-pipelines-runner:1.512</span><br></pre></td></tr></table></figure>
<h2 id="使用-Runner"><a href="#使用-Runner" class="headerlink" title="使用 Runner"></a>使用 Runner</h2><p>在 <code>bitbucket.pipelines.yml</code> 中的相关步骤配置 <code>runs-on</code>，如：<br><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">pipelines:</span><br><span class="line">  default:</span><br><span class="line">      - step:</span><br><span class="line">          runs-on:</span><br><span class="line">            - self.hosted</span><br><span class="line">            - linux.shell</span><br><span class="line">          script:</span><br></pre></td></tr></table></figure></p>
<p>因为 runner 是以 linux-shell 的方式运行的，所以会有一些限制： <a href="https://support.atlassian.com/bitbucket-cloud/docs/set-up-runners-for-linux-shell/">https://support.atlassian.com/bitbucket-cloud/docs/set-up-runners-for-linux-shell/</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;以前有介绍过 Github Runner 和 Gitlab Runner 的配置，现在再来介绍一下 Bitbucket Runner 的配置。&lt;/p&gt;
&lt;p&gt;参考官方文档： &lt;a href=&quot;https://support.atlassian.com/bitbucket-c
    
    </summary>
    
    
      <category term="git" scheme="http://www.xefan.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>记录在docker中运行whenever遇到的问题</title>
    <link href="http://www.xefan.com//archives/84183.html"/>
    <id>http://www.xefan.com//archives/84183.html</id>
    <published>2021-09-09T10:12:48.610Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>最近在将一个 rails 的项目移到 docker 方式来部署。这个项目有用到了 whenever 来执行定时任务，这里记录一下在迁移过程中遇到的一些问题。</p>
<p>因为 whenever 是使用系统的 cron 来实现的定时任务，所以直接就在 docker image 内安装一个 cron 即可： <code>apt-get install -y cron</code> 。<br>然后尝试执行命令： <code>whenever -i; crontab -l</code> 看看定时任务都有设置正确。最后在容器启动的时候使用命令 <code>service cron start</code> 一并将 cron 也启动。</p>
<p>至此感觉事情很简单，一切都很顺利。然而过了两天客户反馈说是这两天的定时任务都没有执行。<br>定时任务没执行，那就是 cron 有问题。现在来开始排查问题。</p>
<p>先再安装 syslog： <code>apt-get install -y rsyslog</code>，将 cron 的日志保存下来，方便查找错误。<br>然后往 crontab 中随便添加一条任务，每分钟执行一次命令： <code>date &gt;&gt; /tmp/date.log</code>。</p>
<p>结果1分钟之后 cron 的日志显示有报如下错误：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FAILED to open PAM security session (Cannot make/remove an entry for the specified session)</span><br></pre></td></tr></table></figure></p>
<p>经过搜索知道在 docker 内是没有 session 的，所以 PAM set_loginuid 会失败。需要将 <code>set_loginuid</code> 这行注释掉：<br><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">sed -i &#x27;/^session\s\+required\s\+pam_loginuid.so/c\#session required pam_loginuid.so&#x27; /etc/pam.d/cron</span><br></pre></td></tr></table></figure></p>
<p>重启 cron 之后，<code>date &gt;&gt; /tmp/date.log</code> 定时任务也有正常执行了。</p>
<p>到此感觉问题应该是解决了。然而又过了一天客户还是说定时任务没有执行。<br>看来还是存在问题的，还得接着排查。</p>
<p>whenever 的任务是定时执行一些 rake 任务，然而执行的结果没有任何的日志。<br>于是我就新添加了一条 crontab 任务，将 rake 命令的 stdout 和 stderr 重定向到日志文件中。<br>然后就发现其实是因为在执行 rake 命令时提示 rake 命令不存在，从而导致执行失败的。<br>看来就因为 PATH 环境变量的问题。</p>
<p>在启动 container 时是有设置了一些环境变量的，然而 cron 这个进程并没有继承这些变量。<br>所以现在就需要手动为 cron 再配置环境变量。在启动 cron 之前先执行命令：<br><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">rm /etc/environment</span><br><span class="line">for variable_value in $(cat /proc/1/environ | sed &#x27;s/\x00/\n/g&#x27;); do</span><br><span class="line">  echo $variable_value &gt;&gt; /etc/environment</span><br><span class="line">done</span><br></pre></td></tr></table></figure></p>
<p>现在再测试一切就正常了。<br>至此问题终于是解决了，于是就记录一下方便以后查阅。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近在将一个 rails 的项目移到 docker 方式来部署。这个项目有用到了 whenever 来执行定时任务，这里记录一下在迁移过程中遇到的一些问题。&lt;/p&gt;
&lt;p&gt;因为 whenever 是使用系统的 cron 来实现的定时任务，所以直接就在 docker imag
    
    </summary>
    
      <category term="编程开发" scheme="http://www.xefan.com/categories/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="ruby" scheme="http://www.xefan.com/tags/ruby/"/>
    
      <category term="docker" scheme="http://www.xefan.com/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>Gitlab Runner 配置</title>
    <link href="http://www.xefan.com//archives/84182.html"/>
    <id>http://www.xefan.com//archives/84182.html</id>
    <published>2021-04-13T18:13:48.096Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>前一篇文章有介绍了 Github Runner 的配置，现在再来介绍一下 Gitlab Runner 的配置。</p>
<p>关于添加自己的机器，可参考 Gitlab 官方文档: <a href="https://docs.gitlab.com/runner/register/index.html">https://docs.gitlab.com/runner/register/index.html</a></p>
<p>同样的，为了方便，我们使用 docker 来一键部署。首先到 gitlab 的 CI/CD 项目设置页面，然后查看当前项目的 runner 注册 token。<br>然后执行如下命令进行 runner 注册。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">docker run --rm -it -v $PWD/tmp/gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:alpine register</span><br><span class="line">  --non-interactive \</span><br><span class="line">  --url &quot;https://gitlab.com/&quot; \</span><br><span class="line">  --token &quot;PROJECT_REGISTRATION_TOKEN&quot; \</span><br><span class="line">  --executor &quot;docker&quot; \</span><br><span class="line">  --docker-image alpine:latest \</span><br><span class="line">  --docker-volumes /var/run/docker.sock:/var/run/docker.sock</span><br><span class="line">  --description &quot;docker-runner&quot; \</span><br><span class="line">  --tag-list &quot;docker,linux&quot; \</span><br><span class="line">  --run-untagged=&quot;true&quot; \</span><br><span class="line">  --locked=&quot;false&quot; \</span><br><span class="line">  --access-level=&quot;not_protected&quot;</span><br></pre></td></tr></table></figure>
<p>注意这里的 <code>PROJECT_REGISTRATION_TOKEN</code> 需要替换为对应的 token；<br><code>--run-untagged</code> 要设为 true，才能运行所有的 job。否则就只会执行打了 tag 的 job 。<br>如果需要多个项目共享该 runner，那么 <code>--locked</code> 就设为 false；</p>
<p>注册成功之后会生成 ./tmp/gitlab-runner-config/config.toml 文件：<br><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">concurrent = 1</span><br><span class="line">check_interval = 0</span><br><span class="line"></span><br><span class="line">[session_server]</span><br><span class="line">  session_timeout = 1800</span><br><span class="line"></span><br><span class="line">[[runners]]</span><br><span class="line">  name = &quot;runner-docker-01&quot;</span><br><span class="line">  url = &quot;https://gitlab.com/&quot;</span><br><span class="line">  token = &quot;xxxxxxxxxxxxxxxxxxxx&quot;</span><br><span class="line">  executor = &quot;docker&quot;</span><br><span class="line">  [runners.custom_build_dir]</span><br><span class="line">  [runners.cache]</span><br><span class="line">    [runners.cache.s3]</span><br><span class="line">    [runners.cache.gcs]</span><br><span class="line">    [runners.cache.azure]</span><br><span class="line">  [runners.docker]</span><br><span class="line">    host = &quot;unix:///var/run/docker.sock&quot;</span><br><span class="line">    tls_verify = false</span><br><span class="line">    image = &quot;alpine&quot;</span><br><span class="line">    privileged = true</span><br><span class="line">    disable_entrypoint_overwrite = false</span><br><span class="line">    oom_kill_disable = false</span><br><span class="line">    disable_cache = false</span><br><span class="line">    volumes = [&quot;/var/run/docker.sock:/var/run/docker.sock&quot;, &quot;/cache&quot;]</span><br><span class="line">    shm_size = 0</span><br></pre></td></tr></table></figure></p>
<p>如果更换服务器了，但是还想要继续使用该 runner 的配置，只需要保留该配置文件即可。</p>
<p>以上是 runner 注册成功，接下来就使用该配置文件来启动 runner 服务吧。<br>同样也是为了安全起见，我们使用 docker-in-docker 的方式来部署。<br>创建一个 docker-compose.yml 文件：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;2&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">dind:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker:19.03-dind</span></span><br><span class="line">    <span class="attr">privileged:</span> <span class="literal">true</span></span><br><span class="line">    <span class="comment"># 使用国内docker镜像源</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;--registry-mirror&quot;</span>, <span class="string">&quot;https://hub-mirror.c.163.com&quot;</span>]</span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-lib:/var/lib/docker</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-run:/var/run</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">gitlab-runner:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">gitlab/gitlab-runner:alpine</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">dind</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-run:/var/run</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/gitlab-runner-config:/etc/gitlab-runner</span></span><br></pre></td></tr></table></figure>
<p>然后执行命令 <code>docker-compose up -d</code> 启动服务即可。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;前一篇文章有介绍了 Github Runner 的配置，现在再来介绍一下 Gitlab Runner 的配置。&lt;/p&gt;
&lt;p&gt;关于添加自己的机器，可参考 Gitlab 官方文档: &lt;a href=&quot;https://docs.gitlab.com/runner/registe
    
    </summary>
    
      <category term="编程开发" scheme="http://www.xefan.com/categories/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="git" scheme="http://www.xefan.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Github Runner 配置</title>
    <link href="http://www.xefan.com//archives/84181.html"/>
    <id>http://www.xefan.com//archives/84181.html</id>
    <published>2020-11-23T09:00:58.877Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的文章<a href="/archives/84178.html">《Github Actions使用》</a>有介绍了Github Actions的使用方法。<br>之前是直接的 Github 官方提供的 runner，来执行。不过官方提供的 runner 多少还是有些限制。<br>这次我们来尝试将自己的 server 配置成 runner 来执行 actions。</p>
<p>关于添加自己的机器，可参考 Github 官方文档： <a href="https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/adding-self-hosted-runners">https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/adding-self-hosted-runners</a></p>
<p>在 github 中 runner 分为三个级别： repository, organization, enterprise。<br>其中 repository 级别的 runner 就只能在这个代码仓库下使用；<br>organization 和 enterprise 级别的 runner 可以在这个 organization 或者 enterprise 下的所有代码仓库中共享。</p>
<p>由于我是免费用户，这里我就以 organization 为例吧。另外为了方便我们就直接通过 docker 来运行。</p>
<p>参考官方文档，首先进入到 organization 的 actions 设置页面，然后选择添加一个 runner。没有 organization 的可以自己创建一个。<br>接着在自己的 server 上执行命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">    -v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">    -e GH_REPOSITORY=xxxxxxxxxxxx \</span><br><span class="line">    -e GH_RUNNER_TOKEN=xxxxxxxxxxxxx \</span><br><span class="line">    -e GH_RUNNER_LABELS=label1,label2 \</span><br><span class="line">    wusuopu/github-actions-runner:2.273.6</span><br></pre></td></tr></table></figure>
<p>其中 GH_REPOSITORY 就是设置页面上显示的 url 参数，GH_RUNNER_TOKEN 就是 token 参数。GH_RUNNER_LABELS 则根据自己的需要来为该 runner 设置标签。</p>
<p>至此，自己的 runner 就已经配置好了。不过这里我们是直接在这个 runner 的 container 中操作 server 上的 docker 服务。这样其实是非常危险的，尤其是对于公有仓库来说。</p>
<p>为了安全起见，我们可以再运行一个 Docker-in-Docker 的服务，然后这个就只需要连接到该服务即可。<br>创建一个 docker-compose.yml 文件：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;2&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">dind:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker:19.03-dind</span></span><br><span class="line">    <span class="attr">privileged:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-lib:/var/lib/docker</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-run:/var/run</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">github-runner:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">wusuopu/github-actions-runner:2.273.6</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">dind</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GH_REPOSITORY=https://github.com/xxxx</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GH_RUNNER_TOKEN=xxxxxxxxxxxxx</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GH_RUNNER_LABELS=dind</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./tmp/docker-run:/var/run</span></span><br></pre></td></tr></table></figure>
<p>然后执行命令 <code>docker-compose up -d</code> 启动服务即可。<br>如果之后 actions 任务太多处理不过来，可以将 runner 扩容，多增加几个 runner 实例： <code>docker-compose scale github-runner=3</code></p>
<p>至此，我们自己的 runner 就配置好了。如果想要在 actions 中使用我们自己的 runner，则需要修改之前的配置文件 <code>.github/workflows/main.yml</code>。将 <code>runs-on: ubuntu-latest</code> 改为 <code>runs-on: self-hosted</code> 。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在之前的文章&lt;a href=&quot;/archives/84178.html&quot;&gt;《Github Actions使用》&lt;/a&gt;有介绍了Github Actions的使用方法。&lt;br&gt;之前是直接的 Github 官方提供的 runner，来执行。不过官方提供的 runner 多少还是
    
    </summary>
    
      <category term="编程开发" scheme="http://www.xefan.com/categories/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="git" scheme="http://www.xefan.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>使用python编写帝国时代2修改器</title>
    <link href="http://www.xefan.com//archives/84180.html"/>
    <id>http://www.xefan.com//archives/84180.html</id>
    <published>2020-05-14T10:12:42.474Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>之前的文章有介绍了<a href="/archives/84171.html">《使用python编写游戏修改器》</a>。<br>最近怀旧一下，在玩《帝国时代2》，然后就尝试着再写一个修改器试试。</p>
<h2 id="查找内存地址"><a href="#查找内存地址" class="headerlink" title="查找内存地址"></a>查找内存地址</h2><p>首先用 Cheat Engine 来修改资源，以及查找资源的内存地址。注意这里的数据都要使用浮点数类型。<br>如图找到的食物内存地址是 0F2B93B0<br><img src="/wp-content/uploads/2020/05/age2_ce01.jpg" alt="age2_ce01"></p>
<p>通过逐步分析得到以下指标的内存地址为：<br>木材地址 = 食物地址 + 4<br>石头地址 = 食物地址 + 8<br>黄金地址 = 食物地址 + 12<br>黄金地址 = 食物地址 + 12<br>人口上限地址 = 食物地址 + 16<br>当前人口地址 = 食物地址 + 44</p>
<p>如果想要实际无限人口，只需要修改 人口上限 和 当前人口 这两个数据即可。<br>这些地址都找到之后，然后就开始写代码，使用 Python 调用 <code>WriteProcessMemory</code> 来修改游戏的内存。<br>然而在打完这一局之后开启新的游戏时却发现这些地址全都变了。<br>每次开启新的游戏时都需要重新查找一遍地址内存，这个就有点麻烦了。</p>
<h2 id="查找游戏基址"><a href="#查找游戏基址" class="headerlink" title="查找游戏基址"></a>查找游戏基址</h2><p>通过分析发现这些地址都全是动态地址，每次游戏时都不一样。根据仅记得的一点汇编知识知道，这些动态地址是通过基址加偏移地址得到的。所以现在一劳永逸的方法是找到基址地址。而基址是静态的。然后接下来逐步进行分析。</p>
<p>首先刚刚食物地址是0F2B93B0。游戏程序如果要从这个地址获取食物数据，那么就得在某个地方储存这个地址值。这里我们就先将这个地址称为<code>食物地址指针</code>。接着用 CE 查找一下 0F2B93B0 这个值，然后就找到了<code>食物地址指针</code>地址为 206221B8。如图：<br><img src="/wp-content/uploads/2020/05/age2_ce02.jpg" alt="age2_ce02"><br>注意这里要用 16 进制的方式来查找这个地址值。如果用 C 语言代码的描述相当于：<br><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> food_point = <span class="number">0x206221B8</span>       <span class="comment">// 食物地址指针</span></span><br><span class="line"><span class="type">int</span> food_address = *food_point;   <span class="comment">// 食物地址指针 指向食物地址 0x0F2B93B0</span></span><br><span class="line"><span class="type">int</span> food = *food_point;           <span class="comment">// 从食物地址 0x0F2B93B0 得到食物的值</span></span><br></pre></td></tr></table></figure></p>
<p>经过测试发现这个<code>食物地址指针</code>地址也是动态的。没办法了，只能继续往下找。现在来看看都有谁访问了这个地址。如图：<br><img src="/wp-content/uploads/2020/05/age2_ce03.jpg" alt="age2_ce03"><br><img src="/wp-content/uploads/2020/05/age2_ce04.jpg" alt="age2_ce04"><br>这里我们又得到了一个地址： 20622110 。再来看看是哪里记录了这个地址呢。如图：<br><img src="/wp-content/uploads/2020/05/age2_ce05.jpg" alt="age2_ce05"><br>这里我们又找到了 007A5FEC 这个地址，运气不错。在 CE 中显示为绿色，表示它是一个静态地址。这表示它就是我们要找的基址了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>现在来总结一下。使用 C 语言代码描述如下：<br><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> base_addr = <span class="number">0x007A5FEC</span>;             <span class="comment">// 游戏基址</span></span><br><span class="line"><span class="type">int</span> food_point = *base_addr + <span class="number">0xA8</span>;     <span class="comment">// 基址记录的值0x20622110 + 偏移地址0xA8 = 食物地址指针</span></span><br><span class="line"><span class="type">int</span> food_address = *food_point;         <span class="comment">// 得到食物地址</span></span><br><span class="line"><span class="type">int</span> food = *food_address;               <span class="comment">// 从食物地址 0x0F2B93B0 得到食物的值</span></span><br></pre></td></tr></table></figure></p>
<p>最终修改的结果如图：<br><img src="/wp-content/uploads/2020/05/age2_ce06.jpg" alt="age2_ce06"></p>
<p>上面修改器的完整源代码，如有需要可通过以下链接获取：<br><a href="https://github.com/wusuopu/cheat_engine_age2">https://github.com/wusuopu/cheat_engine_age2</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;之前的文章有介绍了&lt;a href=&quot;/archives/84171.html&quot;&gt;《使用python编写游戏修改器》&lt;/a&gt;。&lt;br&gt;最近怀旧一下，在玩《帝国时代2》，然后就尝试着再写一个修改器试试。&lt;/p&gt;
&lt;h2 id=&quot;查找内存地址&quot;&gt;&lt;a href=&quot;#查找内存地址&quot;
    
    </summary>
    
      <category term="Python栏目" scheme="http://www.xefan.com/categories/Python%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="python" scheme="http://www.xefan.com/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>使用Vagrant和Docker搭建Kubernetes集群</title>
    <link href="http://www.xefan.com//archives/84179.html"/>
    <id>http://www.xefan.com//archives/84179.html</id>
    <published>2020-04-03T12:28:20.946Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>在之前的文章<a href="/archives/84177.html">《使用Vagrant在Ubuntu系统上搭建Kubernetes集群》</a>介绍了使用 Vagrant 在 VirtualBox 中安装 Ubuntu 系统搭建 Kubernetes 集群。<br>因为 Vagrant 是支持 Docker 的，所以这篇文章就来尝试不再使用 VirtualBox 了，而是直接使用 Docker 来搭建 Kubernetes 集群。</p>
<h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><p>需要先安装以下程序：</p>
<ul>
<li>Vagrant</li>
<li>Docker</li>
<li>Kubectl</li>
</ul>
<h2 id="运行集群"><a href="#运行集群" class="headerlink" title="运行集群"></a>运行集群</h2><p>因为是在本地学习 k8s，因此为了方便我就使用 Rancher 的 k3s 来进行安装。<br>下载 Vagrant 配置文件： <a href="https://github.com/wusuopu/kubernetes-vagrant-alpine">https://github.com/wusuopu/kubernetes-vagrant-alpine</a></p>
<p>然后执行命令：<br><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">vagrant up --no-parallel --provision</span><br></pre></td></tr></table></figure></p>
<p>这里启动 k3s 服务，并将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来，这样就可以直接在 host 系统中用 kubectl 来操作集群。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">-&gt; % k get node -o wide</span><br><span class="line">NAME        STATUS   ROLES    AGE   VERSION        INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME</span><br><span class="line">alpine-02   Ready    &lt;none&gt;   59m   v1.17.4-k3s1   172.17.0.3    &lt;none&gt;        Alpine Linux v3.11   4.9.184-linuxkit   docker://19.3.8</span><br><span class="line">alpine-01   Ready    master   59m   v1.17.4-k3s1   172.17.0.2    &lt;none&gt;        Alpine Linux v3.11   4.9.184-linuxkit   docker://19.3.8</span><br><span class="line">alpine-03   Ready    &lt;none&gt;   58m   v1.17.4-k3s1   172.17.0.4    &lt;none&gt;        Alpine Linux v3.11   4.9.184-linuxkit   docker://19.3.8</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>这里集群的各个 node 都是一个 docker container。<br><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">-&gt; % docker ps</span><br><span class="line">CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                                                           NAMES</span><br><span class="line">a91d13e2cca0        wusuopu/vagrant:k3s-alpine   &quot;dockerd-entrypoint.…&quot;   About an hour ago   Up 35 minutes       2375-2376/tcp, 6443/tcp, 127.0.0.1:2201-&gt;22/tcp                 kubernetes-vagrant-alpine_alpine-03_1585887389</span><br><span class="line">cce93873b666        wusuopu/vagrant:k3s-alpine   &quot;dockerd-entrypoint.…&quot;   About an hour ago   Up 35 minutes       2375-2376/tcp, 6443/tcp, 127.0.0.1:2200-&gt;22/tcp                 kubernetes-vagrant-alpine_alpine-02_1585887382</span><br><span class="line">d4844843ca3e        wusuopu/vagrant:k3s-alpine   &quot;dockerd-entrypoint.…&quot;   About an hour ago   Up 36 minutes       2375-2376/tcp, 0.0.0.0:6443-&gt;6443/tcp, 127.0.0.1:2222-&gt;22/tcp   kubernetes-vagrant-alpine_alpine-01_1585887365</span><br></pre></td></tr></table></figure></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在之前的文章&lt;a href=&quot;/archives/84177.html&quot;&gt;《使用Vagrant在Ubuntu系统上搭建Kubernetes集群》&lt;/a&gt;介绍了使用 Vagrant 在 VirtualBox 中安装 Ubuntu 系统搭建 Kubernetes 集群。&lt;br&gt;
    
    </summary>
    
      <category term="Linux栏目" scheme="http://www.xefan.com/categories/Linux%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="Docker" scheme="http://www.xefan.com/tags/Docker/"/>
    
      <category term="k8s" scheme="http://www.xefan.com/tags/k8s/"/>
    
  </entry>
  
  <entry>
    <title>Github Actions使用</title>
    <link href="http://www.xefan.com//archives/84178.html"/>
    <id>http://www.xefan.com//archives/84178.html</id>
    <published>2020-02-03T14:35:36.443Z</published>
    <updated>2025-10-22T05:34:37.737Z</updated>
    
    <content type="html"><![CDATA[<p>最近在看 Github Actions，相对一 Gitlab CI 来说，它的配置就没有那么直观了。</p>
<p>以下通过一个例子来说下 Github Actions 的用法。</p>
<p>在项目根目录下创建文件 <code>.github/workflows/main.yml</code></p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">CI</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="comment"># 仅在有以 &#x27;v&#x27; 开头的 tag push 时才会触发</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">tags:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">v*</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">build</span> <span class="string">docker</span> <span class="string">image</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="comment"># 切换到当前的 commit</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">ref:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.ref</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">build</span> <span class="string">docker</span> <span class="string">image</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="attr">canister_user:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.canister_user</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">canister_password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.canister_password</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">docker_image_name:</span> <span class="string">cloud.canister.io:5000/$&#123;&#123;</span> <span class="string">github.repository</span> <span class="string">&#125;&#125;</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        echo $GITHUB_WORKSPACE $GITHUB_REF</span></span><br><span class="line"><span class="string">        cd $GITHUB_WORKSPACE</span></span><br><span class="line"><span class="string">        docker build -t $docker_image_name:$&#123;GITHUB_REF##*/&#125; .</span></span><br><span class="line"><span class="string">        docker login -u $canister_user -p $canister_password cloud.canister.io:5000</span></span><br><span class="line"><span class="string">        docker push $docker_image_name:$&#123;GITHUB_REF##*/&#125;</span></span><br></pre></td></tr></table></figure>
<p>以上的配置执行的操作是，当有新的 ‘v’ 开头的 tag push 时，则 build docker image 并 push 到 cloud.canister.io 的私有 docker registry 上。</p>
<p>这里 canister 的用户名和密码是通过 secrets 来配置的，在 github 的 repository -&gt; Settings -&gt; Secrets 页面。</p>
<p>参考： <a href="https://help.github.com/en/actions">https://help.github.com/en/actions</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近在看 Github Actions，相对一 Gitlab CI 来说，它的配置就没有那么直观了。&lt;/p&gt;
&lt;p&gt;以下通过一个例子来说下 Github Actions 的用法。&lt;/p&gt;
&lt;p&gt;在项目根目录下创建文件 &lt;code&gt;.github/workflows/main
    
    </summary>
    
      <category term="编程开发" scheme="http://www.xefan.com/categories/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="git" scheme="http://www.xefan.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>使用Vagrant在Ubuntu系统上搭建Kubernetes集群</title>
    <link href="http://www.xefan.com//archives/84177.html"/>
    <id>http://www.xefan.com//archives/84177.html</id>
    <published>2019-11-30T15:37:50.127Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><h3 id="Vagrant"><a href="#Vagrant" class="headerlink" title="Vagrant"></a>Vagrant</h3><p>目前 Vagrant (<a href="https://www.vagrantup.com/downloads.html">https://www.vagrantup.com/downloads.html</a>) 最新为 v2.2.6，Kubernetes 为 v1.16</p>
<h3 id="Kubectl"><a href="#Kubectl" class="headerlink" title="Kubectl"></a>Kubectl</h3><p>下载最新版的 kubectl:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">`curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl`</span><br></pre></td></tr></table></figure>
<h3 id="VirtualBox"><a href="#VirtualBox" class="headerlink" title="VirtualBox"></a>VirtualBox</h3><p>需要下载 Vagrant 所兼容的版本( <a href="https://www.vagrantup.com/docs/virtualbox/">https://www.vagrantup.com/docs/virtualbox/</a> )。我是直接用 apt-get 安装 5.2 的版本。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install virtualbox virtualbox-guest-additions-iso</span><br></pre></td></tr></table></figure>
<h2 id="运行-ubuntu-单实例"><a href="#运行-ubuntu-单实例" class="headerlink" title="运行 ubuntu 单实例"></a>运行 ubuntu 单实例</h2><p>创建 Vagrantfile 配置文件，这里在 virtualbox 内运行的是 Ubuntu 18.04。<br>若 Vagrant 的 Box 文件下载太慢，可先提前下载好：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wget https://app.vagrantup.com/ubuntu/boxes/bionic64/versions/20191125.0.0/providers/virtualbox.box</span><br><span class="line"></span><br><span class="line">cat &gt; virtualbox.json &lt;EOF</span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;ubuntu/bionic64&quot;,</span><br><span class="line">  &quot;versions&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;version&quot;: &quot;20191125.0.0&quot;,</span><br><span class="line">      &quot;providers&quot;: [</span><br><span class="line">        &#123;</span><br><span class="line">          &quot;name&quot;: &quot;virtualbox&quot;,</span><br><span class="line">          &quot;url&quot;: &quot;virtualbox.box&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">vagrant box add ubuntu/bionic64 virtualbox.json</span><br></pre></td></tr></table></figure></p>
<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- mode: ruby -*-</span></span><br><span class="line"><span class="comment"># vi: set ft=ruby :</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$num_instances</span> = <span class="number">1</span></span><br><span class="line"><span class="variable">$instance_name_prefix</span> = <span class="string">&quot;ubuntu&quot;</span></span><br><span class="line"><span class="variable">$vm_gui</span> = <span class="literal">false</span></span><br><span class="line"><span class="variable">$vm_memory</span> = <span class="number">1024</span></span><br><span class="line"><span class="variable">$vm_cpus</span> = <span class="number">1</span></span><br><span class="line"><span class="variable">$vb_cpuexecutioncap</span> = <span class="number">100</span></span><br><span class="line">ip_prefix = <span class="string">&#x27;172.17.8.&#x27;</span></span><br><span class="line"></span><br><span class="line">Vagrant.configure(<span class="number">2</span>) <span class="keyword">do</span> |<span class="params">config</span>|</span><br><span class="line">  (<span class="number">1</span>..<span class="variable">$num_instances</span>).each <span class="keyword">do</span> |<span class="params">i</span>|</span><br><span class="line">    config.vm.define vm_name = <span class="string">&quot;%s-%02d&quot;</span> % [<span class="variable">$instance_name_prefix</span>, i] <span class="keyword">do</span> |<span class="params">node</span>|</span><br><span class="line">      node.vm.box = <span class="string">&#x27;ubuntu/bionic64&#x27;</span></span><br><span class="line">      node.vm.box_version = <span class="string">&quot;20191125.0.0&quot;</span></span><br><span class="line">      node.vm.box_check_update = <span class="literal">false</span></span><br><span class="line">      node.vm.hostname = vm_name</span><br><span class="line">      node.vm.synced_folder <span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;/vagrant&#x27;</span>, <span class="symbol">type:</span> <span class="string">&#x27;virtualbox&#x27;</span></span><br><span class="line"></span><br><span class="line">      ip = <span class="string">&quot;<span class="subst">#&#123;ip_prefix&#125;</span><span class="subst">#&#123;i+<span class="number">100</span>&#125;</span>&quot;</span></span><br><span class="line">      node.vm.network <span class="string">&#x27;private_network&#x27;</span>, <span class="symbol">ip:</span> ip</span><br><span class="line">      node.vm.provider <span class="symbol">:virtualbox</span> <span class="keyword">do</span> |<span class="params">vb</span>|</span><br><span class="line">        vb.gui = <span class="variable">$vm_gui</span></span><br><span class="line">        vb.memory = <span class="variable">$vm_memory</span></span><br><span class="line">        vb.cpus = <span class="variable">$vm_cpus</span></span><br><span class="line">        vb.customize [<span class="string">&quot;modifyvm&quot;</span>, <span class="symbol">:id</span>, <span class="string">&quot;--cpuexecutioncap&quot;</span>, <span class="string">&quot;<span class="subst">#&#123;<span class="variable">$vb_cpuexecutioncap</span>&#125;</span>&quot;</span>]</span><br><span class="line">      <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>接着启动 ubuntu</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">vagrant up</span><br></pre></td></tr></table></figure>
<p>登录 ubuntu 实例</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">vagrant ssh</span><br></pre></td></tr></table></figure>
<p>至此 ubuntu 就是运行起来了。然后却只有一个 实例，但是在实际应用中可能是多个实例组成的集群。</p>
<h2 id="运行-ubuntu-多实例"><a href="#运行-ubuntu-多实例" class="headerlink" title="运行 ubuntu 多实例"></a>运行 ubuntu 多实例</h2><p>先停止刚刚的实例</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vagrant halt</span><br></pre></td></tr></table></figure>
<p>然后修改 Vagrantfile 文件，将 $num_instances 变量改为3。这里我就启动3个实例来组成集群。<br>接着启动这3个实例</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">vagrant up</span><br></pre></td></tr></table></figure>
<p>查看实例状态</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">-&gt; % vagrant status</span><br><span class="line">Current machine states:</span><br><span class="line"></span><br><span class="line">ubuntu-01                 running (virtualbox)</span><br><span class="line">ubuntu-02                 running (virtualbox)</span><br><span class="line">ubuntu-03                 running (virtualbox)</span><br><span class="line"></span><br><span class="line">This environment represents multiple VMs. The VMs are all listed</span><br><span class="line">above with their current state. For more information about a specific</span><br><span class="line">VM, run `vagrant status NAME`.</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">vagrant ssh ubuntu-02</span><br></pre></td></tr></table></figure>
<h2 id="安装-Kubernetes"><a href="#安装-Kubernetes" class="headerlink" title="安装 Kubernetes"></a>安装 Kubernetes</h2><p>目前的3个 ubuntu 实例如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">172.17.8.101 (ubuntu-01)    master</span><br><span class="line">172.17.8.102 (ubuntu-02)    worker1</span><br><span class="line">172.17.8.103 (ubuntu-03)    worker2</span><br></pre></td></tr></table></figure>
<p>因为是在本地学习 k8s，因此为了方便我就使用 Rancher 的 k3s 来进行安装。<br>先分别在各个实例中安装 docker，参考： <a href="https://docs.docker.com/install/linux/docker-ce/ubuntu/">https://docs.docker.com/install/linux/docker-ce/ubuntu/</a></p>
<p>先安装  master node</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">vagrant ssh ubuntu-01</span><br><span class="line">curl -sfL https://get.k3s.io | sh -</span><br></pre></td></tr></table></figure>
<p>接着分别进入另外两个实例内安装 worker node，其中 K3S_TOKEN 的值来自 /var/lib/rancher/k3s/server/node-token 文件。</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">vagrant ssh ubuntu-02</span><br><span class="line">curl -sfL https://get.k3s.io | K3S_URL=https://172.17.8.101:6443 K3S_TOKEN=XXX sh -</span><br></pre></td></tr></table></figure>
<p>最后可以将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来，这样就可以直接在 host 系统中用 kubectl 来操作集群。</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">-&gt; % k get nodes -o wide</span><br><span class="line">NAME        STATUS   ROLES    AGE     VERSION         INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME</span><br><span class="line">ubuntu-03   Ready    &lt;none&gt;   4m57s   v1.16.3-k3s.2   172.17.8.103   &lt;none&gt;        Ubuntu 18.04.3 LTS   4.15.0-70-generic   docker://19.3.5</span><br><span class="line">ubuntu-02   Ready    &lt;none&gt;   7m25s   v1.16.3-k3s.2   172.17.8.102   &lt;none&gt;        Ubuntu 18.04.3 LTS   4.15.0-70-generic   docker://19.3.5</span><br><span class="line">ubuntu-01   Ready    master   8m5s    v1.16.3-k3s.2   172.17.8.101   &lt;none&gt;        Ubuntu 18.04.3 LTS   4.15.0-70-generic   docker://19.3.5</span><br></pre></td></tr></table></figure>
<p>当前安装的 k3s 是 v1.0.0，k8s 是 v1.16.3</p>
<p>最后我将完整的配置已经上传到 github 上了。大家有需要可以直接执行如下命令即可：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/wusuopu/kubernetes-vagrant-ubuntu</span><br><span class="line">cd kubernetes-vagrant-ubuntu</span><br><span class="line">vagrant up</span><br></pre></td></tr></table></figure>
]]></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;h3 id=&quot;Vagrant&quot;&gt;&lt;a href=&quot;#Vagrant&quot; class=&quot;headerlink&quot; title=&quot;Vag
    
    </summary>
    
      <category term="Linux栏目" scheme="http://www.xefan.com/categories/Linux%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="Docker" scheme="http://www.xefan.com/tags/Docker/"/>
    
      <category term="k8s" scheme="http://www.xefan.com/tags/k8s/"/>
    
  </entry>
  
  <entry>
    <title>使用TypeScript开发Express应用</title>
    <link href="http://www.xefan.com//archives/84176.html"/>
    <id>http://www.xefan.com//archives/84176.html</id>
    <published>2019-11-04T11:01:06.658Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<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></pre></td><td class="code"><pre><span class="line">yarn init</span><br></pre></td></tr></table></figure>
<h2 id="然后安装-express"><a href="#然后安装-express" class="headerlink" title="然后安装 express"></a>然后安装 express</h2><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">yarn add express</span><br><span class="line">yarn add @types/express @types/node --dev</span><br></pre></td></tr></table></figure>
<h2 id="安装-TypeScript"><a href="#安装-TypeScript" class="headerlink" title="安装 TypeScript"></a>安装 TypeScript</h2><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">yarn add --dev typescript</span><br></pre></td></tr></table></figure>
<h2 id="配置-webpack"><a href="#配置-webpack" class="headerlink" title="配置 webpack"></a>配置 webpack</h2><p>这是最麻烦的一步。先安装 webpack:</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">yarn add --dev webpack webpack-cli webpack-node-externals ts-loader</span><br><span class="line">yarn add --dev nodemon webpack-shell-plugin</span><br></pre></td></tr></table></figure>
<p>添加 <code>webpack.config.js</code>:<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">const path = require(&#x27;path&#x27;);</span><br><span class="line">const nodeExternals = require(&#x27;webpack-node-externals&#x27;);</span><br><span class="line">const WebpackShellPlugin = require(&#x27;webpack-shell-plugin&#x27;);</span><br><span class="line"></span><br><span class="line">const &#123; NODE_ENV = &#x27;production&#x27; &#125; = process.env;</span><br><span class="line">const rootPath = path.resolve(__dirname)</span><br><span class="line"></span><br><span class="line">let plugins = []</span><br><span class="line">if (NODE_ENV === &#x27;development&#x27;) &#123;</span><br><span class="line">  plugins.push(new WebpackShellPlugin(&#123;</span><br><span class="line">    onBuildEnd: [&#x27;yarn run:dev&#x27;]</span><br><span class="line">  &#125;))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">module.exports = &#123;</span><br><span class="line">  entry: path.resolve(rootPath, &#x27;./src/index.ts&#x27;),</span><br><span class="line">  mode: NODE_ENV,</span><br><span class="line">  target: &#x27;node&#x27;,</span><br><span class="line">  watch: NODE_ENV === &#x27;development&#x27;,</span><br><span class="line">  externals: [ nodeExternals() ],</span><br><span class="line">  output: &#123;</span><br><span class="line">    path: path.resolve(rootPath, &#x27;build&#x27;, NODE_ENV === &#x27;development&#x27; ? &#x27;dev&#x27; : &#x27;prod&#x27;),</span><br><span class="line">    filename: &#x27;index.js&#x27;</span><br><span class="line">  &#125;,</span><br><span class="line">  resolve: &#123;</span><br><span class="line">    extensions: [&#x27;.ts&#x27;, &#x27;.js&#x27;],</span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: plugins,</span><br><span class="line">  module: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: /\.ts$/,</span><br><span class="line">        use: [</span><br><span class="line">          &#x27;ts-loader&#x27;,</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>添加 <code>tsconfig.json</code>:<br><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">&#123;</span><br><span class="line">  &quot;compilerOptions&quot;: &#123;</span><br><span class="line">    &quot;module&quot;: &quot;commonjs&quot;,</span><br><span class="line">    &quot;target&quot;: &quot;es5&quot;,</span><br><span class="line">    &quot;noImplicitAny&quot;: false,</span><br><span class="line">    &quot;sourceMap&quot;: true,</span><br><span class="line">    &quot;outDir&quot;: &quot;build&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;exclude&quot;: [</span><br><span class="line">    &quot;node_modules&quot;,</span><br><span class="line">    &quot;build&quot;,</span><br><span class="line">    &quot;config&quot;,</span><br><span class="line">    &quot;release&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在 <code>package.json</code> 中添加相关命令:<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">  &quot;start&quot;: &quot;node build/prod/index.js&quot;,</span><br><span class="line">  &quot;start:dev&quot;: &quot;NODE_ENV=development node node_modules/webpack/bin/webpack.js --config webpack.config.js&quot;,</span><br><span class="line">  &quot;build&quot;: &quot;NODE_ENV=production node node_modules/webpack/bin/webpack.js --config webpack.config.js&quot;,</span><br><span class="line">  &quot;run:dev&quot;: &quot;NODE_ENV=development nodemon build/dev/index.js&quot;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这样在开发过程只需要执行 <code>yarn run start:dev</code> 启动开发服务器即可，在代码有改动之后会自己重新 build 并重启服务。<br>开发完成之后执行 <code>yarn build</code> 进行打包发布。</p>
<h2 id="使用-jest-进行测试-可选"><a href="#使用-jest-进行测试-可选" class="headerlink" title="使用 jest 进行测试(可选)"></a>使用 jest 进行测试(可选)</h2><p>安装 jest：<br><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">yarn add -D jest @types/jest ts-jest supertest</span><br></pre></td></tr></table></figure></p>
<p>添加 <code>jest.config.js</code>:<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">module.exports = &#123;</span><br><span class="line">  testEnvironment: &#x27;node&#x27;,</span><br><span class="line">  transform: &#123;</span><br><span class="line">    &quot;^.+\\.ts$&quot;: &quot;ts-jest&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>相应的测试文件的文件名以 <code>.test.ts</code> 结尾。</p>
<p>然后在 <code>package.json</code> 中添加相关命令：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">  &quot;test&quot;: &quot;jest&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>最终项目目录结构如下：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── jest.config.js</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│   └── index.ts</span><br><span class="line">├── tests</span><br><span class="line">│   └── index.test.ts</span><br><span class="line">├── tsconfig.json</span><br><span class="line">├── webpack.config.js</span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure></p>
<p>参考源代码： <a href="https://github.com/wusuopu/baidu-pan-cli">https://github.com/wusuopu/baidu-pan-cli</a></p>
]]></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;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cl
    
    </summary>
    
      <category term="Web开发" scheme="http://www.xefan.com/categories/Web%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="js" scheme="http://www.xefan.com/tags/js/"/>
    
      <category term="node" scheme="http://www.xefan.com/tags/node/"/>
    
      <category term="typescript" scheme="http://www.xefan.com/tags/typescript/"/>
    
  </entry>
  
  <entry>
    <title>记录神舟战神 Z7M-KP7GZ 安装黑苹果过程</title>
    <link href="http://www.xefan.com//archives/84175.html"/>
    <id>http://www.xefan.com//archives/84175.html</id>
    <published>2019-05-28T08:40:43.583Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>现在这台 MacBook Air 差不多用了4年，因此打算换台新的电脑。目前需要一台有独显的电脑，一来是跑一些程序，二来是方便打游戏。<br>另外由于 macOS 已经用习惯了，不想再换其他系统。因此才考虑的安装黑苹果。通过两周的调研，别人给的经验是最好选择已经有人安装成功过的电脑型号，免得有问题自己折腾。<br>于是乎就选择了性价比比较高的神舟。最后安装完成之后，系统还有些小问题，不过基本也能用。对于要求比较高的建议还是白苹果吧。</p>
<h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul>
<li>一个8G以上的U盘，用于烧写系统镜像</li>
<li>一个U盘，大小不限，只要启动WinPE就行</li>
<li>一个usb鼠标，安装过程中触摸板用不了</li>
</ul>
<p>因为我打算安装3个系统，我的硬盘分区如下：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">128G SSD:</span><br><span class="line">ESP     600M    用于EFI引导</span><br><span class="line">EXT4    127G    用于安装ubuntu</span><br><span class="line"></span><br><span class="line">1TB机械硬盘：</span><br><span class="line">ESP     200M    用于EFI引导</span><br><span class="line">MSR     128M</span><br><span class="line">NTFS    100G    Windows C</span><br><span class="line">NTFS    100G    Windows D</span><br><span class="line">SWAP    16G     Linux交换分区</span><br><span class="line">EXFAT32 320G    用于多个系统之间分享文件</span><br><span class="line">APFS    440G    安装黑苹果</span><br></pre></td></tr></table></figure></p>
<p>在安装过程中需要用苹果的硬盘工具进行分区，如果硬盘上有文件的话需要先进行备份。</p>
<p>安装过程我是参考的黑果小兵博客： <a href="https://blog.daliansky.net/Lenovo-Xiaoxin-Air-13-macOS-Mojave-installation-tutorial.html">https://blog.daliansky.net/Lenovo-Xiaoxin-Air-13-macOS-Mojave-installation-tutorial.html</a></p>
<p>下载镜像 ( <a href="https://blog.daliansky.net/tags/%E9%95%9C%E5%83%8F/">https://blog.daliansky.net/tags/%E9%95%9C%E5%83%8F/</a> ) 然后使用镜像烧写工具将镜像烧写到8G的U盘。这里我用的是 Etcher 这个工具。<br>参考别人的说法，10.14 的系统没有 Nvidia 的显卡驱动，10.13 的系统才有。不过我还是下载的 10.14.4 的系统镜像。</p>
<p>下载 Z7M-KP7GZ 的相关驱动： <a href="https://github.com/kirainmoe/hasee-z7-kp7gz-macos">https://github.com/kirainmoe/hasee-z7-kp7gz-macos</a></p>
<h2 id="安装步骤"><a href="#安装步骤" class="headerlink" title="安装步骤"></a>安装步骤</h2><p>启动电脑进入 BIOS 禁用 Secure Boot。然后通过U盘启动进入WinPE，先按照上面的硬盘分区计划对硬盘进行分区。如果你是整块硬盘只用于安装 macOS，则可以忽略这上步。<br>然后重启换另一个U盘启动进入 macOS 的安装界面。进入 Clover 后按 o 键在 Boot 项后面添加 nv_disable=1 参数。如图：<br><img src="/wp-content/uploads/2019/05/hasee_macos_01.jpg" alt=""></p>
<p>然后回车开始引导 macOS。等几分钟之后就进入了安装程序，依次选择语言、同意用户协议、选择安装硬盘。<br>这里可以打开硬盘工具，然后进行分区。建议使用 APFS 格式的分区。注意，如果原来硬盘上有数据的记得先备份。如图：<br><img src="/wp-content/uploads/2019/05/hasee_macos_02.png" alt=""><br><img src="/wp-content/uploads/2019/05/hasee_macos_03.jpg" alt=""></p>
<p>安装过程中会重启，重启之后进入 Clover 再选择使用对应的硬盘分区，选择”Boot macOS install from Mac”启动项，不要选择Preboot的启动项。<br><img src="/wp-content/uploads/2019/05/hasee_macos_04.jpg" alt=""></p>
<p>如果出现Kernel Panic错误，重启再来试一遍吧。<br><img src="/wp-content/uploads/2019/05/hasee_macos_05.jpg" alt=""></p>
<p>安装过程中啥都干不了，只能等了。差不多等半个小时就安装完成。接下来进入设置向导，选择地区、添加几号等操作。这个过程中触摸板用不了，所以需要外接鼠标。<br>最后安装完成，进入系统。<br><img src="/wp-content/uploads/2019/05/hasee_macos_06.jpg" alt=""></p>
<p>系统安装完成之后，接下来就是安装EFI。因为现在EFI文件还是U盘里，否则就只能每次启动时都需要通过U盘来引导了。<br>在前面使用硬盘工具进行分区时，它会在当前硬盘的开始位置创建一个200M的ESP分区。我们只需要将U盘中的EFI复制到这个分区里即可。</p>
<p>打开终端执行命令：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 查找分区情况</span><br><span class="line">diskutil list</span><br><span class="line"></span><br><span class="line"># 分别挂载本地硬盘的EFI分区各u盘的EFI分区</span><br><span class="line">sudo diskutil mount disk1s1</span><br><span class="line">sudo diskutil mount disk3s1</span><br></pre></td></tr></table></figure></p>
<p>然后将u盘中EFI目录下的所有文件复制到硬盘中的EFI目录。如果之前已经有安装过其他的系统，那么只需要将u盘中的 EFI/CLOVER 复制到硬盘中的 EFI 目录下即可</p>
<h2 id="安装驱动"><a href="#安装驱动" class="headerlink" title="安装驱动"></a>安装驱动</h2><p>现在系统安装好了，EFI也安装好了。应该就可以正常地进入系统了。不过目前还有这些问题：</p>
<ul>
<li>触摸板用不了</li>
<li>WiFi、蓝牙用不了</li>
<li>显卡用不了，不用使用外接显示器</li>
</ul>
<p>然后我们再接着安装驱动文件。先挂载 ESP 分区，备份 EFI 目录，以免出错之后进不了系统时可以恢复。</p>
<p>删掉 /EFI/CLOVER 文件夹中的所有文件，然后将刚刚下载的所有驱动文件复制到 /EFI/CLOVER 目录下。<br>重启电脑然后执行里面的 optimize.sh 脚本，注意不要启动HiDPI。驱动安装好之后触摸板应该就能用了。</p>
<p>如果声卡不能使用的话，执行命令 kextstat -list-only | grep AppleALC  看看AppleALC.kext 驱动已正确加载。如果没有正确加载，请将其安装在 /Library/Extensions 下后执行命令 sudo kextcache -i / 重建缓存并重启。</p>
<p>接下来搞定无线网卡，如果不想折腾直接用有线了行。Z7M-KP7GZ用的是Intel的无线网卡，这个在 macOS 下是无解的。<br>其他折腾过的网友建议是换Broadcom的。如果要使用AirDrop或者Handoff，要求有蓝牙4.0LE，需要特定型号的博通网卡，比如博通BCM94322HMP（有带蓝牙和不带蓝牙版本的，注意挑选）。</p>
<p>于是我就在淘宝上买了块 BCM94350 WiFi、蓝牙二合一。然后拆机将网卡换上去。网卡在风扇的旁边。<br><img src="/wp-content/uploads/2019/05/hasee_macos_07.jpg" alt=""></p>
<p>结果一开机系统就崩溃了，进入不了登录界面。后来联系卖家，他们说这个只有 macOS 10.13 的驱动，没有 10.14 的。而我安装主正是 10.14 的系统。<br>欲哭无泪啊，折腾了两天，BCM94350网卡的驱动实在搞不定。最后只得再买了个支持黑苹果的USB无线网卡。<br>另外显卡驱动目前在 10.14 下也是无解的。如果要外接显示器的话，可能需要再购买带显卡的 type-C 设备。</p>
]]></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;现在这台 MacBook Air 差不多用了4年，因此打算换台新的电脑。目前需要一台有独显的电脑，一来是跑一些程序，二来是方便打游戏。&lt;br
    
    </summary>
    
      <category term="教程" scheme="http://www.xefan.com/categories/%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="Mac" scheme="http://www.xefan.com/tags/Mac/"/>
    
  </entry>
  
  <entry>
    <title>react storybook使用typescript</title>
    <link href="http://www.xefan.com//archives/84174.html"/>
    <id>http://www.xefan.com//archives/84174.html</id>
    <published>2019-03-14T10:27:36.262Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>在前面的文章有介绍<a href="84172.html" target="_blank">在storybook中使用antd</a>。<br>之前开发js是使用的flow来作类型检查，最近想尝试一下typescript。<br>如果是从头创建一个空的typescript项目则相对就比较简单，但是现在我们是需要将之前的create-react-app项目迁移到typescript。</p>
<h2 id="在create-react-app中添加typescript"><a href="#在create-react-app中添加typescript" class="headerlink" title="在create-react-app中添加typescript"></a>在create-react-app中添加typescript</h2><p>参考 <a href="https://facebook.github.io/create-react-app/docs/adding-typescript">https://facebook.github.io/create-react-app/docs/adding-typescript</a><br>react-scripts从2.1.0版本开始就支持typescript了，这里我们先将项目的react-scripts依赖升级到最新版本：<br><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">yarn add --exact react-scripts@2.1.8</span><br></pre></td></tr></table></figure></p>
<p>然后再添加typescript依赖：<br><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">yarn add typescript @types/node @types/react @types/react-dom @types/jest</span><br></pre></td></tr></table></figure></p>
<p>然后将 <code>src/index.js</code> 更名为 <code>src/index.tsx</code><br>接着创建 <code>tsconfig.json</code> 文件：<br><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;compilerOptions&quot;: &#123;</span><br><span class="line">    &quot;module&quot;: &quot;esnext&quot;,</span><br><span class="line">    &quot;target&quot;: &quot;es5&quot;,</span><br><span class="line">    &quot;lib&quot;: [&quot;es5&quot;, &quot;es6&quot;, &quot;es7&quot;, &quot;es2017&quot;, &quot;dom&quot;],</span><br><span class="line">    &quot;sourceMap&quot;: true,</span><br><span class="line">    &quot;allowJs&quot;: true,</span><br><span class="line">    &quot;moduleResolution&quot;: &quot;node&quot;,</span><br><span class="line">    &quot;forceConsistentCasingInFileNames&quot;: true,</span><br><span class="line">    &quot;skipLibCheck&quot;: false,</span><br><span class="line">    &quot;esModuleInterop&quot;: true,</span><br><span class="line">    &quot;allowSyntheticDefaultImports&quot;: true,</span><br><span class="line">    &quot;strict&quot;: true,</span><br><span class="line">    &quot;resolveJsonModule&quot;: true,</span><br><span class="line">    &quot;isolatedModules&quot;: true,</span><br><span class="line">    &quot;noEmit&quot;: true,</span><br><span class="line">    &quot;jsx&quot;: &quot;preserve&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;include&quot;: [</span><br><span class="line">    &quot;src/**/*&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="在storybook中添加typescript"><a href="#在storybook中添加typescript" class="headerlink" title="在storybook中添加typescript"></a>在storybook中添加typescript</h2><p>参考 <a href="https://storybook.js.org/docs/configurations/typescript-config/">https://storybook.js.org/docs/configurations/typescript-config/</a><br>同样也先将storybook升级到最新版：<br><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">yarn add --dev --exact @storybook/addons@5.0.1 @storybook/react@5.0.1</span><br></pre></td></tr></table></figure><br>然后再添加依赖：<br><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">yarn add --dev awesome-typescript-loader @types/storybook__react @storybook/addon-info react-docgen-typescript-webpack-plugin ts-jest</span><br></pre></td></tr></table></figure></p>
<p>接着修改webpack配置，对应<code>.storybook/webpack.config.js</code>文件：<br><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></pre></td><td class="code"><pre><span class="line">const path = require(&quot;path&quot;);</span><br><span class="line">const antdTheme = &#123;</span><br><span class="line">  &#x27;@primary-color&#x27;: &#x27;#846bc1&#x27;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">module.exports = (&#123; config, mode &#125;) =&gt; &#123;</span><br><span class="line">  config.module.rules.push(&#123;</span><br><span class="line">    test: /\.less$/,</span><br><span class="line">    use: [&#123;</span><br><span class="line">      loader: &quot;style-loader&quot;</span><br><span class="line">    &#125;, &#123;</span><br><span class="line">      loader: &quot;css-loader&quot;</span><br><span class="line">    &#125;, &#123;</span><br><span class="line">      loader: &quot;less-loader&quot;,</span><br><span class="line">      options: &#123;</span><br><span class="line">        modifyVars: antdTheme,    // 如果要自定义主题样式</span><br><span class="line">        javascriptEnabled: true</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;);</span><br><span class="line">  config.module.rules.push(&#123;</span><br><span class="line">    test: /\.(ts|tsx)$/,</span><br><span class="line">    loader: require.resolve(&#x27;awesome-typescript-loader&#x27;),</span><br><span class="line">    options: &#123; configFileName: path.resolve(__dirname, &#x27;./tsconfig.json&#x27;) &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  config.resolve.extensions.push(&#x27;.ts&#x27;, &#x27;.tsx&#x27;);</span><br><span class="line"></span><br><span class="line">  return config;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>以及babel配置，对应<code>.storybook/babelrc.js</code>文件：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">module.exports = function(api) &#123;</span><br><span class="line">  api.cache.forever();</span><br><span class="line">  return &#123;</span><br><span class="line">    &quot;presets&quot;: [</span><br><span class="line">      [</span><br><span class="line">        &quot;@babel/preset-env&quot;,</span><br><span class="line">        &#123;</span><br><span class="line">          &quot;modules&quot;: false,</span><br><span class="line">          &quot;targets&quot;: &#123;</span><br><span class="line">            &quot;browsers&quot;: [</span><br><span class="line">              &quot;&gt;1%&quot;,</span><br><span class="line">              &quot;last 4 versions&quot;,</span><br><span class="line">              &quot;Firefox ESR&quot;,</span><br><span class="line">              &quot;not ie &lt; 11&quot;</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">      &quot;@babel/preset-react&quot;</span><br><span class="line">    ],</span><br><span class="line">    &quot;plugins&quot;: [</span><br><span class="line">      [</span><br><span class="line">        &quot;import&quot;,</span><br><span class="line">        &#123;</span><br><span class="line">          &quot;libraryName&quot;: &quot;antd&quot;,</span><br><span class="line">          &quot;style&quot;: true</span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>同时修改storybook配置<code>.storybook/config.js</code>，让其支持.tsx文件：<br>将之前的：<br><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">const req = require.context(&#x27;../src/components&#x27;, true, /\.stories\.js$/)</span><br></pre></td></tr></table></figure><br>修改为：<br><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">const req = require.context(&#x27;../src/components&#x27;, true, /\.stories\.[jt]sx?$/)</span><br></pre></td></tr></table></figure></p>
<p>最后再创建 <code>.storybook/tsconfig.json</code> 文件：<br><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;extends&quot;: &quot;../tsconfig&quot;,</span><br><span class="line">  &quot;compilerOptions&quot;: &#123;</span><br><span class="line">    &quot;jsx&quot;: &quot;react&quot;,</span><br><span class="line">    &quot;isolatedModules&quot;: false,</span><br><span class="line">    &quot;noEmit&quot;: false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>至此一切都完成了。</p>
<p>运行storybook时若出现错误： <code>Error: Cannot find module &#39;@emotion/core/package.json&#39;</code>，则手动安装一下： <code>yarn add --dev @emotion/core</code></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在前面的文章有介绍&lt;a href=&quot;84172.html&quot; target=&quot;_blank&quot;&gt;在storybook中使用antd&lt;/a&gt;。&lt;br&gt;之前开发js是使用的flow来作类型检查，最近想尝试一下typescript。&lt;br&gt;如果是从头创建一个空的typescript项
    
    </summary>
    
      <category term="前端相关" scheme="http://www.xefan.com/categories/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/"/>
    
    
      <category term="js" scheme="http://www.xefan.com/tags/js/"/>
    
      <category term="react" scheme="http://www.xefan.com/tags/react/"/>
    
  </entry>
  
  <entry>
    <title>在树莓派3上运行Android6</title>
    <link href="http://www.xefan.com//archives/84173.html"/>
    <id>http://www.xefan.com//archives/84173.html</id>
    <published>2019-03-02T21:26:22.080Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>最近需要在 android 上进行测试，无奈在电脑上使用模拟器运行速度太慢了。<br>正好我还有几个闲置的树莓派3设备，于是就尝试在树莓派上运行android看看。</p>
<h2 id="刻录系统到SD卡"><a href="#刻录系统到SD卡" class="headerlink" title="刻录系统到SD卡"></a>刻录系统到SD卡</h2><p>1.先下载 android6 的树莓派镜像： <a href="https://pan.baidu.com/s/1YHrmjN3be7UaLAdBJr-YhQ">https://pan.baidu.com/s/1YHrmjN3be7UaLAdBJr-YhQ</a></p>
<p>2.下载完成后解压，然后开始执行写入操作： sudo dd if=andrpi3-20160626.img of=/dev/disk3 bs=4096000</p>
<p>这里我用的读卡器，被识别为 disk3。文件比较大，请耐心等待。我写入差不多花了一个小时的时间。</p>
<h2 id="运行系统"><a href="#运行系统" class="headerlink" title="运行系统"></a>运行系统</h2><p>将SD卡插回到树莓派中，并接上鼠标、键盘、显示器，然后开机。<br>这里稍微注意一下，树莓派3需要 5V/2A 的电源才能工作。<br>刚开始我试了一下开不了机，结果才发现是电源电压不足。</p>
<p>运行效果如图：<br><img src="/wp-content/uploads/2019/03/raspberry_pi-android6.JPG" alt=""></p>
<p>连接上网络之后使用 adb 进行操作： adb connect 192.168.0.101<br>这里我的树莓派的ip为 192.168.0.101</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近需要在 android 上进行测试，无奈在电脑上使用模拟器运行速度太慢了。&lt;br&gt;正好我还有几个闲置的树莓派3设备，于是就尝试在树莓派上运行android看看。&lt;/p&gt;
&lt;h2 id=&quot;刻录系统到SD卡&quot;&gt;&lt;a href=&quot;#刻录系统到SD卡&quot; class=&quot;heade
    
    </summary>
    
      <category term="Linux栏目" scheme="http://www.xefan.com/categories/Linux%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="Android" scheme="http://www.xefan.com/tags/Android/"/>
    
      <category term="树莓派" scheme="http://www.xefan.com/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"/>
    
  </entry>
  
  <entry>
    <title>在storybook中使用antd</title>
    <link href="http://www.xefan.com//archives/84172.html"/>
    <id>http://www.xefan.com//archives/84172.html</id>
    <published>2018-11-22T14:55:23.919Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>最近一个 react 的项目有用到了 antd 这个 ui 库。这里作个笔记记录一下如何在 storybook 中显示 antd 的组件。</p>
<p>项目是使用 <code>create-react-app</code> 创建的，项目目录结构如下：<br><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">├── .storybook/</span><br><span class="line">├── README.md</span><br><span class="line">├── antd-theme.js</span><br><span class="line">├── node_modules</span><br><span class="line">├── package.json</span><br><span class="line">├── public</span><br><span class="line">└── src</span><br><span class="line">    ├── App.js</span><br><span class="line">    ├── components</span><br><span class="line">    │   ├── button.js</span><br><span class="line">    │   └── button.stories.js</span><br><span class="line">    ├── index.js</span><br><span class="line">    └── registerServiceWorker.js</span><br></pre></td></tr></table></figure></p>
<p>这里创建了一个 <code>button</code> 组件，内容如下：<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/components/button.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">Component</span>, <span class="title class_">Fragment</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ButtonGhost</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123; </span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">Fragment</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h3</span> <span class="attr">className</span>=<span class="string">&quot;ex-title&quot;</span>&gt;</span>Ghost Button<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">background:</span> &#x27;<span class="attr">rgb</span>(<span class="attr">47</span>, <span class="attr">45</span>, <span class="attr">165</span>)&#x27;, <span class="attr">padding:</span> &#x27;<span class="attr">26px</span> <span class="attr">16px</span> <span class="attr">16px</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span>Primary<span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">className</span>=<span class="string">&quot;ml20&quot;</span> <span class="attr">ghost</span>&gt;</span>Default<span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">className</span>=<span class="string">&quot;ml20&quot;</span> <span class="attr">type</span>=<span class="string">&quot;dashed&quot;</span> <span class="attr">ghost</span>&gt;</span>Dashed<span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Button</span> <span class="attr">className</span>=<span class="string">&quot;ml20&quot;</span> <span class="attr">type</span>=<span class="string">&quot;danger&quot;</span> <span class="attr">ghost</span>&gt;</span>danger<span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Fragment</span>&gt;</span></span></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"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">ButtonGhost</span>;</span><br></pre></td></tr></table></figure></p>
<p>对应的 storybook 案例如下：<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/components/button.stories.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; storiesOf &#125; <span class="keyword">from</span> <span class="string">&#x27;@storybook/react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Button</span> <span class="keyword">from</span> <span class="string">&#x27;./button&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">storiesOf</span>(<span class="string">&#x27;General&#x27;</span>, <span class="variable language_">module</span>)</span><br><span class="line">  .<span class="title function_">add</span>(<span class="string">&#x27;Button&#x27;</span>, <span class="function">() =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> /&gt;</span></span>)</span><br></pre></td></tr></table></figure></p>
<p>然后 storybook 配置如下：<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .storybook/config.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; configure &#125; <span class="keyword">from</span> <span class="string">&#x27;@storybook/react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ThemeProvider</span>  &#125; <span class="keyword">from</span> <span class="string">&#x27;styled-components&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> req = <span class="built_in">require</span>.<span class="title function_">context</span>(<span class="string">&#x27;../src/components&#x27;</span>, <span class="literal">true</span>, <span class="regexp">/\.stories\.js$/</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">loadStories</span>(<span class="params"></span>) &#123;</span><br><span class="line">  req.<span class="title function_">keys</span>().<span class="title function_">forEach</span>(<span class="function">(<span class="params">filename</span>) =&gt;</span> <span class="title function_">req</span>(filename))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">configure</span>(loadStories, <span class="variable language_">module</span>)</span><br></pre></td></tr></table></figure></p>
<p>然后运行 storybook ： <code>start-storybook -p 6006 -c .storybook</code>，效果如下：</p>
<p><img src="/wp-content/uploads/2018/11/antd-storybook-01.png" alt=""></p>
<p>这是由于 antd 的 css 没有加载，因此所有按钮的样式都没有。<br>参考 <a href="https://ant.design/docs/react/use-with-create-react-app-cn">https://ant.design/docs/react/use-with-create-react-app-cn</a> 的说明，修改 babel 和 webpack 的配置。<br>参考 <a href="https://storybook.js.org/configurations/custom-webpack-config">https://storybook.js.org/configurations/custom-webpack-config</a> 的说明，修改 storybook 的 webpack 配置。</p>
<p>创建文件 <code>.storybook/.babelrc</code>：<br><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;presets&quot;</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&quot;env&quot;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;modules&quot;</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="string">&quot;targets&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;browsers&quot;</span>: [</span><br><span class="line">            <span class="string">&quot;&gt;1%&quot;</span>,</span><br><span class="line">            <span class="string">&quot;last 4 versions&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Firefox ESR&quot;</span>,</span><br><span class="line">            <span class="string">&quot;not ie &lt; 11&quot;</span></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">    <span class="string">&quot;react&quot;</span>,</span><br><span class="line">    <span class="string">&quot;stage-3&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;plugins&quot;</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&quot;import&quot;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;libraryName&quot;</span>: <span class="string">&quot;antd&quot;</span>,</span><br><span class="line">        <span class="string">&quot;style&quot;</span>: <span class="literal">true</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>创建文件 <code>.storybook/webpack.config.js</code>：<br><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [&#123;</span><br><span class="line">          <span class="attr">loader</span>: <span class="string">&quot;style-loader&quot;</span></span><br><span class="line">        &#125;, &#123;</span><br><span class="line">          <span class="attr">loader</span>: <span class="string">&quot;css-loader&quot;</span></span><br><span class="line">        &#125;, &#123;</span><br><span class="line">          <span class="attr">loader</span>: <span class="string">&quot;less-loader&quot;</span>,</span><br><span class="line">          <span class="attr">options</span>: &#123;</span><br><span class="line">            <span class="comment">//modifyVars: antdTheme,    // 如果要自定义主题样式</span></span><br><span class="line">            <span class="attr">javascriptEnabled</span>: <span class="literal">true</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>然后在 <code>.storybook/config.js</code> 文件添加 <code>import &#39;antd/dist/antd.less&#39;;</code> 。<br>最后的效果如下：<br><img src="/wp-content/uploads/2018/11/antd-storybook-02.png" alt=""></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近一个 react 的项目有用到了 antd 这个 ui 库。这里作个笔记记录一下如何在 storybook 中显示 antd 的组件。&lt;/p&gt;
&lt;p&gt;项目是使用 &lt;code&gt;create-react-app&lt;/code&gt; 创建的，项目目录结构如下：&lt;br&gt;&lt;figure
    
    </summary>
    
      <category term="前端相关" scheme="http://www.xefan.com/categories/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/"/>
    
    
      <category term="js" scheme="http://www.xefan.com/tags/js/"/>
    
      <category term="react" scheme="http://www.xefan.com/tags/react/"/>
    
  </entry>
  
  <entry>
    <title>使用python编写游戏修改器</title>
    <link href="http://www.xefan.com//archives/84171.html"/>
    <id>http://www.xefan.com//archives/84171.html</id>
    <published>2018-11-16T17:25:37.797Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>最近比较怀旧，在玩一个比较老的PC游戏。由于游戏难度太高了，于是就打算自己写一个修改器。<br>通过查阅资料，在 Windows 下的修改器主要需要用到四个函数：OpenProcess, CloseHandle, WriteProcessMemory, ReadProcessMemory。</p>
<p>这几个都是C++的函数，在Python中可以通过ctypes来直接调用。<br>然后接下来介绍一下基本的操作流程。</p>
<p>1.通过 任务管理器 或者其他方式得到需要修改的游戏进程。然后通过 <code>OpenProcess</code> 注入该进程。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">PROCESS_QUERY_INFORMATION = <span class="number">0x0400</span></span><br><span class="line">PROCESS_VM_OPERATION = <span class="number">0x0008</span></span><br><span class="line">PROCESS_VM_READ = <span class="number">0x0010</span></span><br><span class="line">PROCESS_VM_WRITE = <span class="number">0x0020</span></span><br><span class="line"></span><br><span class="line">hProcess = ctypes.windll.kernel32.OpenProcess(</span><br><span class="line">    PROCESS_QUERY_INFORMATION|PROCESS_VM_READ|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,</span><br><span class="line">    <span class="literal">False</span>, pid</span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>2.然后可以通过 <code>ReadProcessMemory</code> 来扫描游戏的内存找到需要修改的数值项的内存地址。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">buf = ctypes.c_int32()</span><br><span class="line">nread = ctypes.c_size_t()</span><br><span class="line">ret = ctypes.windll.kernel32.ReadProcessMemory(</span><br><span class="line">    hProcess,</span><br><span class="line">    base_addr,</span><br><span class="line">    ctypes.byref(buf),</span><br><span class="line">    ctypes.sizeof(buf),</span><br><span class="line">    ctypes.byref(nread)</span><br><span class="line">)</span><br></pre></td></tr></table></figure><br>这里是读取 <code>base_addr</code> 地址之后的4个字节的内容。可以通过循环来遍历游戏的内存，找到需要修改的地址。<br>当然，为了方便也可以直接使用 Cheat Engine 之类的软件来查找，然后把找到的内存地址记录下来即可。<br><a href="/wp-content/uploads/2018/11/ce.png"><img src="/wp-content/uploads/2018/11/ce.png" /></a></p>
<p>3.得到需要修改的内存地址之后，就可以 <code>WriteProcessMemory</code> 来修改该地址保存的值。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">buf = ctypes.c_int32(value)</span><br><span class="line">nread = ctypes.c_size_t()</span><br><span class="line">ret = ctypes.windll.kernel32.WriteProcessMemory(</span><br><span class="line">  hProcess,</span><br><span class="line">  base_addr,</span><br><span class="line">  ctypes.byref(buf),</span><br><span class="line">  ctypes.sizeof(buf),</span><br><span class="line">  ctypes.byref(nwrite)</span><br><span class="line">)</span><br></pre></td></tr></table></figure><br>这里是往 <code>base_addr</code> 这个地址写入值为 <code>value</code> 的4字节内容。</p>
<p>4.最后如果不再需要修改了的话，就通过 <code>CloseHandle</code> 关闭该注入操作。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ctypes.windll.kernel32.CloseHandle(hProcess)</span><br></pre></td></tr></table></figure></p>
<p>以上都是针对 Windows 系统的，对于 Linux 系统的话 可以通过 <code>ptrace</code> (<a href="http://man7.org/linux/man-pages/man2/ptrace.2.html">http://man7.org/linux/man-pages/man2/ptrace.2.html</a>) 操作实现。由于我没有 Linux 的游戏就没有研究了。</p>
<p>上面修改器的完整源代码，如有需要可通过以下链接获取：<br><a href="https://github.com/wusuopu/cheat_engine_caesar3">https://github.com/wusuopu/cheat_engine_caesar3</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近比较怀旧，在玩一个比较老的PC游戏。由于游戏难度太高了，于是就打算自己写一个修改器。&lt;br&gt;通过查阅资料，在 Windows 下的修改器主要需要用到四个函数：OpenProcess, CloseHandle, WriteProcessMemory, ReadProces
    
    </summary>
    
      <category term="Python栏目" scheme="http://www.xefan.com/categories/Python%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="python" scheme="http://www.xefan.com/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>使用pytest测试flask应用</title>
    <link href="http://www.xefan.com//archives/84170.html"/>
    <id>http://www.xefan.com//archives/84170.html</id>
    <published>2018-11-06T15:12:52.301Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>python 本身就有 unittest 单元测试框架，但是觉得它并不是很好用，我更倾向于使用 pytest 。</p>
<p>下面通过一个例子来介绍如何使用 pytest 对 flask 应用进行单元测试。</p>
<p>首先新建一个 flask 应用，并针对根路径创建一条路由。代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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"># server.py</span></span><br><span class="line">app = flask.Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">home</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;ok&#x27;</span></span><br></pre></td></tr></table></figure>
<p>然后针对首页编写单元测试，代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># tests/test_app.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_home_page</span>(<span class="params">client</span>):</span><br><span class="line">    rv = client.get(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> rv.status_code == <span class="number">200</span></span><br><span class="line">    <span class="keyword">assert</span> rv.data == <span class="string">b&#x27;ok&#x27;</span></span><br></pre></td></tr></table></figure>
<p>然后执行命令运行该测试用例： <code>pytest -s tests/test_app.py</code></p>
<p>在 pytest 中编写测试用例就只需要新建一个以 <code>test_</code> 开头的函数即可。</p>
<p>以上是针对flask路由作的最基本测试。接下来编写一个新的路由，该页面只有用户登录之后才能访问。代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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"># server.py</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/member&#x27;</span></span>)</span></span><br><span class="line"><span class="meta">@flask_security.decorators.login_required</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">member</span>():</span><br><span class="line">    user = flask_security.core.current_user</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">str</span>(user.<span class="built_in">id</span>)</span><br></pre></td></tr></table></figure>
<p>要对该路由进行测试，则需要先创建一个用户。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># tests/test_app.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_module</span>(<span class="params">module</span>):</span><br><span class="line">    App.testing = <span class="literal">True</span></span><br><span class="line">    fixture.setup()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">teardown_module</span>(<span class="params">module</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br></pre></td></tr></table></figure><br>上面的 <code>setup_module</code> 和 <code>teardown_module</code> 函数分别是在所有的测试用例执行之前与执行之后执行。在这里我们通过 <code>setup_module</code> 在执行测试之前先创建一个用户。然后再创建一个 pytest 的 fixture:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># tests/conftest.py</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">auth_client</span>(<span class="params">client</span>):</span><br><span class="line">    <span class="keyword">with</span> client.session_transaction() <span class="keyword">as</span> sess:</span><br><span class="line">        sess[<span class="string">&#x27;user_id&#x27;</span>] = <span class="built_in">str</span>(fixture.users[<span class="number">0</span>].<span class="built_in">id</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">yield</span> client</span><br></pre></td></tr></table></figure><br>这里创建了一个 <code>auth_client</code> fixture，之后所有以 <code>auth_client</code> 发起的请求都是登录状态的。</p>
<p>最后再针对 <code>/member</code> 路由编写两个测试用例，分别是未登录状态与登录状态下的。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_member_page_without_login</span>(<span class="params">client</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    没有登录则跳转到登录页面</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    rv = client.get(<span class="string">&#x27;/member&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> rv.headers[<span class="string">&#x27;Location&#x27;</span>] == <span class="string">&#x27;http://localhost/login?next=%2Fmember&#x27;</span></span><br><span class="line">    <span class="keyword">assert</span> rv.status_code == <span class="number">302</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_member_page_with_login</span>(<span class="params">auth_client</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    已经登录则返回当前用户id</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    rv = auth_client.get(<span class="string">&#x27;/member&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> rv.status_code == <span class="number">200</span></span><br><span class="line">    <span class="keyword">assert</span> rv.data.decode(<span class="string">&#x27;utf8&#x27;</span>) == <span class="built_in">str</span>(fixture.users[<span class="number">0</span>].<span class="built_in">id</span>)</span><br></pre></td></tr></table></figure></p>
<p>以上就是一个简单的 flask 应用了。但是有时一个稍微复杂一点的应用会用到一些第三方的api。这时针对这种情况编写测试用例时就需要用到 mock 功能了。再编写一个新的路由页面：<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># server.py</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/movies&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">movies</span>():</span><br><span class="line">    data = utils.fetch_movies()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>, <span class="number">500</span></span><br><span class="line">    <span class="keyword">return</span> flask.jsonify(data)</span><br></pre></td></tr></table></figure></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fetch_movies</span>():</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        url = <span class="string">&#x27;http://api.douban.com/v2/movie/top250?start=0&amp;count=1&#x27;</span></span><br><span class="line">        res = requests.get(url, timeout=<span class="number">5</span>)</span><br><span class="line">        <span class="keyword">return</span> res.json()</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="keyword">return</span> &#123;&#125;</span><br></pre></td></tr></table></figure>
<p>请求该路由会返回豆瓣top250的电影信息。然后再编写两个测试用例分别模拟api调用成功与失败的情况。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># tests/test_app.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_movies_api</span>(<span class="params">client</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    调用豆瓣api成功的情况</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    fetch_movies_patch = mock.patch(<span class="string">&#x27;utils.fetch_movies&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    func = fetch_movies_patch.start()</span><br><span class="line">    func.return_value = &#123;<span class="string">&#x27;start&#x27;</span>: <span class="number">0</span>, <span class="string">&#x27;count&#x27;</span>: <span class="number">0</span>, <span class="string">&#x27;subjects&#x27;</span>: []&#125;</span><br><span class="line"></span><br><span class="line">    rv = client.get(<span class="string">&#x27;/movies&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> rv.status_code == <span class="number">200</span></span><br><span class="line">    <span class="keyword">assert</span> func.called</span><br><span class="line"></span><br><span class="line">    fetch_movies_patch.stop()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_movies_api_with_error</span>(<span class="params">client</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    调用豆瓣api出错的情况</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    fetch_movies_patch = mock.patch(<span class="string">&#x27;utils.fetch_movies&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    func = fetch_movies_patch.start()</span><br><span class="line">    func.return_value = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    rv = client.get(<span class="string">&#x27;/movies&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> rv.status_code == <span class="number">500</span></span><br><span class="line">    <span class="keyword">assert</span> func.called</span><br><span class="line"></span><br><span class="line">    fetch_movies_patch.stop()</span><br></pre></td></tr></table></figure></p>
<p>这里使用 python 的 mock 模块来模拟让某个函数返回固定的结果。</p>
<p>完整的代码请访问： <a href="https://github.com/wusuopu/flask-test-example">https://github.com/wusuopu/flask-test-example</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;python 本身就有 unittest 单元测试框架，但是觉得它并不是很好用，我更倾向于使用 pytest 。&lt;/p&gt;
&lt;p&gt;下面通过一个例子来介绍如何使用 pytest 对 flask 应用进行单元测试。&lt;/p&gt;
&lt;p&gt;首先新建一个 flask 应用，并针对根路径创建一
    
    </summary>
    
      <category term="Python栏目" scheme="http://www.xefan.com/categories/Python%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="python" scheme="http://www.xefan.com/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>HAProxy+Nginx+gunicorn获取真实ip</title>
    <link href="http://www.xefan.com//archives/84169.html"/>
    <id>http://www.xefan.com//archives/84169.html</id>
    <published>2017-06-10T22:31:24.730Z</published>
    <updated>2025-10-22T05:34:37.736Z</updated>
    
    <content type="html"><![CDATA[<p>之前在部署在 nginx + uwsgi 应用时都是通过如下方法来获取真实的客户端ip的：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">upstream app_server &#123;</span><br><span class="line">    server unix:///tmp/gunicorn.sock fail_timeout=0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    listen       80;</span><br><span class="line">    server_name  localhost;</span><br><span class="line">    location / &#123;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">        proxy_set_header Host $http_host;</span><br><span class="line">        proxy_redirect off;</span><br><span class="line"></span><br><span class="line">        proxy_pass   http://app_server;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这样在 uwsgi 的应用程序中只需要读取 http headers 中的 X-Forwarded-For 字段即可。</p>
<p>但是最近由于运维架构的是采用 haproxy + nginx + uwsgi 是形式，导致了在 uwsgi 应用程序中获取到的 ip 都是 haproxy 的。<br>为了要获取到真实的ip地址，需要由 haproxy 将 ip 传给 nginx，再由 nginx 传给 uwsgi。<br>在网上搜索了半天 haproxy 的相关配置，感觉太复杂了。因此还是决定从 nginx 入手。</p>
<p>经过实验将 nginx 的配置改为如下即可：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">proxy_set_header X-Forwarded-For $http_x_forwarded_for;</span><br><span class="line">proxy_set_header Host $http_host;</span><br><span class="line">proxy_redirect off;</span><br></pre></td></tr></table></figure>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;之前在部署在 nginx + uwsgi 应用时都是通过如下方法来获取真实的客户端ip的：&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;lin
    
    </summary>
    
      <category term="Python栏目" scheme="http://www.xefan.com/categories/Python%E6%A0%8F%E7%9B%AE/"/>
    
    
      <category term="python" scheme="http://www.xefan.com/tags/python/"/>
    
  </entry>
  
</feed>
