<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>zhd_superman (HD)</title>
    <link>https://ruby-china.org/zhd_superman</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>黑客说：如何做到 4 天上线一个小程序？</title>
      <description>&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/G5q2k7-Rm2vv9D" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;自 6 月 6 号上线“黑客说”网页版（hackertalk.net）以来吸引了很多用户，为了进一步完善终端体验，我们决定复用已有的技术栈，实现微信端小程序，前后开发仅花了 4 天，本文主要从技术的角度讨论我们如何快速上线小程序。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/M5waOq-zzPjzP0" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="黑客说是什么 ？"&gt;黑客说是什么？&lt;/h2&gt;
&lt;p&gt;这是我们专门为程序员群体定制的交流平台，有及时技术资讯、高质量技术问答、实用编程经验分享，还有程序员的日常生活。接近 500 个编程相关话题。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/mDA2QR-RQ5EGV9" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一个高度定制的 Markdown 编辑器：所见即所得，再也不用分屏预览了～&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/P5AmDX-z82nNRw" title="" alt="网页版编辑器：插入 latex 公式"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/q1X94n-pbP7jX7" title="" alt="网页版编辑器：插入 markdown 文本"&gt;&lt;/p&gt;

&lt;p&gt;​感兴趣的小伙伴可以戳下面链接直接体验 👇👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hackertalk.net" rel="nofollow" target="_blank" title=""&gt;黑客说：一个有趣的程序员交流平台&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="网页端技术栈"&gt;网页端技术栈&lt;/h2&gt;
&lt;p&gt;为了代码更好地复用和维护，我们在 &lt;a href="https://vuejs.org/" rel="nofollow" target="_blank" title=""&gt;Vue&lt;/a&gt; 和 React 中选择了 &lt;a href="https://reactjs.org/" rel="nofollow" target="_blank" title=""&gt;React&lt;/a&gt;，网页端主要技术栈如下：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;react + typescript + redux + immer + redux-saga + axios + tailwindcss + fakerjs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;typescript 项目必备，极大提高代码正确性和可维护性&lt;/li&gt;
&lt;li&gt;immer 替代了传统的 &lt;a href="https://github.com/immutable-js/immutable-js" rel="nofollow" target="_blank" title=""&gt;immutablejs&lt;/a&gt; 方案，在 reducer 中实现类似 vue 的直接数值操作（简洁性），同时保持 immutable 数据流的优点（可维护性）&lt;/li&gt;
&lt;li&gt;saga 保持了 API 接口调用的简洁性、可调试性&lt;/li&gt;
&lt;li&gt;axios 封装了 http 请求，可以通过自定义 adapter 适应不同终端运行环境&lt;/li&gt;
&lt;li&gt;tailwindcss 通过原子化的 css 大大降低了样式文件体积，加快网页加载速度，也很大程度降低了小程序包体积（2MB 限制），更多的代码空间可以用于 UI 界面和 JS 逻辑&lt;/li&gt;
&lt;li&gt;fakerjs 用于模拟数据，在开发环境中注入数据到 redux，方便调试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="小程序端技术栈"&gt;小程序端技术栈&lt;/h3&gt;
&lt;p&gt;小程序端技术栈和网页端高度重合（这也是我们能够快速上线应用的原因），其中最大的变化是由 react 变为 react + &lt;a href="https://docs.taro.zone/" rel="nofollow" target="_blank" title=""&gt;taro&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Taro&amp;nbsp;是一个开放式跨端跨框架解决方案，支持使用 React/Vue/Nerv 等框架来开发&amp;nbsp;微信&amp;nbsp;/&amp;nbsp;京东&amp;nbsp;/&amp;nbsp;百度&amp;nbsp;/&amp;nbsp;支付宝&amp;nbsp;/&amp;nbsp;字节跳动&amp;nbsp;/&amp;nbsp;QQ&amp;nbsp;小程序 / H5 / RN 等应用&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;小程序端开发可谓混乱至极，原生代码难以组织、难以维护，通常都需要一些框架进行封装，Taro 是我们在使用了几个不同方案后决定采纳的，和 react 高度重合，可以直接使用 hook，极大提高代码复用的可能性（这是以前积累的经验基础）。&lt;/p&gt;
&lt;h3 id="APP 端技术栈"&gt;APP 端技术栈&lt;/h3&gt;
&lt;p&gt;目前黑客说还没有上线相关 APP，技术栈复用可以直接将 react 换为 react-native。&lt;/p&gt;
&lt;h2 id="代码文件组织"&gt;代码文件组织&lt;/h2&gt;
&lt;p&gt;组织良好的代码是高度复用的关键，我们采用 components + containers 的代码分割方式，严格规范代码组织方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI 界面相关组件只能放在 components 文件夹，无状态，不能耦合任何状态管理库相关代码&lt;/li&gt;
&lt;li&gt;数据注入的容器组件只能放于 containers 文件夹，不能包含任何 UI 相关代码，比如 &lt;code&gt;div&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;模块化、原子化：代码分层设计，实现组件高度复用，保持应用一致性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;文件夹布局如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── assets     固定资源文件：图片、文字、svg 等
├── components 纯 UI 组件
├── constants  全局常量
├── containers 纯容器组件
├── hooks      自定义 hooks
├── layout     布局相关 UI 逻辑
├── locales    国际化相关
├── pages      整页逻辑
├── services   API 接口代码
├── store      状态管理代码
├── styles     样式代码
├── types      ts 类型声明
└── utils      公共工具类
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Store 状态管理"&gt;Store 状态管理&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── actions
├── reducers
├── sagas
├── selectors
└── types
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;saga 调用 API 代码组织如下：调用调试非常方便&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;getPostById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReduxAction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPostById&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_POST_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的 postApi 来自 services 文件夹：&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPostById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/v1/posts/by_id/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="小程序端特殊适配"&gt;小程序端特殊适配&lt;/h2&gt;&lt;h3 id="Cookie"&gt;Cookie&lt;/h3&gt;
&lt;p&gt;由于小程序端无法支持 http cookie，无法像浏览器一样使用 cookie 机制保证安全性和维护用户登录状态，我们需要手动模拟一个 cookie 机制，这里我们推荐使用京东开源的一个方案：&lt;a href="https://juejin.cn/post/6844904045920911374" rel="nofollow" target="_blank" title=""&gt;京东购物小程序 cookie 方案实践&lt;/a&gt;，可以实现 cookie 过期、多 cookie 功能。其原理使用了 localstorage 替代 cookie。&lt;/p&gt;
&lt;h3 id="Http Request"&gt;Http Request&lt;/h3&gt;
&lt;p&gt;小程序端只能使用 &lt;code&gt;wx.request&lt;/code&gt; 进行 http 请求，如果大量 API 直接使用这个接口编写，代码将难以维护和复用，我们使用 axios 的 adapter 模式封装 &lt;code&gt;wx.request&lt;/code&gt; ，请求结果和 error 都按 axios 数据格式进行加工。这样我们就能够直接在小程序端使用 axios 了。&lt;/p&gt;

&lt;p&gt;转换请求参数：&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toQueryStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;axios 适配器模式（CookieUtil 代码参考上文京东的例子）&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 请求字段拼接&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;toQueryStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 常规请求封装&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CookieUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCookiesStr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CookieUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setCookieStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set-Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nx"&gt;CookieUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCookieFromHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setCookieStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;axiosRes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StatusText&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;config&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;axiosRes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;axiosErr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;axiosRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;isAxiosError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;axiosErr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;axiosErr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;isAxiosError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;axiosErr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;axios 适配完成后原先 API 相关代码无需改动一行即可直接复用。&lt;/p&gt;
&lt;h3 id="Message"&gt;Message&lt;/h3&gt;
&lt;p&gt;消息弹窗和 toast 不能运行在小程序端，我们通过接口兼容实现代码复用：&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @author z0000
 * @version 1.0
 * message 弹窗，api 接口参考 antd，小程序向此接口兼容
 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Taro&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tarojs/taro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showToast error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showToast error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showToast error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showToast error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars&lt;/span&gt;
  &lt;span class="nf"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLoading&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showLoading error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hideLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里接口参考的 Antd 的 Message API，实现浏览器端和小程序端的兼容。&lt;/p&gt;
&lt;h3 id="History"&gt;History&lt;/h3&gt;
&lt;p&gt;小程序端 history 机制和浏览器端不一样，为了代码复用，我们将小程序路由 API 转换适配浏览器端接口（react router 的 history 方法）：&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * common api 小程序向 react router 的 history 方法兼容
 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Taro&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tarojs/taro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// TODO: 增加query对象方法&lt;/span&gt;
  &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigateTo fail: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirectTo fail: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;positive number not support in wx environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateBack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigateBack fail: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="nf"&gt;goBack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateBack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigateBack fail: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后批量搜索代码中 &lt;code&gt;useHistory&lt;/code&gt; 相关 hook 代码，转换为上述实现即可。&lt;/p&gt;
&lt;h3 id="Router"&gt;Router&lt;/h3&gt;
&lt;p&gt;小程序端不能直接使用 react-router 类似的路由管理方案，受益于代码模块化分割，大部分代码并没有耦合 react-router-dom 相关的东西，最多的就是 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 组件，这里我们小小改造一下 Link 组件，批量替代即可：&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Taro&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tarojs/taro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tarojs/components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LinkProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;Taro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是 &lt;code&gt;Taro.navigateTo&lt;/code&gt; 不能直接跳转 Tab 页面，所有最终代码完成后需要 search + 测试覆盖检查相关问题。当然，你也可以在上面代码中检查 to 参数是否为 tab 页面，切换成 &lt;code&gt;Taro.switchTab&lt;/code&gt; 方法。&lt;/p&gt;
&lt;h3 id="Path Params"&gt;Path Params&lt;/h3&gt;
&lt;p&gt;小程序不支持类似 &lt;code&gt;/post/:id&lt;/code&gt; 的路由参数，我们需要将路由参数转换为：&lt;code&gt;/post?id=xx&lt;/code&gt;，这个转换通过 IDE 搜索，批量 replace 即可。&lt;/p&gt;
&lt;h3 id="CSS"&gt;CSS&lt;/h3&gt;
&lt;p&gt;由于小程序端的 rpx 单位、px 单位直接使用会有很大的复用问题，导致网页端往小程序端迁移时需要大量改造 HTML 代码，这里我们使用 sass 实现了 tailwindcss 类似的功能（针对小程序端进行改造），通过变量开关切换单位，可以做到不同设计稿代码也能兼容（375px 和 750px 或者 rpx，rem 单位都可以直接兼容）。&lt;/p&gt;

&lt;p&gt;设计复用有时比代码复用更加重要，这是用户体验一致性的前提，幸运的是 tailwincss 之类的方案选型让我们很容易做到这一点，我们后续将开源小程序端 tailwindcss 代码，敬请期待。&lt;/p&gt;
&lt;h2 id="团队协作"&gt;团队协作&lt;/h2&gt;
&lt;p&gt;协作也是很重要的一环，产品成功离不开高效合作，我们使用 google doc 全家桶进行协作，包括项目文档、需求、任务管理、邮件，google 全家桶最大的好处就是多端支持，这是目前支持终端最多、协作最方便的工具。linux + android + ios + ipad + windows + mac 都能无缝同步协作。方便设计师、产品经理、程序员共同工作。&lt;/p&gt;
&lt;h2 id="最后"&gt;最后&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://hackertalk.net" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://content.markdowner.net/pub/5jXvDO-aYoXxO1" title="" alt="hackertalk"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;欢迎各位体验！&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hackertalk.net/" rel="nofollow" target="_blank" title=""&gt;黑客说网页版&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hackertalk.net/post/189392795524841472" rel="nofollow" target="_blank" title=""&gt;HackerTalk（黑客说）第一帖：Happy hacking!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;微信小程序搜索：黑客说，或者扫码：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/q1X6Bn-z52Nkzo" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Sat, 07 Aug 2021 18:42:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/41559</link>
      <guid>https://ruby-china.org/topics/41559</guid>
    </item>
    <item>
      <title>如何成为一名后端开发工程师（附路线图）</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;这是一系列高赞文章，详细介绍了前端、后端、DevOps 的路线图，在 Github 上也是一个高星项目。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://markdowner.net/article/170940730528129024" rel="nofollow" target="_blank" title=""&gt;如何成为一名后端开发工程师（附路线图）&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/mbR6zR-B4vXARN" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;程序员都在使用的 markdown 笔记，&lt;a href="https://markdowner.net/" rel="nofollow" target="_blank" title=""&gt;码道笔记，做开发者最好的朋友&lt;/a&gt;&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Fri, 16 Apr 2021 19:15:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/41152</link>
      <guid>https://ruby-china.org/topics/41152</guid>
    </item>
    <item>
      <title>Vue 转 React 指南，看这篇文章就够了</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;为什么要从 Vue 转到 React，这篇文章为什么我们放弃了 Vue？Vue 和 React 深度对比大家可以看看，文章写得很详细，不过对于大多数人来说，用 Vue 还是 React 不是自己说了算，多学一门技术不是坏处，而且 React 被大厂大量使用，要进入大厂也是必备的技能，笔者原先使用 Vue，由于 React 相关概念更加简单，只要会 js 就行，转到 React 只花了几天时间（已经回不去了～）。本文写给想从 Vue 转到 React 的同学，假设读者有一定的 Vue 基础。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://markdowner.net/article/166272088981004288" rel="nofollow" target="_blank" title=""&gt;Vue 转 React 指南，看这篇文章就够了&lt;/a&gt;&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Sat, 03 Apr 2021 19:51:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/41108</link>
      <guid>https://ruby-china.org/topics/41108</guid>
    </item>
    <item>
      <title>前端测试：（1）库、框架、概念（Jest、enzyme、sinon、nock ）</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;测试是开发中不可或缺的一环，当产品不断迭代时，总有一些代码会变更，每次提交代码前执行一遍测试，能够极大降低相同 bug 出现的概率，避免问题扩散到线上环境。编写测试代码时也能够发现原先设计上的问题，将代码解耦分割成更容易测试的片段，重构的过程能够提升代码质量。本文作为【前端测试系列】的第一篇，主要讲常见的测试概念、框架，方便新手入门前端测试领域。所有的 demo 都是基于 React，需要读者有一定的 React 开发基础。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://markdowner.net/article/165892437133643776" rel="nofollow" target="_blank" title=""&gt;前端测试：（1）库、框架、概念（Jest、enzyme、sinon、nock）&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/mbR6zR-B4vXARN" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;程序员都在使用的 markdown 笔记，&lt;a href="https://markdowner.net/" rel="nofollow" target="_blank" title=""&gt;码道笔记，做开发者最好的朋友&lt;/a&gt;&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Fri, 02 Apr 2021 18:41:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/41107</link>
      <guid>https://ruby-china.org/topics/41107</guid>
    </item>
    <item>
      <title>程序员机械键盘入门指南</title>
      <description>&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/Ok49xW-Ly0Pyq1" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;作为一名经常与键盘交互的键盘侠（程序员），拥有一个实用的机械键盘可以很大提高编码效率、编程体验，作为一名老码农，使用过多款机械键盘，无论 Mac 的剪刀键盘做的多好，体验和机械键盘都没有可比性。本文主要介绍一些常见的机械键盘类型、特点帮助新手入门机械键盘。&lt;/p&gt;
&lt;h2 id="为什么非得使用机械键盘"&gt;为什么非得使用机械键盘&lt;/h2&gt;
&lt;p&gt;常见的键盘类型主要有两种&lt;/p&gt;
&lt;h3 id="薄膜键盘"&gt;薄膜键盘&lt;/h3&gt;
&lt;p&gt;顶层薄膜由一个框架支撑，其底部涂有一些导电的材料。底层中有一些断开的导电材料。当顶层薄膜被按下导致两层接触时，电路被接通，为了追求轻薄，大部分笔记本键盘都是采用这种类似的结构，在此基础上进行优化，比如 Mac 的剪刀式结构。这种键盘的一个缺点是使用寿命短，通常只有几百万次。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/RLW0Gm-EowY61e" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="机械键盘"&gt;机械键盘&lt;/h3&gt;
&lt;p&gt;每一个按键都由一个独立的微动开关（Switch，通称为“轴”）组成&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/PyVrLM-abmO64o" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从结构的角度可以看出机械键盘的键程（按键距离）较长，按键段落感强。同时由于全机械结构，整体重量也比普通键盘大（拿起来有点沉），相对成本较高，寿命可达 5000 万次以上。键帽通常可以单独拿下来进行清洗。&lt;/p&gt;

&lt;p&gt;较长的键程可以很好的&lt;strong&gt;避免输入错误，提高效率&lt;/strong&gt;，这是我用机械键盘后很直接的感受，平时打代码速度较快，难免输入错误需要删除，由于机械键盘按键长，即使手指触碰有点错位，也有足够空间通过手指触感反馈调节，这样输入错误大大减少，同时长期打字很容易形成键位适应，越用越熟练。&lt;/p&gt;

&lt;p&gt;笔记本上或者普通薄膜键盘的&lt;strong&gt;键程太短，按键感弱&lt;/strong&gt;，手感反馈时间过短，很容易误点击导致误输入。如果你工作需要长时间使用键盘，机械键盘可以很好帮你提高打字效率。&lt;/p&gt;
&lt;h2 id="机械键盘的主要轴体"&gt;机械键盘的主要轴体&lt;/h2&gt;
&lt;p&gt;市面上主要销售的机械键盘都是采用 &lt;a href="https://www.cherry.cn" rel="nofollow" target="_blank" title=""&gt;Cherry&lt;/a&gt; 公司的 MX 轴（专利保护已结束），主要有以下几种类型&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/4XgWdE-dpjP1Lo" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="黑轴"&gt;黑轴&lt;/h3&gt;
&lt;p&gt;手感较重，按键直上直下，没有停顿感，回弹力量较大，比较适合电竞场景（需要手速快，单位时间按键响应次数高）。对于电竞选手，如果使用薄膜键盘，可能第二次按键的时候第一次按钮还没回弹上来。（注意！黑轴按键的声音可能较大，打着打着可能变成键盘侠，像王者荣耀典韦手上拿着的那个东东）&lt;/p&gt;
&lt;h3 id="青轴"&gt;青轴&lt;/h3&gt;
&lt;p&gt;这是最具有机械键盘特点的轴体，按键体验类似老式打字机，按键过程有停顿感，有点像按圆珠笔滴哩一下的感觉，这种轴体打字效率最高（按键没触底就有反馈感，给按键过程留下反馈时间），但打字的时候会有嘀哩嘀哩的声音，当然比黑轴声音小一点（但无论你按的多轻一定会发出声音）。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/pb7Qa0-0zOpVmb" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="红轴"&gt;红轴&lt;/h3&gt;
&lt;p&gt;这种轴体可以说是黑轴的弱化版本，同样直上直下，但回弹较为轻柔，特点较少。&lt;/p&gt;
&lt;h3 id="茶轴"&gt;茶轴&lt;/h3&gt;
&lt;p&gt;介于红轴和青轴之间，按键向下过程有轻微反馈感，回弹类似红轴体验，声音很小，比较适合在安静的环境下使用，避免打扰别人。&lt;/p&gt;
&lt;h3 id="应该选择哪种轴体？"&gt;应该选择哪种轴体？&lt;/h3&gt;
&lt;p&gt;我觉得主要还是看使用场景以及环境，黑轴玩游戏体验最佳，但声音大，如果你在宿舍使用黑轴，很可能会没朋友（和青轴一样俗称舍友快乐轴或者宿舍去世轴），红轴特点较少，青轴最有按键反馈感，输入效率高，但无法避免发出声音，茶轴声音较小。我在家使用青轴（手感好），在公司使用茶轴（避免声音打扰别人），主要用来写代码。如果你是新手，建议选用万能轴（茶轴），或者到实体店实际体验按键感。&lt;/p&gt;
&lt;h2 id="其他考虑因素"&gt;其他考虑因素&lt;/h2&gt;
&lt;p&gt;消费群体越来越注重个性化定制，键帽、背光、布局、形状、价格也是购买时考虑的因素之一。比如如下几款：&lt;/p&gt;
&lt;h3 id="键帽"&gt;键帽&lt;/h3&gt;
&lt;p&gt;皮卡丘定制款（Cherry）¥2000+&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/XXLW2o-BvmWxMD" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;熊本熊定制款（Cherry）¥1600+&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/Q2VyDN-adQM9po" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/AxLBLr-aY1NewP" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="背光"&gt;背光&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/Py4XkN-VG8a1ab" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="布局（有无数字键，方向键，附加功能键）"&gt;布局（有无数字键，方向键，附加功能键）&lt;/h3&gt;
&lt;p&gt;cherry 游戏键盘 ¥1200+&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/714V77-wW8kDkQ" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;HHKB 简洁版本 ¥2300+ （寿命超长，号称程序员最后一个键盘）&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/vLG8OO-d6W0A65" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="价格"&gt;价格&lt;/h3&gt;
&lt;p&gt;上述机械键盘价格略高，普通学生党可能消费不起，但如果你想体验机械键盘带来的效率提升，100-200¥ 左右的价位也能买到体验不差的机械键盘，多逛逛某宝或者狗东就能找到啦～&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.bilibili.com/video/av60850215/" rel="nofollow" target="_blank" title=""&gt;【回形针 PaperClip】如何打造一把爆款键盘？&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="机械键盘类型（摘自 维基百科 ）"&gt;机械键盘类型（摘自 &lt;a href="https://en.wikipedia.org/wiki/Keyboard_technology" rel="nofollow" target="_blank" title=""&gt;维基百科&lt;/a&gt; ）&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;轴体类型&lt;/th&gt;
&lt;th style="text-align:center;"&gt;键程&lt;/th&gt;
&lt;th style="text-align:center;"&gt;触发行程&lt;/th&gt;
&lt;th style="text-align:center;"&gt;段落行程&lt;/th&gt;
&lt;th style="text-align:center;"&gt;触发声音&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;青轴 (MX Blue)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.5mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2.2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.75mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;有&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;绿轴 (MX Green)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.5mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2.2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.75mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;有&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;奶轴 (MX White)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.5mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2.2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;有（较小）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;茶轴 (MX Brown)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.4mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.25mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;有 (较小)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;白轴 (MX Clear)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.5mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.25mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;红轴 (MX Red)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.4mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;黑轴 (MX Black)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.4mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;灰轴 (MX Linear Grey)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.4mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2±0.6 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;td style="text-align:center;"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;橘轴 (MX Orange)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.05mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.9±0.4 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;黄轴 (MX Yellow)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;3.5mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.2±0.3 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;银轴 (MX Silver)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;3.4mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.2±0.4 mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;紫轴 (MX Purple)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4-0.0&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.5±mm&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="最后～"&gt;最后～&lt;/h2&gt;
&lt;p&gt;拥有一款高效的机械键盘也要搭配一款键盘友好的笔记，所见即所得，可以避免重复鼠标操作，高效简洁&lt;/p&gt;

&lt;p&gt;&lt;img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f3f9c6b6b684058a2127e98dc409b17~tplv-k3u1fbpfcp-zoom-1.image" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一个程序员都在使用的 markdown 笔记，&lt;a href="https://markdowner.net/" rel="nofollow" target="_blank" title=""&gt;码道笔记，做开发者最好的朋友&lt;/a&gt;，欢迎大家体验。&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Mon, 15 Mar 2021 23:29:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/41031</link>
      <guid>https://ruby-china.org/topics/41031</guid>
    </item>
    <item>
      <title>加密技术的未来：从服务端密码存储到用户数据加密方案</title>
      <description>&lt;p&gt;经作者授权转载，&lt;a href="https://markdowner.net/article/158265681589440512" rel="nofollow" target="_blank" title=""&gt;原文链接&lt;/a&gt;，作者：&lt;a href="https://markdowner.net/u/129319864136368128" rel="nofollow" target="_blank" title=""&gt;Roronoa Zoro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文主要讲常见场景的数据加密方案，以及对未来加密技术的展望，先看几条新闻：&lt;/p&gt;

&lt;p&gt;Facebook 明文存储用户密码：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hundreds of millions of Facebook users had their account passwords stored in plain text and searchable by thousands of Facebook employees — in some cases going back to 2012, KrebsOnSecurity has learned. Facebook says an ongoing investigation has so far found no indication that employees have abused access to this data.&lt;/p&gt;

&lt;p&gt;早在 2012 年，Facebook 明文存储数亿用户的账户密码，成千上万的 Facebook 的员工可以随意进行搜索......&lt;/p&gt;

&lt;p&gt;原文：&lt;a href="https://krebsonsecurity.com/tag/plaintext-passwords/" rel="nofollow" target="_blank" title=""&gt;Facebook Stored Hundreds of Millions of User Passwords in Plain Text for Years&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CSDN 600 万用户账号密码泄露：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;北京时间 12 月 21 日晚间消息，中国开发者技术在线社区 CSDN 今晚发表声明，就“600 万用户账号密码泄露”一事公开道歉，承认部分用户账号面临风险，将临时关闭用户登录，并要求“2009 年 4 月以前注册的帐号，且 2010 年 9 月之后没有修改过密码”的用户立即修改密码。&lt;/p&gt;

&lt;p&gt;原文：&lt;a href="https://tech.qq.com/a/20111221/000485.htm" rel="nofollow" target="_blank" title=""&gt;CSDN 详解 600 万用户密码泄露始末：暂关闭登录&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="为什么不能明文存储密码"&gt;为什么不能明文存储密码&lt;/h2&gt;
&lt;p&gt;很多新手程序员都是这样存储密码的：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;username&lt;/th&gt;
&lt;th&gt;phone&lt;/th&gt;
&lt;th&gt;password&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;小明&lt;/td&gt;
&lt;td&gt;18888888888&lt;/td&gt;
&lt;td&gt;asd123456&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大明&lt;/td&gt;
&lt;td&gt;17777777777&lt;/td&gt;
&lt;td&gt;123abc!@#&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;为什么这样做是不安全的？&lt;/p&gt;

&lt;p&gt;首先，如果遇到数据泄露事件，明文密码直接将用户隐私暴露在空中，任何人可以登陆暴露密码的账号，随意更改。其次，即使不会泄露，内部员工也可以轻易访问用户的明文密码，当公司上了规模，你无法保证公司内部没有坏人，他们是否会搜索某些用户的密码，侵犯用户隐私。所以明文存储密码是绝对不安全的。&lt;/p&gt;

&lt;p&gt;即使你用了这样的密码：ppnn13%dkstFeb.1st（娉娉袅袅十三余，豆蔻梢头二月初），明文存储，安全性也是木有的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;再复杂的密码，也敌不过 CSDN 的明文&lt;/p&gt;

&lt;p&gt;来自知乎用户：&lt;a href="https://www.zhihu.com/people/softwaring" rel="nofollow" target="_blank" title=""&gt;Right Here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;题外话：&lt;a href="https://www.zhihu.com/question/21460228" rel="nofollow" target="_blank" title=""&gt;历史上最有名的电脑密码是什么？&lt;/a&gt;&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;密码&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLZX3000cY4yhx9day&lt;/td&gt;
&lt;td&gt;飞流直下三千尺，疑似银河下九天&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hanshansi.location()!∈[gusucity]&lt;/td&gt;
&lt;td&gt;姑苏城外寒山寺&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hold?fish:palm&lt;/td&gt;
&lt;td&gt;鱼和熊掌不可兼得&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree_0f0=sprintf("2_Bird_ff0/a")&lt;/td&gt;
&lt;td&gt;两个黄鹂鸣翠柳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;csbt34.ydhl12s&lt;/td&gt;
&lt;td&gt;池上碧苔三四点，叶底黄鹂一两声&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;for_\$n(@ RenSheng)_\$n+="die"&lt;/td&gt;
&lt;td&gt;人生自古谁无死&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;while(1)Ape1Cry&amp;amp;&amp;amp;Ape2Cry&lt;/td&gt;
&lt;td&gt;两岸猿声啼不住&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;doWhile(1){LeavesFly();YangtzeRiverFlows()};&lt;/td&gt;
&lt;td&gt;无边落木萧萧下，不尽长江滚滚来&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dig?F*ckDang5&lt;/td&gt;
&lt;td&gt;锄禾日当午&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="如何存储 &amp;amp; 检查密码"&gt;如何存储 &amp;amp; 检查密码&lt;/h2&gt;
&lt;p&gt;既然密码不能明文存储，那怎么存储才是安全的？我如何检查用户输入的密码是正确的？&lt;/p&gt;

&lt;p&gt;存储相关信息用于校验是必须的，有没有一种机制能够只保存密码的部分信息，也能用于密码校验？这样即使数据库泄露，攻击者也无法通过这些信息反推用户的密码，进而保护用户账号安全。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B8" rel="nofollow" target="_blank" title=""&gt;哈希函数&lt;/a&gt;（hash function）可以解决这个问题。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/3e6Wnp-038oQV9" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;哈希函数是单向不可逆的，从上图很好理解，经过 hash 函数都会被丢弃一部分信息，就如同这个算法：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;算法：存储用户名时丢弃用户姓氏然后随机打乱顺序，输入赵日天，输出天日。&lt;/p&gt;

&lt;p&gt;即使知道这个算法和天日这个数据，也无法推断出赵日天这个名字，因为部分信息丢失了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;$$
h = hash(p)
$$&lt;/p&gt;

&lt;p&gt;h 为最终存储到数据库的值，p 为用户原始密码，在用户登陆时，输入密码 p1，我们通过计算 $h_1 = hash(p_1)$，判断 h1 是否与数据库中的记录 h 相同，确定用户输入的密码是否正确。&lt;/p&gt;

&lt;p&gt;所有哈希函数都有一个性质：如果两个 h 值是不一致的，那么输入 p 值也不一样（单向散列函数），但另一方面输入和输出并非一一对应的关系，比如存在不同的 h 值，使得经过 hash 函数计算后的 p 值是一样的。&lt;/p&gt;
&lt;h2 id="哈希函数就安全了吗？"&gt;哈希函数就安全了吗？&lt;/h2&gt;
&lt;p&gt;没有，由于上述哈希函数的性质，如果两个用户都用了 123456abc，这样的密码，那么数据库存储的 h 值都是一样的，而且不同密码可能计算出来的 h 值是一样的（碰撞攻击），那么攻击者就可以根据 hash 函数，暴力计算所有可能性，做成一张表格，这样拿到 h 值的时候就可以推断出密码是 123456abc 了。这种做法叫&lt;a href="https://zh.wikipedia.org/zh-hans/%E5%BD%A9%E8%99%B9%E8%A1%A8#:~:text=%E5%BD%A9%E8%99%B9%E8%A1%A8%E6%98%AF%E4%B8%80%E4%B8%AA%E7%94%A8,%E5%A6%82%E4%BF%A1%E7%94%A8%E5%8D%A1%E3%80%81%E6%95%B0%E5%AD%97%E7%AD%89%EF%BC%89%E3%80%82" rel="nofollow" target="_blank" title=""&gt;彩虹表&lt;/a&gt;攻击。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/aOzpRP-zAzv85w" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;比如以前常用的哈希函数 &lt;a href="https://zh.wikipedia.org/wiki/MD5" rel="nofollow" target="_blank" title=""&gt;MD5&lt;/a&gt;，由于计算力逐步增强，现在已经是不安全的了：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MD5 1996 年后被证实存在弱点，可以被加以破解，对于需要高度安全性的资料，专家一般建议改用其他算法，如 SHA-2。2004 年，证实 MD5 算法无法防止碰撞攻击，因此不适用于安全性认证，如 SSL 公开密钥认证或是数字签名等用途&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="安全加固：加盐"&gt;安全加固：加盐&lt;/h2&gt;
&lt;p&gt;对付彩虹表可以用 Sated Hash 的方式，比如对于每个用户密码存储时都记录一个随机的 salt 值（盐值），用这个盐值和密码 p，计算出 h 值：&lt;/p&gt;

&lt;p&gt;$$
h = hash(salt, p)
$$&lt;/p&gt;

&lt;p&gt;数据库同样存储了 salt 值和 h 值，这样攻击者想获取一个用户的密码，就得建立一个对应的彩虹表，增大攻击者的成本。&lt;/p&gt;

&lt;p&gt;但即使这样，使用 SHA-2 再加盐也不是安全的，因为计算力逐年提升，攻击成本下降，有财力的攻击集团还是能够建立这些彩虹表，进而窃取用户密码。&lt;/p&gt;
&lt;h2 id="安全加固：提高计算强度"&gt;安全加固：提高计算强度&lt;/h2&gt;
&lt;p&gt;加盐的方式只是多了独立的彩虹表，如果我们可以利用硬件控制每次 hash 计算的时间比如 1 秒，且无论用什么机器，什么高性能的 CPU 计算，每次都要 1 秒的时间，攻击者要计算这个彩虹表，1000 万个组合就得 115 天（hash 空间远远超过 1000 万），那这种方式就很难破解了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/Pyz9VX-E6nrb6" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/Bcrypt" rel="nofollow" target="_blank" title=""&gt;bcrypt&lt;/a&gt; 是一个由 Niels Provos 以及 David Mazières 根据 Blowfish 加密算法所设计的密码散列函数，于 1999 年在 USENIX 中展示 [1]。实现中 bcrypt 会使用一个加盐的流程以防御彩虹表攻击，同时 bcrypt 还是适应性函数，它可以借由增加迭代之次数来抵御日益增进的电脑运算能力透过暴力法破解。&lt;/p&gt;

&lt;p&gt;除了对您的数据进行加密，默认情况下，bcrypt 在删除数据之前将使用随机数据三次覆盖原始输入文件，以阻挠可能会获得您的计算机数据的人恢复数据的尝试。如果您不想使用此功能，可设置禁用此功能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;除了 bcrypt 这种调整计算强度，抵御日益增长的 CPU 计算力带来攻击风险的算法，scrypt 算法还利用了内存空间，每次计算都要占用一定内容，不过 bcrypt 算法由于有成熟的实现，实际使用较多，spring boot security 的密码加密就用的这个算法。&lt;/p&gt;

&lt;p&gt;比如某个密码经过 bcrypt 加密后变成这样：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;\$2a\$07\$woshiyigesaltzhi\$\$\$\$\$.lrU488y7E1Xw.JA4uizIu.PBSSe7t4y&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2a 表示 bcrypt 算法的版本，07 代表迭代次数，次数越高，每次计算所需的时间越长，后面的 woshiyigesaltzhi$$$$$ 代表加密用的 salt 值，数据库可以直接存储这个字段，比如：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;phone&lt;/th&gt;
&lt;th&gt;pwd_hash&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;小明&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;\$2a\$07\$woshiyigesaltzhi\$\$\$\$\$.lrU488y7E1Xw.JA4uizIu.PBSSe7t4y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这种也是本文推荐的密码存储方式，hash + salt + 计算强度，可以较好保护用户密码安全，由于登陆不是频繁的操作，每次登陆时用户等待 1 秒也没有太大关系。&lt;/p&gt;
&lt;h2 id="用户数据密码加密方案：双重 hash"&gt;用户数据密码加密方案：双重 hash&lt;/h2&gt;
&lt;p&gt;密码信息可以 hash，达到不可逆的目的，但是一些用户数据是可逆的，而且要求加密，怎么办？比如用户在线文档，通过用户自定义的密码加解密。&lt;/p&gt;

&lt;p&gt;很容易想到的就是 &lt;a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" rel="nofollow" target="_blank" title=""&gt;AES256&lt;/a&gt; 之类的对称加密技术：&lt;/p&gt;

&lt;p&gt;$$
e = AES256(salt, text)
$$&lt;/p&gt;

&lt;p&gt;通过拿用户的密码作为 salt 值加解密文档，服务端存储用户密码。&lt;/p&gt;

&lt;p&gt;还是前面的问题，明文存储密码是不安全的行为，这里可以用双重 hash 的方法保证一定的安全性（这里的双重 hash 不是连续计算两次 hash 的意思）：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;实现方案&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;用户密码存储仍然采用前面提到的方案，可以用 bcrypt 算法，这里记存储的值为 h1，当用户请求加密数据时，提供了加密密码，我们通过 h1 检验用户密码的正确性，同时用用户的密码计算出另一个 hash 值，记为 h2，h2 的计算方式与 h1 不同，只是简单的 hash，不能加盐，这时我们使用 h2 加解密用户文档：&lt;/p&gt;

&lt;p&gt;$$
e = AES256(h2, text)
$$&lt;/p&gt;

&lt;p&gt;记住，h2 的值是不能存储的（不能保存于数据库中），用于加解密数据。由于 h2 的值用完就丢掉。h2 的 hash 函数可以是私有的，进一步保障安全性。&lt;/p&gt;

&lt;p&gt;为什么需要 h2 这个参数？首先用户密码长度不一致，像 AES 之类的对称加密算法需要有一个固定长度的加密参数，其次经过 hash 之后能够进一步保障数据安全性，如果数据库泄露，攻击者即使知道用户密码，不知道私有 hash 函数也无法进行解密。&lt;/p&gt;
&lt;h2 id="其他用户数据的加密"&gt;其他用户数据的加密&lt;/h2&gt;
&lt;p&gt;上述提到的加密方案是用户密码控制的，数据安全性非常高（只有用户知道密码，且密码丢失后数据不可恢复），但是这种做法很多场景不适合，比如常规的用户数据：手机号、社交账号、地址、姓名，高频访问的数据不适合用密码加密，效率太低，那如何保护用户这类数据的安全性呢？&lt;/p&gt;

&lt;p&gt;理解这个问题，需要知道用户数据是怎样传输的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/bWxAQB-0GogEm2" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;用户在客户端软件上产生数据（比如浏览器上），经过 https 加密传输到后端服务器，有服务端软件处理（比如 java），之后经过调用数据库接口通过数据库软件（比如 mysql）存储到存储设备中（比如硬盘）。&lt;/p&gt;

&lt;p&gt;有 4 个阶段可以进行数据加密，加密的过程越靠近用户越安全（客户端加密后面说）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务端软件加密：数据到达服务端后立马加密存储（内存中执行），比如 java 执行 AES256&lt;/li&gt;
&lt;li&gt;数据库软件加密：调用数据库 API 实现数据库加密，比如 mysql 的 AES 加密&lt;/li&gt;
&lt;li&gt;存储端落盘加密：使用硬件加密技术进行加密存储，比如云服务商提供的云盘加密功能&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;加密方式&lt;/th&gt;
&lt;th&gt;防止内部泄露&lt;/th&gt;
&lt;th&gt;防止数据库泄露&lt;/th&gt;
&lt;th&gt;防止物理机丢失泄露&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务端软件加密&lt;/td&gt;
&lt;td&gt;√（大部分场景）&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据库软件加密&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存储端落盘加密&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;前两者加密方式都可以保证即使是数据库管理员也无法查看用户数据，最后一种通常意义不大，但一些国家、地区的法律要求，或者用户要求有硬盘加密措施，仍然需要使用。数据库软件加密仍然存在内部泄露的风险，比如 mysql 的 binlog，即使你使用了 AES256，数据同步时密钥也会存储在 binlog 中，存在泄露的途径。&lt;/p&gt;

&lt;p&gt;如果相关用户数据没有搜索的需求（只有常规读写需求），可以使用服务端加密或者数据库加密的手段保护用户数据。但如果相关数据需要支持搜索功能，这个问题就很棘手了。&lt;/p&gt;
&lt;h2 id="可搜索加密技术"&gt;可搜索加密技术&lt;/h2&gt;
&lt;p&gt;这篇论文 &lt;a href="https://ieeexplore.ieee.org/document/848445" rel="nofollow" target="_blank" title=""&gt;Practical techniques for searches on encrypted data&lt;/a&gt; 发表于 2000 年，开创了一个新的研究方向 &lt;a href="https://en.wikipedia.org/wiki/Searchable_symmetric_encryption" rel="nofollow" target="_blank" title=""&gt;Searchable Encryption&lt;/a&gt;，提出了第一个实用的可搜索加密方案 SWP，实现思路大概是：通过加密每个单词，然后将一个 hash 值嵌入密文中，服务器通过提取改 hash 值，检查密文中是否有类似的特殊格式，确认是否匹配搜索。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/4XN9vD-z80xbvg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上文想法很理想，但是落地时会有很多困难，比如必须使用固定大小的单词，但搜索系统最重要的就是其分词引擎，如何对多种语言进行良好的分词直接决定了搜索的效果，不少搜索系统任然采用如下结构：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/qbBn8O-a446qO1" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;将 mysql 的数据单向同步到 elasticsearch 中，完成相关搜索功能的支持。20 多年过去了，今天，可搜索加密技术依然无法落地使用，甚至可以这么说，如果软件商提供了搜索功能，该数据存储的时候就是没有加密的（硬盘加密对软件层不可见），已加密的数据是无法支持搜索功能的。&lt;/p&gt;
&lt;h2 id="阴谋实现的前提"&gt;阴谋实现的前提&lt;/h2&gt;
&lt;p&gt;假如我是一个坏人，要实现某个阴谋，很重要的前提就是操作足够简单，知道的人足够少，当复杂度太大或者需要的人很多的时候就不可能实现这个阴谋。&lt;/p&gt;

&lt;p&gt;知道这一点我们可以很轻松地判断：“美国登月是假的”，这个阴谋猜想是错的。因为登月工程涉及的人很多、工程复杂，无法实现这个阴谋。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这个结论是下文内容的基础。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="一些软件声明的安全说的是什么？"&gt;一些软件声明的安全说的是什么？&lt;/h2&gt;
&lt;p&gt;上述提到的可搜索加密技术无法落地，一些软件厂商（包括一些大厂）提供了搜索功能，仍然声称他们对用户数据进行了加密，很安全，他们到底在说什么？&lt;/p&gt;

&lt;p&gt;第一，他们可能说的是硬盘加密，而不是软件层的加密（只能防御硬盘盗窃风险，不能防御数据泄露风险、内部风险）；第二，他们可能说的是传输过程的加密，比如 https 或私有通讯加密协议；第三，他们可能有完善内部管理流程，控制内外部风险。&lt;/p&gt;

&lt;p&gt;利用上面阴谋论的前提，只要增加一些流程，公开透明出来，提高使坏的成本，在技术不可实现的情况下，也能保障用户数据的安全，比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据库操作日志审查：由其他人审核 DBA 数据库操作是否合规，是否偷偷查看某用户数据等&lt;/li&gt;
&lt;li&gt;多重密码：由多人掌握密码，只有统一输入密码才能解密数据，提高操作复杂度，如多人审核&lt;/li&gt;
&lt;li&gt;公开透明的流程：涉密操作流程记录，比如某某由于开发测试原因，需解密数据&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;拥有完善的流程也能让用户放心存储隐私数据。&lt;/p&gt;
&lt;h2 id="加密技术的未来：不用加密"&gt;加密技术的未来：不用加密&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/bWxAQB-0GogEm2" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;前面没有提到的客户端加密放这里说。加密点越靠近用户，就越安全，如果用户发出来的数据就是经过加密的（非 https 类传输加密），并且自行控制密码，这样服务提供商也无需进一步做加密处理了，标题提到的不用加密指的就是这个，服务商不用再花费大量精力、成本在数据安全上面，将数据所有权交给用户。&lt;/p&gt;

&lt;p&gt;上面客户端加密所需的一个技术就是：&lt;a href="https://zh.wikipedia.org/wiki/%E5%90%8C%E6%80%81%E5%8A%A0%E5%AF%86" rel="nofollow" target="_blank" title=""&gt;全同态加密&lt;/a&gt;，这是密码学领域的一个重要课题，在 2009 年 9 月，IBM 的博士克雷格·金特里发表了一篇论文：&lt;a href="https://www.cs.cmu.edu/~odonnell/hits09/gentry-homomorphic-encryption.pdf" rel="nofollow" target="_blank" title=""&gt;Fully Homomorphic Encryption Using Ideal Lattices&lt;/a&gt;，提出了一个可行方法，自此解决了密码学的一大难题。全同态加密可以简单理解如下：&lt;/p&gt;

&lt;p&gt;$$
f(data) = DE(\ f(\ E(data)\ ))
$$&lt;/p&gt;

&lt;p&gt;其中 f 是任意操作函数，E 是加密函数，DE 是解密函数，也就是说对密文的任意计算操作等同于对明文做同样的操作。&lt;/p&gt;

&lt;p&gt;这就很牛逼了！！！！！！！！！！！比如我手上有一些财务数据，需要第三方机构进行统计分析，但是我又不想直接给他们数据，他们告诉我可以提供全同态加密服务，那么我可以在给他们数据之前将数据加密一遍，他们给我统计计算后的结果，我解密相关结果即可，真实的数据只有我看得到。&lt;/p&gt;

&lt;p&gt;全同态加密的这些特性能够很好解决数据安全，信任的问题，克雷格·金特里给出了一个实现，之后很多密码学者也给出其他实现方法，但目前的角度来说该技术还不成熟，比如一个密钥就得 100 MB 大小，对于当下网络环境来说无法实际使用。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;/h2&gt;
&lt;p&gt;没有绝对的安全，只有相对的安全。期待全同态加密技术在商用领域取得突破性进展。&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Fri, 12 Mar 2021 18:27:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/41022</link>
      <guid>https://ruby-china.org/topics/41022</guid>
    </item>
    <item>
      <title>一个 Markdown 编辑器的诞生</title>
      <description>&lt;h2 id="为什么要开发一款新的编辑器"&gt;为什么要开发一款新的编辑器&lt;/h2&gt;
&lt;p&gt;自从我开始使用 Markdown，就爱上了这种标记语法，轻量、纯文本兼容是最大的优点，哪里都可以编辑，一开始是在 IDE 上直接编辑，后来笔记越来越多，需要上传图片，有云同步、搜索的需求，尝试了 &lt;a href="https://typora.io/" rel="nofollow" target="_blank" title=""&gt;typora&lt;/a&gt;、有道云笔记、印象笔记、为知笔记后，发现各有个的缺点，除了 typora 大多数都是左编辑右预览的分屏模式，这对一个左撇子来说很难受，就像要我用右手拿剪刀裁剪一样，typora 是其中体验最好的，无奈没有在线版本，搜索、云同步、图片上传功能无法实现（或者需要复杂的配置），想到很多人也有类似的痛点，于是拉起团队打算搞在线版的 markdown 编辑器，目标是要达到 typora 类似的编辑体验，甚至更好。&lt;/p&gt;
&lt;h2 id="叫什么名字"&gt;叫什么名字&lt;/h2&gt;
&lt;p&gt;给一个产品起名字确实是很困难的事，既要国际化又要本地化，还得深度结合产品（比如知乎、百度中文含义深，但域名直接使用拼音也决定了产品国际化的困难），日思夜想，无意间想到 &lt;a href="https://markdowner.net/" rel="nofollow" target="_blank" title=""&gt;markdowner&lt;/a&gt; 这个名字，er 后缀结尾有特定人群的意思，如 follower，lover，中文名可以直接取自发音：码道人。码是代码的码，意思是编程道路上的人。nice ! 中文有深意同时解决国际化本地化的难题。&lt;/p&gt;
&lt;h2 id="口号"&gt;口号&lt;/h2&gt;
&lt;p&gt;码道人，开发者最好的朋友：Markdowner is coder‘s best friend.&lt;/p&gt;
&lt;h2 id="最终效果"&gt;最终效果&lt;/h2&gt;
&lt;p&gt;&lt;img title=":grin:" alt="😁" src="https://twemoji.ruby-china.com/2/svg/1f601.svg" class="twemoji"&gt;  先给大家看看效果，满足好奇心&lt;/p&gt;
&lt;h3 id="插入文本样式"&gt;插入文本样式&lt;/h3&gt;
&lt;p&gt;所见即所得，无需使用鼠标和工具栏，正常的 markdown 输入即可得到您想要的样式，全兼容 markdown 语法，没有任何输入负担，让您更专注于内容创作。 &lt;img src="https://content.markdowner.net/pub/V0PNx0-0Da6YGY" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="插入代码块"&gt;插入代码块&lt;/h3&gt;
&lt;p&gt;快捷输入、支持多种语言、自动语法高亮、tab 键自动转换为空格，类似 IDE 的编辑体验 &lt;img src="https://content.markdowner.net/pub/gqW6g1-Ewaya13" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="插入数学公式"&gt;插入数学公式&lt;/h3&gt;
&lt;p&gt;支持 Latex 语法的数学公式，$$ + enter 快捷插入，一边编辑一边预览，高效简洁&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/MrQjO7-RyQ1xwr" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="插入表格"&gt;插入表格&lt;/h3&gt;
&lt;p&gt;全功能的表格编辑体验，避免手动输入字符排版，支持对齐、插入、删除，简洁实用高效。 &lt;img src="https://content.markdowner.net/pub/QXMNEG-E3agrn3" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="自动排版检查（Markdown lint）"&gt;自动排版检查（Markdown lint）&lt;/h3&gt;
&lt;p&gt;自动检查常见排版错误，比如代码块中的 tab 键、不连续的标题跨越、标点符号前空格等，可以查看错误信息，双击提示按钮可自动修复错误，帮助排版出更漂亮的文档。 &lt;img src="https://content.markdowner.net/pub/PekNON-aG4Evj9" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="智能黏贴"&gt;智能黏贴&lt;/h3&gt;
&lt;p&gt;可以直接黏贴来自编辑器或其他网页的内容，自动转换为 markdown 格式，无需再次手工排版。 &lt;img src="https://content.markdowner.net/pub/3O8np1-L6p8ow1" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="源码编辑模式"&gt;源码编辑模式&lt;/h3&gt;
&lt;p&gt;随时可切换到源码编辑模式，复制黏贴原始 markdown 文档，两个编辑器的数据是同步且兼容的。 &lt;img src="https://content.markdowner.net/pub/W069bP-Vn4ODj0" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="图片插入无阻塞"&gt;图片插入无阻塞&lt;/h3&gt;
&lt;p&gt;支持直接拖动上传，操作更加方便，可在上传图片的同时编辑其他区域的文字，无需等待图片上传完成，编辑无阻塞。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/A0N8eg-w9bgg8L" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="响应式布局"&gt;响应式布局&lt;/h3&gt;
&lt;p&gt;全站响应式布局，兼容 Paid 和移动端显示，支持分屏显示，避免频繁切换窗口，边看资料边记录，关注点分离，全程无阻塞。 &lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/jqvEMX-zA8NDmd" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="实现细节"&gt;实现细节&lt;/h2&gt;&lt;h3 id="多端兼容之 Twitter 三段式"&gt;多端兼容之 Twitter 三段式&lt;/h3&gt;
&lt;p&gt;现在移动端用户越来越多，网页多端兼容是必须的，如此一来必须解决一个世界性的 UI 难题：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;移动端水平空间不足、电脑端垂直空间不足（系统任务栏 + 浏览器 tab 栏 + 地址栏 + 收藏夹占用大量垂直空间）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;要想在移动端和电脑端都保持良好的交互体验太难了，我们参考了大量的 UI 设计，其中在 facebook 网页版、twitter 网页版、iPad、虎牙斗鱼网页 APP 版获取到很多想法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;多端设计中电脑端到手机端的跨越无法很好实现，其中最明显的就是导航栏差异太大，点击与触摸交互难以兼容，例如 Facebook 的电脑网页版到手机端 lite 版本，太多体验不一致，导致 lite 版本很鸡肋，但 twitter 就做的很好。&lt;/li&gt;
&lt;li&gt;扁平化设计是必须的，不能有过多灰色、阴影过渡体现层次感，最多只能一层，否则移动端体验太差。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;考虑再三我们决定参考 twitter 三段式的结构解决这个问题（&lt;strong&gt;可能是这个问题的唯一解&lt;/strong&gt;），电脑端的交互体验类似 iPad，这样过渡到移动端较为方便，同时导航栏自由度高，可以用来实现工具栏，垂直空间利用充分。&lt;/p&gt;
&lt;h3 id="分类系统"&gt;分类系统&lt;/h3&gt;
&lt;p&gt;印象笔记的两级分类（笔记本 -&amp;gt; 笔记）在笔记数量较多的时候常常出现列表过长，难以找到相关笔记的问题，码道笔记采用文件树 + 置顶的形式（后续还将支持标签系统），管理笔记更加自由方便，如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/wPN7xy-a4MYqo5" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可以直接进入文件夹，也可以直接展开当前文件树，置顶文件优先显示，支持全局搜索，在文件夹页面、搜索页面都可以实现笔记编辑，多维度管理更加自由。&lt;/p&gt;
&lt;h3 id="内容排版 what you see is what you get"&gt;内容排版 what you see is what you get&lt;/h3&gt;
&lt;p&gt;印象笔记网页版不支持 markdown，app 版本需要分屏显示（大部分的 markdown 笔记产品都是分屏显示，如马克飞象、有道云笔记），分屏最大的缺点就是没有空间显示大纲栏了，长笔记无法很好跳转相应章节，使用体验还不如直接打开 IDE + git + onedrive。&lt;/p&gt;

&lt;p&gt;为知笔记虽然是单屏，但采用编辑、预览切换的方法，实际使用需要重复切换，使用体验更差。码道笔记的编辑器是所见即所得的，单屏显示，编辑体验类似 typora，无需分屏，所写即所得。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://content.markdowner.net/pub/wPNmOL-aVO6pyQ" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在线网址：&lt;a href="https://markdowner.net" rel="nofollow" target="_blank" title=""&gt;码道人&lt;/a&gt;，欢迎大家体验～&lt;/p&gt;</description>
      <author>zhd_superman</author>
      <pubDate>Tue, 09 Mar 2021 18:59:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/41011</link>
      <guid>https://ruby-china.org/topics/41011</guid>
    </item>
  </channel>
</rss>
