<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>webape</title>
  
  
  <link href="https://www.webape.net/atom.xml" rel="self"/>
  
  <link href="https://www.webape.net/"/>
  <updated>2025-10-23T07:46:18.229Z</updated>
  <id>https://www.webape.net/</id>
  
  <author>
    <name>William</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>LN使用BT面板禁止IP访问站点和恶意解析设置</title>
    <link href="https://www.webape.net/LN%E4%BD%BF%E7%94%A8BT%E9%9D%A2%E6%9D%BF%E7%A6%81%E6%AD%A2IP%E8%AE%BF%E9%97%AE%E7%AB%99%E7%82%B9%E5%92%8C%E6%81%B6%E6%84%8F%E8%A7%A3%E6%9E%90%E8%AE%BE%E7%BD%AE/"/>
    <id>https://www.webape.net/LN%E4%BD%BF%E7%94%A8BT%E9%9D%A2%E6%9D%BF%E7%A6%81%E6%AD%A2IP%E8%AE%BF%E9%97%AE%E7%AB%99%E7%82%B9%E5%92%8C%E6%81%B6%E6%84%8F%E8%A7%A3%E6%9E%90%E8%AE%BE%E7%BD%AE/</id>
    <published>2025-10-23T15:36:33.000Z</published>
    <updated>2025-10-23T07:46:18.229Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 <code>LNMP</code> 搭建的 <code>Linux</code> 环境下，安装了 BT 面板来搭建站点，但当我们在服务器上安装了代理软件，我们不想让别人通过代理 IP 直接访问我们的某些站点。需要禁止某些 IP 访问某些站点，或者禁止某些 IP 进行恶意解析，可以按照下面的方法进行设置：</p><p>编辑修改文件<code>/www/server/panel/vhost/nginx/0.default.conf</code>将默认的配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">&#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name _;</span><br><span class="line">    index index.html;</span><br><span class="line">    root /www/server/nginx/html;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">&#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    listen 443 ssl http2;</span><br><span class="line">    server_name _;</span><br><span class="line">    index index.html;</span><br><span class="line">    root /www/server/nginx/html;</span><br><span class="line"></span><br><span class="line">    # DEFAULT SSL CONFIG</span><br><span class="line">    ssl_certificate    /www/server/panel/vhost/cert/0.default/fullchain.pem;</span><br><span class="line">    ssl_certificate_key    /www/server/panel/vhost/cert/0.default/privkey.pem;</span><br><span class="line">    ssl_protocols TLSv1.2 TLSv1.3;</span><br><span class="line">    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;</span><br><span class="line">    ssl_prefer_server_ciphers off;</span><br><span class="line">    ssl_session_cache shared:SSL:10m;</span><br><span class="line">    ssl_session_timeout 10m;</span><br><span class="line">    add_header Strict-Transport-Security &quot;max-age=31536000&quot;;</span><br><span class="line"></span><br><span class="line">    # 关键设置：直接关闭连接</span><br><span class="line">    return 444;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>return 444; 是 Nginx 独有的指令，它会直接断开连接，不返回任何内容，非常适合用于拦截恶意请求 。添加了这一行后，当用户访问你的站点时，Nginx 会直接返回一个空响应，并断开连接。这样，用户将无法访问你的站点，除非被允许访问的 IP 地址。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在使用 &lt;code&gt;LNMP&lt;/code&gt; 搭建的 &lt;code&gt;Linux&lt;/code&gt; 环境下，安装了 BT 面板来搭建站点，但当我们在服务器上安装了代理软件，我们不想让别人通过代理 IP 直接访问我们的某些站点。需要禁止某些 IP 访问某些站点，或者禁止某些 IP 进行</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>C中实现判断ipv4或ipv6地址是单播、组播、广播地址</title>
    <link href="https://www.webape.net/C%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%88%A4%E6%96%ADipv4%E6%88%96ipv6%E5%9C%B0%E5%9D%80%E6%98%AF%E5%8D%95%E6%92%AD%E3%80%81%E7%BB%84%E6%92%AD%E3%80%81%E5%B9%BF%E6%92%AD%E5%9C%B0%E5%9D%80/"/>
    <id>https://www.webape.net/C%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%88%A4%E6%96%ADipv4%E6%88%96ipv6%E5%9C%B0%E5%9D%80%E6%98%AF%E5%8D%95%E6%92%AD%E3%80%81%E7%BB%84%E6%92%AD%E3%80%81%E5%B9%BF%E6%92%AD%E5%9C%B0%E5%9D%80/</id>
    <published>2025-05-20T16:37:34.000Z</published>
    <updated>2025-10-23T07:46:18.228Z</updated>
    
    <content type="html"><![CDATA[<p>在 C 中我们需要实现输入一个字符集（单播、组播、广播地址类型），来判断某 <code>ipv4</code> 或 <code>ipv6</code> 地址的类型是否在指定的类型集内。</p><p>为方便用户输入，类型的字符集我们定义为：<br>1：单播地址<br>2：组播地址<br>3：广播地址</p><p>可以输入多个类型，以英文逗号分隔，如：1,2,3</p><p>首先定义处理字符串类型匹配的函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 判断目标数字是否在字符串中</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @param target</span></span><br><span class="line"><span class="comment"> * @param data</span></span><br><span class="line"><span class="comment"> * @return true</span></span><br><span class="line"><span class="comment"> * @return false</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">bool</span> <span class="title function_">pp_in_string</span><span class="params">(<span class="type">int</span> target, <span class="type">char</span> *data)</span> &#123;</span><br><span class="line">  <span class="type">bool</span> found = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">if</span> (data == <span class="literal">NULL</span>) <span class="keyword">goto</span> end;</span><br><span class="line"></span><br><span class="line">  <span class="type">char</span> str[<span class="number">128</span>];</span><br><span class="line">  <span class="comment">// 避免字符串拷贝溢出</span></span><br><span class="line">  <span class="built_in">strncpy</span>(str, data, <span class="keyword">sizeof</span>(data) - <span class="number">1</span>);</span><br><span class="line">  str[<span class="keyword">sizeof</span>(str) - <span class="number">1</span>] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="type">char</span> *token = strtok(str, <span class="string">&quot;,&quot;</span>);</span><br><span class="line">  <span class="keyword">while</span> (token != <span class="literal">NULL</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (atoi(token) == target) &#123;</span><br><span class="line">      found = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    token = strtok(<span class="literal">NULL</span>, <span class="string">&quot;,&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">end:</span><br><span class="line">  <span class="keyword">return</span> found;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定义地址类型判断方法：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 检查地址是否在指定范围,判断是否为单播、组播、广播地址</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @param family</span></span><br><span class="line"><span class="comment"> * @param ip ipv4 或 ipv6 地址，分别以“struct in_addr”、&quot;struct in6_addr&quot;的形式传入，统一类型为__u32</span></span><br><span class="line"><span class="comment"> * @param range_type</span></span><br><span class="line"><span class="comment"> * @return true</span></span><br><span class="line"><span class="comment"> * @return false</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">bool</span> <span class="title function_">address_check_range_type</span><span class="params">(<span class="type">int</span> family, <span class="type">unsigned</span> <span class="type">int</span> *ip, <span class="type">char</span> *range_type)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (range_type[<span class="number">0</span>] == <span class="string">&#x27;\0&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 默认为单播1单播，2组播，3广播</span></span><br><span class="line">  <span class="type">int</span> result = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (family == AF_INET) &#123;</span><br><span class="line">    <span class="keyword">if</span> (ntohl(ip[<span class="number">0</span>]) == <span class="number">0xFFFFFFFF</span>) &#123;</span><br><span class="line">      result = <span class="number">3</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((ntohl(ip[<span class="number">0</span>]) &amp; <span class="number">0xF0000000</span>) == ((<span class="type">uint32_t</span>)(<span class="number">0xE0</span> &lt;&lt; <span class="number">24</span>))) &#123;</span><br><span class="line">      result = <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (family == AF_INET6) &#123;</span><br><span class="line">    <span class="comment">// ipv6的格式是分8段，首段是16位，共128位的值，组播需要判断其前8位需要全是1</span></span><br><span class="line">    <span class="comment">// unsigned int是32位的，不能直接使用ip[0]==oxFF去判断，需要转换后去判断</span></span><br><span class="line">    <span class="keyword">struct</span> in6_addr  *addr_v6 = (<span class="keyword">struct</span> in6_addr *)ip;</span><br><span class="line">    <span class="keyword">if</span> (addr_v6-&gt;s6_addr[<span class="number">0</span>] == <span class="number">0xFF</span>) &#123;</span><br><span class="line">      result = <span class="number">2</span>;</span><br><span class="line">      <span class="comment">// ipv6的广播使用组播实现</span></span><br><span class="line">      <span class="keyword">if</span> (pp_in_string(<span class="number">3</span>, range_type)) &#123;</span><br><span class="line">        result = <span class="number">3</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> pp_in_string(result, range_type);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的在 <code>ipv6</code> 格式下比较，需要转换一下再去判断。</p><p><code>ipv6</code> 的地址结构体为：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in6_addr</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">union</span> &#123;</span></span><br><span class="line">        <span class="type">uint8_t</span>   s6_addr[<span class="number">16</span>];   <span class="comment">// 128-bit address</span></span><br><span class="line">        <span class="type">uint16_t</span>  s6_addr16[<span class="number">8</span>];  <span class="comment">// access with uint16_t</span></span><br><span class="line">        <span class="type">uint32_t</span>  s6_addr32[<span class="number">4</span>];  <span class="comment">// access with uint32_t</span></span><br><span class="line">    &#125; __u6_addr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以我们需要使用 <code>in6_addr</code> 结构体中的 <code>s6_addr</code> 去判断，正好是 <code>128</code> 位的 <code>ipv6</code> 地址表示值，而且分了 <code>16</code> 个数组，所以每组即 <code>8</code> 位，正使满足我们比较前 <code>8</code> 位需要全是 <code>1</code> 的条件。而如果我们使用的 <code>s6_addr32</code>，那么每组是 <code>32</code> 位，共 <code>4</code> 组，不能满足判断前 <code>8</code> 位的条件。</p><p><code>ipv6</code> 的地址数据格式为：<code>AA22:BB11:1122:CDED:1234:AA99:7654:7410</code></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在 C 中我们需要实现输入一个字符集（单播、组播、广播地址类型），来判断某 &lt;code&gt;ipv4&lt;/code&gt; 或 &lt;code&gt;ipv6&lt;/code&gt; 地址的类型是否在指定的类型集内。&lt;/p&gt;
&lt;p&gt;为方便用户输入，类型的字符集我们定义为：&lt;br&gt;
1：单播地址&lt;br&gt;
2</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>从本地测试到私有仓库发布的NPM包开发</title>
    <link href="https://www.webape.net/%E4%BB%8E%E6%9C%AC%E5%9C%B0%E6%B5%8B%E8%AF%95%E5%88%B0%E7%A7%81%E6%9C%89%E4%BB%93%E5%BA%93%E5%8F%91%E5%B8%83%E7%9A%84NPM%E5%8C%85%E5%BC%80%E5%8F%91/"/>
    <id>https://www.webape.net/%E4%BB%8E%E6%9C%AC%E5%9C%B0%E6%B5%8B%E8%AF%95%E5%88%B0%E7%A7%81%E6%9C%89%E4%BB%93%E5%BA%93%E5%8F%91%E5%B8%83%E7%9A%84NPM%E5%8C%85%E5%BC%80%E5%8F%91/</id>
    <published>2025-04-01T14:42:27.000Z</published>
    <updated>2025-10-23T07:46:18.231Z</updated>
    
    <content type="html"><![CDATA[<p>我们在使用 <code>node</code> 开发过程中，经常会下载很多第三方包，那些包都是别人封装好的，我们只需要引用即可使用，但是有些时候，我们需要自己封装一些包，这个时候，我们就需要自己动手做一个 <code>npm</code> 包了。</p><p>下面我将介绍如何做一个 <code>npm</code> 包，以及 <code>npm</code> 包发布。</p><p>首先是一个项目的目录结构，可以看出和我们平时开发 <code>vue</code> 项目很像，只是多了一个<code>packages</code>目录，用来存放我们的主要项目文件。</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">www</span><br><span class="line">├─lib                  模块集编译目录</span><br><span class="line">├─node_modules         扩展包文件目录</span><br><span class="line">├─packages             工具包目录</span><br><span class="line">├─public               入口文件目录</span><br><span class="line">│  ├─favicon.ico       站点图标</span><br><span class="line">│  └─index.html        入口文件</span><br><span class="line">├─src                  页面主目录</span><br><span class="line">│  ├─router            路由文件夹</span><br><span class="line">│  ├─views             页面文件夹</span><br><span class="line">│  ├─app.vue           主页入口</span><br><span class="line">│  └─main.js           主核心文件</span><br><span class="line">├─test                 自动化测试用例目录</span><br><span class="line">│  ├─unit              单元测试用例目录</span><br><span class="line">│  └─...               更多</span><br><span class="line">├─.eslintignore        代码格式检测忽略配置文件</span><br><span class="line">├─.eslintrc.js         代码格式检测配置文件</span><br><span class="line">├─.gitignore           git忽略配置文件</span><br><span class="line">├─.npmignore           npm忽略配置文件</span><br><span class="line">├─.prettierrc.js       IDE格式化代码配置文件</span><br><span class="line">├─babel.config.js      JS编译器babel配置文件</span><br><span class="line">├─jsconfig.json        JavaScript 语言服务配置选项文件</span><br><span class="line">├─package.json         项目包的所有相关信息文件</span><br><span class="line">├─pnpm-lock.yaml       pnpm包锁文件</span><br><span class="line">├─README.md            README 文件</span><br><span class="line">└─vue.config.js        项目配置文件</span><br></pre></td></tr></table></figure><p>在这个项目中，我们需要将我们对外提供的内容都放到 <code>packages</code> 中，其中 <code>/packages/index.js</code> 中统一引入后对外暴露。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// /packages/index.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">&quot;./utils/api&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&quot;./axios/index&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123; <span class="attr">api</span>: api, <span class="attr">axios</span>: axios &#125;;</span><br></pre></td></tr></table></figure><p>其中 <code>/src/</code> 目录下，也可以引入自已 <code>packages</code> 中的库，进行本地页面上测试，或在 <code>/test/</code> 目录中编用库的单元测试用例进行验证。</p><p>我们需要重点关注以下几个文件的内容：</p><p><strong>1、.npmrc</strong></p><p>这个文件里定义了 <code>npm</code> 的注册地址，默认是 <code>https://registry.npmjs.org/</code>。</p><p>如果要发布到 <code>npm</code> 的私有仓库，需要修改为私有仓库的注册地址。</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">registry=https://registry.npmjs.org/</span><br></pre></td></tr></table></figure><p><strong>2、package.json</strong></p><p>这个文件里定义了包的一些信息，比如版本号，描述，作者，许可证等，其中脚本 <code>lib</code> 就是将 <code>packages/index.js</code> 进行编译后，输出到 <code>lib</code> 目录下，供其他项目引用的处理命令。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@webape/api&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;api请求库&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;api&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;webape&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;webape@qq.com&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ISC&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;publishConfig&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;@webape:registry&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://npm.webape&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lib/ng.umd.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;serve&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-cli-service serve&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-cli-service build&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;test:unit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-cli-service test:unit&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;lib&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-cli-service build --target lib --name ng --dest lib packages/index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-cli-service lint src packages&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;axios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.4.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;core-js&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.32.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;crypto-js&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;element-ui&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.15.13&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;qs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^6.11.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.7.14&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;vue-router&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.6.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;vuex&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.6.2&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;@babel/core&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.22.10&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@babel/eslint-parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.22.10&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@babel/plugin-proposal-nullish-coalescing-operator&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.18.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-babel&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-router&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-unit-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-vuex&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-service&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/eslint-config-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^6.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/test-utils&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.3.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/vue2-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;babel-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.5.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;babel-polyfill&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^6.26.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.32.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;eslint-config-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.10.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;eslint-plugin-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;eslint-plugin-vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.7.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.5.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;jest-serializer-vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.0.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;less-loader&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;msw&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.2.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.8.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;vue-template-compiler&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.7.14&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslintConfig&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;root&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;plugin:vue/essential&quot;</span><span class="punctuation">,</span> <span class="string">&quot;eslint:recommended&quot;</span><span class="punctuation">,</span> <span class="string">&quot;@vue/prettier&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;parserOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@babel/eslint-parser&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;rules&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;overrides&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;files&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;**/__tests__/*.&#123;j,t&#125;s?(x)&quot;</span><span class="punctuation">,</span> <span class="string">&quot;**/tests/unit/**/*.spec.&#123;j,t&#125;s?(x)&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;browserslist&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&gt; 1%&quot;</span><span class="punctuation">,</span> <span class="string">&quot;last 2 versions&quot;</span><span class="punctuation">,</span> <span class="string">&quot;not dead&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;preset&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@vue/cli-plugin-unit-jest&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;moduleNameMapper&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;^@tests/(.*)$&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;rootDir&gt;/tests/$1&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;^@packages/(.*)$&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;rootDir&gt;/packages/$1&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;transformIgnorePatterns&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;/node_modules[/\\\\]/&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>3、.npmignore</strong></p><p>这个文件里定义了 <code>npm</code> 推送包时需要忽略的文件列表，默认是忽略 <code>node_modules</code> 目录下的文件，这里可以忽略其他不需要发布的文件。</p><p>一般情况下，我们只需要推送 <code>package.json</code> 文件和 <code>lib</code> 文件夹即可，所以可以添加如下内容：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">dist</span><br><span class="line">node_modules</span><br><span class="line">packages</span><br><span class="line">pnpm-store</span><br><span class="line">public</span><br><span class="line">src</span><br><span class="line">tests</span><br><span class="line">.env.*</span><br><span class="line">.eslintignore</span><br><span class="line">.eslintrc.js</span><br><span class="line">.gitignore</span><br><span class="line">.npmignore.js</span><br><span class="line">.npmrc.js</span><br><span class="line">.prettierrc.js</span><br><span class="line">babel.config.js</span><br><span class="line">devCookie.txt</span><br><span class="line">jsconfig.json</span><br><span class="line">pnpm-lock.yaml</span><br><span class="line">vue.config.js</span><br></pre></td></tr></table></figure><p><strong>4、vue.config.js</strong></p><p>这个文件里定义了 <code>vue-cli</code> 的配置，这里可以定义一些打包时的配置，比如打包时的路径，打包时的环境变量等。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; defineConfig &#125; = <span class="built_in">require</span>(<span class="string">&quot;@vue/cli-service&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">resolve</span>(<span class="params">dir</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> path.<span class="title function_">join</span>(__dirname, dir);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Timestamp</span> = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">    <span class="attr">publicPath</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&quot;production&quot;</span> ? <span class="string">&quot;/page/embed/&quot;</span> : <span class="string">&quot;./&quot;</span>,</span><br><span class="line">    <span class="attr">productionSourceMap</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>,</span><br><span class="line">    <span class="attr">outputDir</span>: <span class="string">&quot;dist&quot;</span>,</span><br><span class="line">    <span class="attr">assetsDir</span>: <span class="string">&quot;static&quot;</span>,</span><br><span class="line">    <span class="attr">lintOnSave</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">devServer</span>: &#123;</span><br><span class="line">        <span class="attr">host</span>: <span class="string">&quot;0.0.0.0&quot;</span>,</span><br><span class="line">        <span class="attr">port</span>: <span class="number">8080</span>,</span><br><span class="line">        <span class="attr">compress</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">proxy</span>: &#123;</span><br><span class="line">            <span class="string">&quot;/api&quot;</span>: &#123;</span><br><span class="line">                <span class="attr">target</span>: <span class="string">&quot;https://webapet.net/api/&quot;</span>,</span><br><span class="line">                <span class="attr">changeOrigin</span>: <span class="literal">true</span>,</span><br><span class="line">                <span class="attr">pathRewrite</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;^/api&quot;</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="attr">secure</span>: <span class="literal">false</span>,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">chainWebpack</span>: <span class="function">(<span class="params">config</span>) =&gt;</span> &#123;</span><br><span class="line">        config.<span class="property">resolve</span>.<span class="property">alias</span>.<span class="title function_">set</span>(<span class="string">&quot;@&quot;</span>, <span class="title function_">resolve</span>(<span class="string">&quot;src&quot;</span>));</span><br><span class="line">        config.<span class="property">resolve</span>.<span class="property">alias</span>.<span class="title function_">set</span>(<span class="string">&quot;@packages&quot;</span>, <span class="title function_">resolve</span>(<span class="string">&quot;packages&quot;</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 删除预加载和预获取</span></span><br><span class="line">        config.<span class="property">plugins</span>.<span class="title function_">delete</span>(<span class="string">&quot;preload&quot;</span>);</span><br><span class="line">        config.<span class="property">plugins</span>.<span class="title function_">delete</span>(<span class="string">&quot;prefetch&quot;</span>);</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="attr">configureWebpack</span>: &#123;</span><br><span class="line">        <span class="attr">performance</span>: &#123;</span><br><span class="line">            <span class="attr">hints</span>: <span class="string">&quot;warning&quot;</span>,</span><br><span class="line">            <span class="attr">maxEntrypointSize</span>: <span class="number">50000000</span>,</span><br><span class="line">            <span class="attr">maxAssetSize</span>: <span class="number">30000000</span>,</span><br><span class="line">            <span class="attr">assetFilter</span>: <span class="keyword">function</span> (<span class="params">assetFilename</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> assetFilename.<span class="title function_">endsWith</span>(<span class="string">&quot;.js&quot;</span>);</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 以避免构建后的代码中出现未转译的第三方依赖</span></span><br><span class="line">    <span class="attr">transpileDependencies</span>: [<span class="regexp">/node_modules[/\\\\]/</span>],</span><br><span class="line"></span><br><span class="line">    <span class="attr">css</span>: &#123;</span><br><span class="line">        <span class="attr">extract</span>: &#123;</span><br><span class="line">            <span class="attr">filename</span>: <span class="string">`./static/css/[name].<span class="subst">$&#123;process.env.VUE_APP_VERSION&#125;</span>.<span class="subst">$&#123;Timestamp&#125;</span>.css`</span>,</span><br><span class="line">            <span class="attr">chunkFilename</span>: <span class="string">`./static/css/[name].<span class="subst">$&#123;process.env.VUE_APP_VERSION&#125;</span>.<span class="subst">$&#123;Timestamp&#125;</span>.css`</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里有项目代码根目录的部分文件<a href="/static/file/npm-file.zip">示例源文件</a></p><p>至于想自已搭建私有 <code>npm</code> 仓库，可以查看<a href="/%E5%88%A9%E7%94%A8-verdaccio-%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89npm%E5%B9%B3%E5%8F%B0%E5%8F%8A%E5%9C%A8%E5%86%85%E7%BD%91%E4%B8%AD%E4%BD%BF%E7%94%A8">npm 私服搭建</a></p><p><strong>构建</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run lib</span><br></pre></td></tr></table></figure><p><strong>推送</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm publish</span><br></pre></td></tr></table></figure><p>一些 <code>npm</code> 常用操作命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">强制删除指定版本的库</span></span><br><span class="line">npm unpublish @webape/demo@0.0.1 --force</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">强制删除库</span></span><br><span class="line">npm unpublish @webape/demo --force</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">废弃指定版本的库</span></span><br><span class="line">npm deprecate @webape/demo@0.0.1 &#x27;不在更新了&#x27;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">注册用户</span></span><br><span class="line">npm adduser --registry http://npm.webape</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">登录用户</span></span><br><span class="line">npm login --registry http://npm.webape</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置新密码</span></span><br><span class="line">npm profile set password</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我们在使用 &lt;code&gt;node&lt;/code&gt; 开发过程中，经常会下载很多第三方包，那些包都是别人封装好的，我们只需要引用即可使用，但是有些时候，我们需要自己封装一些包，这个时候，我们就需要自己动手做一个 &lt;code&gt;npm&lt;/code&gt; 包了。&lt;/p&gt;
&lt;p&gt;下面我将介绍</summary>
      
    
    
    
    <category term="npm" scheme="https://www.webape.net/categories/npm/"/>
    
    
  </entry>
  
  <entry>
    <title>实现 Axios 同步请求：兼容 XHR 的队列式解决方案</title>
    <link href="https://www.webape.net/%E5%AE%9E%E7%8E%B0-Axios-%E5%90%8C%E6%AD%A5%E8%AF%B7%E6%B1%82%EF%BC%9A%E5%85%BC%E5%AE%B9-XHR-%E7%9A%84%E9%98%9F%E5%88%97%E5%BC%8F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://www.webape.net/%E5%AE%9E%E7%8E%B0-Axios-%E5%90%8C%E6%AD%A5%E8%AF%B7%E6%B1%82%EF%BC%9A%E5%85%BC%E5%AE%B9-XHR-%E7%9A%84%E9%98%9F%E5%88%97%E5%BC%8F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2025-04-01T13:26:37.000Z</published>
    <updated>2025-10-23T07:46:18.232Z</updated>
    
    <content type="html"><![CDATA[<p>在我们开发 <code>vue</code> 应用项目时，<code>axios</code> 是一个非常好用的 <code>HTTP</code> 请求库，我们可以用它来发送 <code>HTTP</code> 请求，但是在某些情况下，我们需要将使用 <code>axios</code> 的所有 <code>HTTP</code> 请求变为同步请求，例如在适配某些项目时，后端接口是对 <code>token</code> 进行单线加密验证的场景。</p><p>为了 <code>api</code> 库能够在多个模块中使用，便于统一修改，我们可以封装一下 <code>axios</code> 的请求。</p><p>在适配某些其他项目时，其他项目也有可能发送 <code>XHR</code> 请求，但我们又无法管控和修改其他项目的代码，所以我们要获取当前是否有 <code>XHR</code> 请求，如果有，则需要排队等待上一个请求响应结束后才会去发送下一个请求。</p><p>这里我们需要一个 <code>XHR</code> 检测工具：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// axios/xhr-detect.js</span></span><br><span class="line"><span class="comment">// 避免重复处理</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span>.<span class="property">__isPatched</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title class_">OriginalXHR</span> = <span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hasOwnProperty</span>.<span class="title function_">call</span>(<span class="variable language_">window</span>, <span class="string">&quot;WA_activeXHRCount&quot;</span>)) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">WA_activeXHRCount</span> = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重写XMLHttpRequest构造函数</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> xhr = <span class="keyword">new</span> <span class="title class_">OriginalXHR</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 保存原生send方法</span></span><br><span class="line">        <span class="keyword">const</span> originalSend = xhr.<span class="property">send</span>.<span class="title function_">bind</span>(xhr);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 重写send方法以计数</span></span><br><span class="line">        xhr.<span class="property">send</span> = <span class="keyword">function</span> (<span class="params">...args</span>) &#123;</span><br><span class="line">            <span class="variable language_">window</span>.<span class="property">WA_activeXHRCount</span>++;</span><br><span class="line">            <span class="title function_">originalSend</span>(...args);</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 请求结束时减少计数器</span></span><br><span class="line">        <span class="keyword">const</span> <span class="title function_">decrement</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">            <span class="variable language_">window</span>.<span class="property">WA_activeXHRCount</span>--;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 监听所有结束事件</span></span><br><span class="line">        xhr.<span class="title function_">addEventListener</span>(<span class="string">&quot;load&quot;</span>, decrement);</span><br><span class="line">        xhr.<span class="title function_">addEventListener</span>(<span class="string">&quot;error&quot;</span>, decrement);</span><br><span class="line">        xhr.<span class="title function_">addEventListener</span>(<span class="string">&quot;abort&quot;</span>, decrement);</span><br><span class="line">        xhr.<span class="title function_">addEventListener</span>(<span class="string">&quot;timeout&quot;</span>, decrement);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> xhr;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标记已修补，避免重复</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span>.<span class="property">__isPatched</span> = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 提供检查方法</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">getActiveXHRCount</span> = <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="property">WA_activeXHRCount</span>;</span><br></pre></td></tr></table></figure><p>接下来我们要封装 <code>axios</code> 请求，达到所有使用该库发送请求的 <code>api</code> 请求都变成同步请求的目的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// axios/index.js</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&quot;axios&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">&quot;../utils/api&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;./xhr-detect&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否是&#123;&#125;样的对象</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">isObject</span> = (<span class="params">obj</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> obj !== <span class="literal">null</span> &amp;&amp; <span class="keyword">typeof</span> obj === <span class="string">&quot;object&quot;</span> &amp;&amp; !<span class="title class_">Array</span>.<span class="title function_">isArray</span>(obj);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检测函数是否在api对外暴露的方法集合中</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">isFunIn</span> = (<span class="params">fun</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> name = fun.<span class="property">name</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Object</span>.<span class="title function_">keys</span>(api).<span class="title function_">indexOf</span>(name) &gt; -<span class="number">1</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> defaultConfig = &#123;</span><br><span class="line">    <span class="attr">baseURL</span>: <span class="string">&quot;/&quot;</span> + process.<span class="property">env</span>.<span class="property">VUE_APP_API_PRE</span>,</span><br><span class="line">    <span class="attr">timeout</span>: <span class="number">1000</span> * <span class="number">15</span>,</span><br><span class="line">    <span class="comment">// 跨域请求时携带cookie凭证</span></span><br><span class="line">    <span class="attr">withCredentials</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-www-form-urlencoded&quot;</span>,</span><br><span class="line">        <span class="string">&quot;X-Requested-With&quot;</span>: <span class="string">&quot;XMLHttpRequest&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> service = axios.<span class="title function_">create</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义公共变量</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hasOwnProperty</span>.<span class="title function_">call</span>(<span class="variable language_">window</span>, <span class="string">&quot;WA_requestQueue&quot;</span>)) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">WA_requestQueue</span> = [];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (!<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hasOwnProperty</span>.<span class="title function_">call</span>(<span class="variable language_">window</span>, <span class="string">&quot;WA_requestQueue&quot;</span>)) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">WA_isRequesting</span> = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 队列处理</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">processQueue</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 在当前有axios在请求中时、请求队列没有数据了，都暂不处理</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">WA_isRequesting</span> || <span class="variable language_">window</span>.<span class="property">WA_requestQueue</span>.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果平台有未完成的xhr请求时，延迟处理</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="title function_">getActiveXHRCount</span>() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="title function_">processQueue</span>();</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">WA_isRequesting</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">const</span> &#123; config, fun, resolve, reject &#125; = <span class="variable language_">window</span>.<span class="property">WA_requestQueue</span>.<span class="title function_">shift</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> options;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isObject</span>(config) &amp;&amp; fun === <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 传递的纯axios的配置对象</span></span><br><span class="line">        options = &#123; ...config &#125;;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(config) &amp;&amp; <span class="keyword">typeof</span> fun === <span class="string">&quot;function&quot;</span> &amp;&amp; <span class="title function_">isFunIn</span>(fun)) &#123;</span><br><span class="line">        <span class="comment">// 传递的api处理函数，在队列中实时计算数据</span></span><br><span class="line">        options = <span class="title function_">fun</span>(...config);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;request参数错误,请检查api配置&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    options = <span class="title class_">Object</span>.<span class="title function_">assign</span>(defaultConfig, options);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">service</span>(options)</span><br><span class="line">        .<span class="title function_">then</span>(<span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="title function_">resolve</span>(response);</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="title function_">reject</span>(error);</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">window</span>.<span class="property">WA_isRequesting</span> = <span class="literal">false</span>;</span><br><span class="line">            <span class="title function_">processQueue</span>();</span><br><span class="line">        &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 请求处理方法</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">*</span>&#125; config 可以是axios的对象配置，也可以是api对外暴露的函数参数值数组按顺序传递</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">*</span>&#125; fun api对象暴露的函数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">request</span> = (<span class="params">config = &#123;&#125;, fun = <span class="literal">null</span></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">WA_requestQueue</span>.<span class="title function_">push</span>(&#123; config, fun, resolve, reject &#125;);</span><br><span class="line">        <span class="title function_">processQueue</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置默认的请求配置</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">setDefaultOption</span> = (<span class="params">option</span>) =&gt; &#123;</span><br><span class="line">    defaultConfig = <span class="title class_">Object</span>.<span class="title function_">assign</span>(defaultConfig, option);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123; <span class="attr">request</span>: request, <span class="attr">service</span>: service, <span class="attr">setDefaultOption</span>: setDefaultOption &#125;;</span><br></pre></td></tr></table></figure><div class="tips"><p><strong>提示</strong><br>上面的有个 <code>api</code> 库，是对外暴露了一些处理方法，因为后端特定设计的原因，需要我们对传递的数据进行动态加密，而且不同的相请求，处理方法不一样，例如文件上传、命令调用等，需要有不同的处理方法，所以，我们把处理方法封装到 <code>api</code> 中，然后通过 <code>api</code> 暴露给外部使用，这样，我们的代码就清晰了很多。所以 <code>api</code> 一个函数集合，在上面的代码中也有用于判断函数是否在预计中的逻辑。</p></div><p>我们需要将该模块库中的所有的处理方法及 <code>axios</code> 库一起对外暴露</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">&quot;./utils/api&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&quot;./axios/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123; <span class="attr">api</span>: api, <span class="attr">axios</span>: axios &#125;;</span><br></pre></td></tr></table></figure><p>将上面的库构建后，可以在具体的应用项目中进行使用。</p><p>这样我们就实现了将 <code>axios</code> 的所有请求变为同步请求，而且不影响 <code>axios</code> 的请求与响应的拦截器的定义，在定义 <code>api</code> 中可以像下面这样使用。</p><p>首先，在我们的项目中需要定义一个统一的 <code>api</code> 处理器</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// utils/request.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 引入前面推送的库</span></span><br><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">&quot;@webape/api&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建axios实例</span></span><br><span class="line"><span class="keyword">const</span> service = api.<span class="property">axios</span>.<span class="property">service</span>;</span><br><span class="line"></span><br><span class="line">api.<span class="property">axios</span>.<span class="title function_">setDefaultOption</span>(&#123;</span><br><span class="line">    <span class="attr">baseURL</span>: <span class="string">&quot;/&quot;</span> + process.<span class="property">env</span>.<span class="property">VUE_APP_API_PRE</span>,</span><br><span class="line">    <span class="attr">timeout</span>: <span class="number">10000</span>,</span><br><span class="line">    <span class="comment">// 跨域请求时携带cookie凭证</span></span><br><span class="line">    <span class="attr">withCredentials</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-www-form-urlencoded&quot;</span>,</span><br><span class="line">        <span class="string">&quot;X-Requested-With&quot;</span>: <span class="string">&quot;XMLHttpRequest&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// request拦截器</span></span><br><span class="line">service.<span class="property">interceptors</span>.<span class="property">request</span>.<span class="title function_">use</span>(</span><br><span class="line">    <span class="function">(<span class="params">config</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">return</span> config;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">    &#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// response拦截器</span></span><br><span class="line">service.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">use</span>(</span><br><span class="line">    <span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> &#123; data &#125; = response;</span><br><span class="line">        <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(data) === <span class="string">&quot;[object Array]&quot;</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(data);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&quot;数据结构异常&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 判断请求异常信息中是否含有超时timeout字符串</span></span><br><span class="line">        <span class="keyword">if</span> (error.<span class="property">message</span>.<span class="title function_">includes</span>(<span class="string">&quot;timeout&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&quot;请求超时,请稍后再试&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (error.<span class="property">message</span>.<span class="title function_">includes</span>(<span class="string">&quot;NetworkError&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&quot;网络错误,请稍后再试&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> api.<span class="property">axios</span>.<span class="property">request</span>;</span><br></pre></td></tr></table></figure><p>然后在应用项目的 <code>api</code> 定义文件中像下面这样使用：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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> request <span class="keyword">from</span> <span class="string">&quot;@/utils/request&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">&quot;@webape/api&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用函数处理方式</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">show</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">request</span>([<span class="string">&quot;commandXml函数参数param1&quot;</span>, <span class="string">&quot;commandXml函数参数param2&quot;</span>, <span class="string">&quot;commandXml函数参数param3&quot;</span>], api.<span class="property">api</span>.<span class="property">commandXml</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 直接使用axios的请求参数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">show2</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">request</span>(&#123; <span class="attr">url</span>: <span class="string">&quot;/webape.net/api/commandXml&quot;</span>, <span class="attr">method</span>: <span class="string">&quot;post&quot;</span>, <span class="attr">data</span>: &#123; <span class="attr">webape</span>: <span class="string">&quot;webape&quot;</span> &#125; &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后就可以在具体的 <code>vue</code> 文件中 <code>import</code> 后进行使用了，这样只要使用封装的 <code>axios</code> 库的请求，都会被处理为同步请求，而且兼容在同一个项目中，有其他不被管控制的 <code>XHR</code> 请求，也会等待串行处理。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在我们开发 &lt;code&gt;vue&lt;/code&gt; 应用项目时，&lt;code&gt;axios&lt;/code&gt; 是一个非常好用的 &lt;code&gt;HTTP&lt;/code&gt; 请求库，我们可以用它来发送 &lt;code&gt;HTTP&lt;/code&gt; 请求，但是在某些情况下，我们需要将使用 &lt;code&gt;axio</summary>
      
    
    
    
    <category term="axios" scheme="https://www.webape.net/categories/axios/"/>
    
    
    <category term="同步" scheme="https://www.webape.net/tags/%E5%90%8C%E6%AD%A5/"/>
    
  </entry>
  
  <entry>
    <title>在ubuntu上安装Qemu来创建虚拟机及实现剪切板共享</title>
    <link href="https://www.webape.net/%E5%9C%A8ubuntu%E4%B8%8A%E5%AE%89%E8%A3%85Qemu%E6%9D%A5%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%89%AA%E5%88%87%E6%9D%BF%E5%85%B1%E4%BA%AB/"/>
    <id>https://www.webape.net/%E5%9C%A8ubuntu%E4%B8%8A%E5%AE%89%E8%A3%85Qemu%E6%9D%A5%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%89%AA%E5%88%87%E6%9D%BF%E5%85%B1%E4%BA%AB/</id>
    <published>2025-03-31T19:23:59.000Z</published>
    <updated>2025-10-23T07:46:18.232Z</updated>
    
    <content type="html"><![CDATA[<p>在 <code>windows</code> 系统上我们使用虚拟机来运行一些软件，比如我们可以使用 <code>VMware</code> 来创建我们的虚拟机，而且在 <code>windows</code> 平台上有很多破解版可以让我们免费使用 <code>VMware</code>。</p><p>但在 <code>Linux</code> 系统上，例如 <code>ubuntu</code>，使用 <code>VMware</code> 就不是那么友好，当你去查资料时，可能还会被推荐 <code>VirualBox</code>,这也是一个不错好用的软件，但随着更新 <code>Linux</code> 内核版本，<code>VirtualBox</code> 也逐渐被弃用，而导致之前创建的虚拟机无法运行。</p><p>这里推荐大家使用 <code>Qemu</code> 来创建虚拟机，<code>Qemu</code> 是一个开源的虚拟化软件，它允许我们创建虚拟机，如果你需要运行 <code>Linux</code> 虚拟机，<code>Qemu</code> 是目前最快的管理程序之一，甚至没有“之一”。</p><h1>在 Ubuntu 上安装 Qemu 和 KVM</h1><p>首先检查当前 <code>Linux</code> 平台是否支持虚拟化，在终端输入：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LC_ALL=C lscpu | grep Virtualization</span><br></pre></td></tr></table></figure><p><code>AMD</code> 的架构中，如果支持虚拟化，则输出：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Virtualization:                       AMD-V</span><br></pre></td></tr></table></figure><p>还要看当前系统中有多少核以供分配虚拟机，在终端输入：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">egrep -c &#x27;(vmx|svm)&#x27; /proc/cpuinfo</span><br></pre></td></tr></table></figure><p>确认没有问题后，我们就可以安装 <code>Qemu</code> 了，在终端输入：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install qemu qemu-kvm qemu-system  qemu-user-static virt-manager bridge-utils</span><br></pre></td></tr></table></figure><p>安装结束后，需要重新启动系统。</p><p>安装完成之后，可以查看 <code>Qemu</code> 版本：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">qemu-system-x86_64 -version</span><br><span class="line">qemu-system-arm -version</span><br></pre></td></tr></table></figure><p>为了使 <code>Qemu</code> 工作，你必须 将你的用户加入两个组：<code>libvirt-kvm</code> 和 <code>libvirt</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo useradd -g $USER libvirt</span><br><span class="line">sudo useradd -g $USER libvirt-kvm</span><br></pre></td></tr></table></figure><p>接下来，启用并启动 <code>libvirt</code> 服务：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl enable libvirtd.service &amp;&amp; sudo systemctl start libvirtd.service</span><br></pre></td></tr></table></figure><h1>创建虚拟机</h1><p>在 <code>ubuntu</code> 系统中打开虚拟系统管理器，可以根据 <code>ISO</code> 系统镜像来创建虚拟机。这里是图形化界面的操作过程，比较简单，这里就不多说了。</p><h1>虚拟机与宿主机实现剪切板共享</h1><p>在我们使用虚拟机，需要在宿主机与虚拟机之间进行文件传输，如果是虚拟机是 <code>Linux</code> 系统的，可以通过 <code>scp</code> 来实现，但如果虚拟机是 <code>windows</code> 系统，并且也不支持远程，则还有其他办法。</p><p>下面我们来使用 <code>SPICE vdagent</code> 实现 <code>ubuntu</code> 的宿主机和 <code>windows</code> 虚拟机之间的剪切板共享。</p><p>虚拟机配置 <code>SPICE</code> 通道：</p><p>1、在 <code>virt-manager</code> 中，确保虚拟机使用 <code>SPICE</code> 显示协议。</p><p>添加 <code>Channel</code> 设备（用于 <code>SPICE</code> 通信）：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">channel</span> <span class="attr">type</span>=<span class="string">&#x27;spicevmc&#x27;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">target</span> <span class="attr">type</span>=<span class="string">&#x27;virtio&#x27;</span> <span class="attr">name</span>=<span class="string">&#x27;com.redhat.spice.0&#x27;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">channel</span>&gt;</span></span><br></pre></td></tr></table></figure><p>2、<code>windows</code> 虚拟机安装 <code>vdagent</code>：</p><p><code>windows</code> 下载 <a href="https://www.spice-space.org/download.html">SPICE vdagent</a>，在下载页面中找到 Windows binaries 的 Windows SPICE Guest Tools(<a href="https://www.spice-space.org/download/windows/spice-guest-tools/spice-guest-tools-latest.exe">spice-guest-tools</a>)进行下载安装。</p><p>双击下载的 <code>exe</code> 程序安装完成之后，重新启动虚拟机。</p><p>这样在虚拟机中和宿主机之间就可以实现剪切版共享了，宿主机复制文件到虚拟机中，可以直接拖拽进去。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在 &lt;code&gt;windows&lt;/code&gt; 系统上我们使用虚拟机来运行一些软件，比如我们可以使用 &lt;code&gt;VMware&lt;/code&gt; 来创建我们的虚拟机，而且在 &lt;code&gt;windows&lt;/code&gt; 平台上有很多破解版可以让我们免费使用 &lt;code&gt;VMware&lt;</summary>
      
    
    
    
    <category term="ubuntu" scheme="https://www.webape.net/categories/ubuntu/"/>
    
    
    <category term="Qemu" scheme="https://www.webape.net/tags/Qemu/"/>
    
  </entry>
  
  <entry>
    <title>函数式编程</title>
    <link href="https://www.webape.net/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    <id>https://www.webape.net/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/</id>
    <published>2025-02-08T11:28:38.000Z</published>
    <updated>2025-10-23T07:46:18.232Z</updated>
    
    <content type="html"><![CDATA[<p>函数式编程 (FP) 不仅仅是另一种趋势——它还可以改变编写更清晰、更可预测和可扩展的代码的游戏规则。如果你真的想成为一名更好的开发人员，那么接受 FP 将使你的技能更上一层楼。让我们深入探讨 FP 为何重要以及如何掌握它。</p><h1>为什么要使用函数式编程？因为它使你的生活更轻松！</h1><p>一旦你开始功能性思考，你就永远不会回头。原因如下：</p><ul><li>正常工作的代码：纯函数总是对相同的输入返回相同的输出 - 没有奇怪的意外，没有隐藏状态。</li></ul><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 纯函数：输出仅取决于输入</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">add</span> = (<span class="params">a, b</span>) =&gt; a + b;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">2</span>, <span class="number">3</span>)); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>与命令式编程（函数可能会修改外部变量）不同，FP 可以避免意外的副作用。这使得调试变得非常容易。</p><ul><li>更容易维护：不变性和清晰的数据流意味着更少的错误和更顺畅的调试。</li></ul><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不可变对象更新</span></span><br><span class="line"><span class="keyword">const</span> user = &#123; <span class="attr">name</span>: <span class="string">&quot;Alice&quot;</span>, <span class="attr">age</span>: <span class="number">25</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> updatedUser = &#123; ...user, <span class="attr">age</span>: <span class="number">26</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(user.<span class="property">age</span>); <span class="comment">// 25 (原有不变)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(updatedUser.<span class="property">age</span>); <span class="comment">// 26 (新对象)</span></span><br></pre></td></tr></table></figure><p>这可以防止在代码的某一部分更改对象而意外影响另一部分的错误。</p><ul><li>轻松实现并行：不可变的数据结构消除了竞争条件，使并发编程变得轻而易举。在命令式代码中，多个线程改变同一个变量通常会导致不一致。在 FP 中，数据默认是不可变的，从而使多线程安全。</li></ul><h1>功能精通路线图</h1><p>想要提升你的 FP 技能吗？遵循以下结构化路径：</p><p>掌握基础知识：学习纯函数、不变性、高阶函数和递归。</p><p>示例：高阶函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 以另一个函数作为参数的函数</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">applyTwice</span> = (<span class="params">fn, value</span>) =&gt; <span class="title function_">fn</span>(<span class="title function_">fn</span>(value));</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">double</span> = (<span class="params">x</span>) =&gt; x * <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">applyTwice</span>(double, <span class="number">3</span>)); <span class="comment">// 12</span></span><br></pre></td></tr></table></figure><p>这是 FP 的基础：对其他函数进行操作的函数。</p><p>坚持下去：在实际项目中应用 FP</p><p>理论很棒，但真正的学习发生在编写代码时。方法如下：</p><p>解决 FP 挑战：解决迫使你进行功能性思考的编码练习。</p><p>构建真实应用程序：创建功能性应用程序或为基于 FP 的开源项目做出贡献。</p><p>重构你的代码：将命令式代码转换为函数式风格——你将亲眼看到其好处。</p><p>示例：将命令式循环重构为函数式方法</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">    sum += i;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数式方法</span></span><br><span class="line"><span class="keyword">const</span> sumFunctional = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">reduce</span>(<span class="function">(<span class="params">acc, val</span>) =&gt;</span> acc + val, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>函数式方法更具可读性，消除了变异，并且扩展性更好。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;函数式编程 (FP) 不仅仅是另一种趋势——它还可以改变编写更清晰、更可预测和可扩展的代码的游戏规则。如果你真的想成为一名更好的开发人员，那么接受 FP 将使你的技能更上一层楼。让我们深入探讨 FP 为何重要以及如何掌握它。&lt;/p&gt;
&lt;h1&gt;为什么要使用函数式编程？因为它使</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="函数式编程" scheme="https://www.webape.net/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>使用 umami 搭建私有流量统计平台</title>
    <link href="https://www.webape.net/%E4%BD%BF%E7%94%A8-umami-%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89%E6%B5%81%E9%87%8F%E7%BB%9F%E8%AE%A1%E5%B9%B3%E5%8F%B0/"/>
    <id>https://www.webape.net/%E4%BD%BF%E7%94%A8-umami-%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89%E6%B5%81%E9%87%8F%E7%BB%9F%E8%AE%A1%E5%B9%B3%E5%8F%B0/</id>
    <published>2024-12-24T09:36:53.000Z</published>
    <updated>2025-10-23T07:46:18.232Z</updated>
    
    <content type="html"><![CDATA[<p>现在的流量统计平台，都是基于第三方的服务，比如 Google Analytics、百度统计、CNZZ 等。这些服务都提供了一定的优势，但是也存在一些问题。例如，这些服务都是基于云服务，所以需要付费，而且这些服务也存在一定的隐私问题。</p><p>最近网站内容重新整理后，想使用一个流量统计平台，但上面的国内常用如百度统计、CNZZ等都没有免费的可以使用的了，目前还有一个可以正常免费使用的平台 <a href="https://www.51.la/">51la</a>还可以正常免费使用。</p><p>但是，我更喜欢使用 <a href="https://github.com/mikecao/umami">umami</a> 这个开源项目。使用 <code>umami</code> 可以在私有服务器上搭建一个流量统计平台，并且可以自己定制自己的统计页面。更加可以适应一些内网的需求。</p><p>下面我将以 <code>umami</code> 为例，介绍如何使用 docker 快速搭建一个私有流量统计平台。</p><h1>准备工作</h1><p>下面是 <code>umami</code> 的镜像地址，直接使用 <code>docker pull</code> 下载镜像即可。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker pull elestio/umami:latest</span><br><span class="line"># 数据库镜像</span><br><span class="line">docker pull postgres:15-alpine</span><br></pre></td></tr></table></figure><p>如果国内访问不了docker仓库，可以使用阿里云的镜像源。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker pull registry.cn-hangzhou.aliyuncs.com/elestio_cn/umami:latest</span><br><span class="line"># 数据库镜像</span><br><span class="line">docker pull registry.cn-hangzhou.aliyuncs.com/elestio_cn/postgres:15-alpine</span><br></pre></td></tr></table></figure><p>也可以在镜外服务器中 <code>pull</code> 下来再 <code>save</code> 到本地，下载后 <code>load</code> 到本地服务器中。</p><h1>创建容器</h1><p>下面是 <code>docker-compose</code> 的配置文件，可以参考。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3.3&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">umami:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">elestio/umami:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;172.17.0.1:3956:3000&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">DATABASE_URL:</span> <span class="string">postgresql://umami:$&#123;ADMIN_PASSWORD&#125;@db:5432/umami</span></span><br><span class="line">      <span class="attr">DATABASE_TYPE:</span> <span class="string">postgresql</span></span><br><span class="line">      <span class="attr">APP_SECRET:</span> <span class="string">$&#123;SECRET_KEY_BASE&#125;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line">    <span class="attr">init:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:15-alpine</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">POSTGRES_DB:</span> <span class="string">umami</span></span><br><span class="line">      <span class="attr">POSTGRES_USER:</span> <span class="string">umami</span></span><br><span class="line">      <span class="attr">POSTGRES_PASSWORD:</span> <span class="string">$&#123;ADMIN_PASSWORD&#125;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./umami-db-data:/var/lib/postgresql/data</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br></pre></td></tr></table></figure><p>上面的配置中，可以将变量 ADMIN_PASSWORD、SECRET_KEY_BASE 替换为实际的值。</p><p>将上面的配置文件保存为 <code>docker-compose.yml</code>，然后使用 <code>docker-compose up -d</code> 命令启动容器。</p><h1>访问 umami</h1><p>访问地址为 <a href="http://172.17.0.1:3956">http://172.17.0.1:3956</a>，默认用户名是 <code>admin</code> ，密码是上面配置文件中设置的变量值。其中 172.17.0.1 为宿主机的IP地址，3956 为端口号。</p><p>上面配置是只是一个示例，在生产环境中使用时，还需要配置域名绑定，使用nginx反向代理等。</p><ol><li>首先可以新增网站，在设置中可以看到网站列表</li></ol><p><img src="/static/images/umami_set.png" alt="umami_set.png"></p><ol start="2"><li>编辑查看页面中，可以获到流量统计的代码</li></ol><p><img src="/static/images/umami_code.png" alt="umami_code.png"></p><ol start="3"><li>将统计代码插入到网站中，即可实现流量统计。再通过 <code>umami</code> 查看流量统计。</li></ol><p><img src="/static/images/umami_tj.png" alt="umami_tj.png"></p><p>这样就完成了使用 <code>umami</code> 搭建私有流量统计平台的过程。有更多需求，可以基于其 <code>github</code> 项目进行二次开发。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;现在的流量统计平台，都是基于第三方的服务，比如 Google Analytics、百度统计、CNZZ 等。这些服务都提供了一定的优势，但是也存在一些问题。例如，这些服务都是基于云服务，所以需要付费，而且这些服务也存在一定的隐私问题。&lt;/p&gt;
&lt;p&gt;最近网站内容重新整理后，想</summary>
      
    
    
    
    <category term="工具" scheme="https://www.webape.net/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="docker" scheme="https://www.webape.net/tags/docker/"/>
    
    <category term="流量统计" scheme="https://www.webape.net/tags/%E6%B5%81%E9%87%8F%E7%BB%9F%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 中的依赖注入</title>
    <link href="https://www.webape.net/JavaScript-%E4%B8%AD%E7%9A%84%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/"/>
    <id>https://www.webape.net/JavaScript-%E4%B8%AD%E7%9A%84%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/</id>
    <published>2024-12-23T11:26:43.000Z</published>
    <updated>2025-10-23T07:46:18.228Z</updated>
    
    <content type="html"><![CDATA[<p>编写面对不断变化的需求具有弹性的代码需要有意识地应用实现这一目标的技术。</p><p>在本文中，我们将探讨依赖注入作为这些技术之一。</p><p>看看下面的代码片段。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getData</span> = <span class="keyword">async</span> (<span class="params">url</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(url);</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>此函数使用 <code>Fetch API</code> 通过网络检索资源并返回它。虽然这可行，但从干净且可维护的代码角度来看，这里有很多事情可能会出错。</p><ul><li>如果我们的需求将来发生变化，并且我们决定用另一个 <code>HTTP</code> 客户端（例如 <code>Axios</code>）替换 <code>Fetch API</code>，我们将必须修改整个函数才能与 <code>Axios</code> 一起使用。</li><li><code>Fetch API</code> 是浏览器中的全局对象，在我们要运行测试的 <code>Node.js</code> 等环境中不可用或可能无法完全按照预期工作。</li><li>在测试时，我们可能不想从网络上实际检索资源，但目前还没有办法做到这一点。</li></ul><p>这就是依赖注入发挥作用的地方。</p><p>其核心是，依赖注入是从外部提供我们的代码所需的依赖项，而不是像我们在上面的示例中所做的那样，让我们的代码直接构造和解析依赖项。我们将代码所需的依赖项作为参数传递给 getData 函数。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">const</span> <span class="title function_">getData</span> = <span class="keyword">async</span> (<span class="params">fetch, url</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(url);</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">  <span class="keyword">return</span> data;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">(<span class="function"><span class="params">async</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> resourceData = <span class="keyword">await</span> <span class="title function_">getData</span>(<span class="variable language_">window</span>.<span class="property">fetch</span>, <span class="string">&quot;https://myresourcepath&quot;</span>);</span><br><span class="line">  <span class="comment">//do something with resourceData</span></span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure><p>依赖注入背后的目的是实现关注点分离。这使得我们的代码更加模块化、可重用、可扩展和可测试。</p><p><code>javascript</code> 的核心是对象和原型，因此我们可以通过函数式或面向对象的方式进行依赖注入。</p><p><code>JavaScript</code> 的函数式编程特性（如高阶函数和闭包）使我们能够优雅地实现依赖注入。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetchResource</span> = (<span class="params">httpClient</span>) =&gt; <span class="function">(<span class="params">url</span>) =&gt;</span></span><br><span class="line">    <span class="title function_">httpClient</span>(url)</span><br><span class="line">        .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> data.<span class="property">json</span>)</span><br><span class="line">        .<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(error));</span><br></pre></td></tr></table></figure><p><code>fetchResource</code> 函数获取 <code>HTTP</code> 客户端的一个实例，并返回一个接受 <code>URL</code> 参数并对资源发出实际请求的函数。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">import</span> axios <span class="keyword">from</span> <span class="string">&quot;axios&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> httpClient = axios.<span class="title function_">create</span>(&#123;</span><br><span class="line">    <span class="attr">baseURL</span>: <span class="string">&quot;https://mybasepath&quot;</span>,</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123; <span class="string">&quot;Access-Control-Allow-Origin&quot;</span>: <span class="string">&quot;*&quot;</span> &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> getData = <span class="title function_">fetchResource</span>(httpClient);</span><br><span class="line"><span class="title function_">getData</span>(<span class="string">&quot;/resourcepath&quot;</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">response</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(response.<span class="property">data</span>));</span><br></pre></td></tr></table></figure><p>我们用 <code>Axios</code> 替换了原生的 <code>fetch</code>，一切仍然正常，无需干预内部实现。在这种情况下，我们的代码不直接依赖于任何特定的库或实现。因为我们可以轻松地替换另一个库。</p><p>接收依赖项的对象（在本例中为函数）通常称为客户端，而被注入的对象称为服务。</p><p>服务可能需要跨代码库的不同配置。由于我们的客户不关心服务的内部实现或配置，因此我们可以像上面所做的那样以不同的方式预配置服务。</p><p>依赖注入使我们能够将代码（业务逻辑）与库、框架、数据库、ORM 等外部组件的更改隔离开来。通过适当的关注点分离，测试变得简单明了。</p><p>我们可以消除依赖关系，并针对独立于外部组件的多个理想和边缘情况测试我们的代码。</p><p>在更复杂的用例中，通常是更大的项目，手动进行依赖项注入根本无法扩展，并且会带来全新的复杂性。我们可以利用依赖注入容器的强大功能来解决这个问题。宽松地说，依赖项注入容器包含依赖项以及创建这些依赖项的逻辑。你向容器请求服务的新实例，它会解析依赖关系，构造对象并将其返回。</p><p>有许多 <code>Javascript</code> 依赖注入容器库。我个人最喜欢的是 <code>TypeDI</code> 和 <code>InversifyJS</code>。以下示例演示了 <code>Typedi</code> 与 <code>JavaScript</code> 的基本用法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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> &#123; <span class="title class_">Container</span> &#125; <span class="keyword">from</span> <span class="string">&quot;typedi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ExampleClass</span> &#123;</span><br><span class="line">    <span class="title function_">print</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;I am alive!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 从 TypeDI 请求 ExampleClass 的实例。 */</span></span><br><span class="line"><span class="keyword">const</span> classInstance = <span class="title class_">Container</span>.<span class="title function_">get</span>(<span class="title class_">ExampleClass</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 我们收到了一个 ExampleClass 实例并准备好使用它。 */</span></span><br><span class="line">classInstance.<span class="title function_">print</span>();</span><br></pre></td></tr></table></figure><p>依赖注入技术跨越了不同的编程语言。作为一般经验法则，依赖注入可以使用允许将函数和对象作为参数传递的语言来完成。一些流行的 <code>HTTP</code> 框架（例如 <code>NestJs</code> 和 <code>FastAPI</code>）带有内置的依赖注入系统。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;编写面对不断变化的需求具有弹性的代码需要有意识地应用实现这一目标的技术。&lt;/p&gt;
&lt;p&gt;在本文中，我们将探讨依赖注入作为这些技术之一。&lt;/p&gt;
&lt;p&gt;看看下面的代码片段。&lt;/p&gt;
&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cla</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="依赖注入" scheme="https://www.webape.net/tags/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/"/>
    
  </entry>
  
  <entry>
    <title>掌握 JavaScript 类</title>
    <link href="https://www.webape.net/%E6%8E%8C%E6%8F%A1-JavaScript-%E7%B1%BB/"/>
    <id>https://www.webape.net/%E6%8E%8C%E6%8F%A1-JavaScript-%E7%B1%BB/</id>
    <published>2024-12-18T16:23:32.000Z</published>
    <updated>2025-10-23T07:46:18.233Z</updated>
    
    <content type="html"><![CDATA[<h1>了解 JavaScript 类</h1><p><code>JavaScript</code> 类是其原型继承系统的语法糖。 <code>ES6</code> 中引入的类提供了一种清晰且结构化的方式来定义对象并使用 <code>JavaScript</code> 中的继承，使代码更具可读性和组织性。</p><h1>定义一个类</h1><p>你可以使用 <code>class</code> 关键字定义一个类。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">greet</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`我的名字是： <span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span>，我的年龄是：<span class="subst">$&#123;<span class="variable language_">this</span>.age&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> person1 = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;webape&quot;</span>, <span class="number">25</span>);</span><br><span class="line">person1.<span class="title function_">greet</span>(); <span class="comment">// 我的名字是：webape，我的年龄是：25</span></span><br></pre></td></tr></table></figure><h1>类的主要特点</h1><h2 id="构造函数方法：">构造函数方法：</h2><p>构造函数是初始化新对象的特殊方法。当创建该类的新实例时，它会被自动调用。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">Car</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">brand</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myCar = <span class="keyword">new</span> <span class="title class_">Car</span>(<span class="string">&quot;宝马&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(myCar.<span class="property">brand</span>); <span class="comment">// 宝马</span></span><br></pre></td></tr></table></figure><h2 id="方法：">方法：</h2><p>在类中定义的函数称为方法。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">    <span class="title function_">sound</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;这是测试信息&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dog = <span class="keyword">new</span> <span class="title class_">Animal</span>();</span><br><span class="line">dog.<span class="title function_">sound</span>(); <span class="comment">// 这是测试信息</span></span><br></pre></td></tr></table></figure><h2 id="静态方法：">静态方法：</h2><p>使用 <code>static</code> 关键字定义的方法属于类本身，而不是类的实例。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">MathUtils</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> a + b;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">MathUtils</span>.<span class="title function_">add</span>(<span class="number">3</span>, <span class="number">5</span>)); <span class="comment">// 8</span></span><br></pre></td></tr></table></figure><h2 id="Getters-和-Setters：">Getters 和 Setters：</h2><p>访问和修改属性的特殊方法。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">Rectangle</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">width, height</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">width</span> = width;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">height</span> = height;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">get</span> <span class="title function_">area</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">width</span> * <span class="variable language_">this</span>.<span class="property">height</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> rect = <span class="keyword">new</span> <span class="title class_">Rectangle</span>(<span class="number">10</span>, <span class="number">5</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(rect.<span class="property">area</span>); <span class="comment">// 50</span></span><br></pre></td></tr></table></figure><h1>类中的继承</h1><p>继承允许一个类使用 <code>extends</code> 关键字从另一个类获取属性和方法。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">speak</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span> 叫`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Animal</span> &#123;</span><br><span class="line">    <span class="title function_">speak</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span> 吠.`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dog = <span class="keyword">new</span> <span class="title class_">Dog</span>(<span class="string">&quot;Rex&quot;</span>);</span><br><span class="line">dog.<span class="title function_">speak</span>(); <span class="comment">// Rex 吠.</span></span><br></pre></td></tr></table></figure><h1>私有字段和方法</h1><p><code>ES2022</code> 中引入的私有字段和方法以 <code>#</code> 开头，不能在类外部访问。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BankAccount</span> &#123;</span><br><span class="line">    #balance;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">initialBalance</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.#balance = initialBalance;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">deposit</span>(<span class="params">amount</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.#balance += amount;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`存入: <span class="subst">$&#123;amount&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">getBalance</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.#balance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> account = <span class="keyword">new</span> <span class="title class_">BankAccount</span>(<span class="number">100</span>);</span><br><span class="line">account.<span class="title function_">deposit</span>(<span class="number">50</span>); <span class="comment">// 存入: 50</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(account.<span class="title function_">getBalance</span>()); <span class="comment">// 150</span></span><br><span class="line"><span class="comment">// console.log(account.#balance); // Error: Private field &#x27;#balance&#x27; must be declared in an enclosing class</span></span><br></pre></td></tr></table></figure><h1>类表达式</h1><p>类也可以定义为表达式并分配给变量。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">const</span> <span class="title class_">Rectangle</span> = <span class="keyword">class</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">width, height</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">width</span> = width;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">height</span> = height;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">getArea</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">width</span> * <span class="variable language_">this</span>.<span class="property">height</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> rect = <span class="keyword">new</span> <span class="title class_">Rectangle</span>(<span class="number">10</span>, <span class="number">5</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(rect.<span class="title function_">getArea</span>()); <span class="comment">// 50</span></span><br></pre></td></tr></table></figure><h1>与原型混合</h1><p>虽然类是语法糖，但你仍然可以将它们与 <code>JavaScript</code> 基于原型的继承结合起来。</p><p>例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">class</span> <span class="title class_">Person</span> &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayHello</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Hello!&quot;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> person = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">person.<span class="title function_">sayHello</span>(); <span class="comment">// Hello!</span></span><br></pre></td></tr></table></figure><h1>类的最佳实践</h1><ul><li>封装：</li></ul><p>使用私有字段来保护敏感数据。</p><ul><li>可重复使用性：</li></ul><p>利用继承在多个类之间重用代码。</p><ul><li>避免过于复杂：</li></ul><p>仅在必要时使用类。简单的对象或函数可能足以完成小任务。</p><ul><li>一致性：</li></ul><p>遵循方法和属性的命名约定以提高可读性。</p><h1>总结</h1><p><code>JavaScript</code> 类提供了一种干净有效的方法来管理 <code>JavaScript</code> 中的面向对象编程。凭借继承、静态方法、私有字段和封装等功能，它们提供了强大的工具来构建和管理代码，从而更轻松地构建可扩展和可维护的应用程序。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;了解 JavaScript 类&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;JavaScript&lt;/code&gt; 类是其原型继承系统的语法糖。 &lt;code&gt;ES6&lt;/code&gt; 中引入的类提供了一种清晰且结构化的方式来定义对象并使用 &lt;code&gt;JavaScript&lt;/code&gt; 中的继承</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="class" scheme="https://www.webape.net/tags/class/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 的现代应用</title>
    <link href="https://www.webape.net/JavaScript-%E7%9A%84%E7%8E%B0%E4%BB%A3%E5%BA%94%E7%94%A8/"/>
    <id>https://www.webape.net/JavaScript-%E7%9A%84%E7%8E%B0%E4%BB%A3%E5%BA%94%E7%94%A8/</id>
    <published>2024-12-04T16:37:40.000Z</published>
    <updated>2025-10-23T07:46:18.229Z</updated>
    
    <content type="html"><![CDATA[<h1>什么在塑造 Web 开发的未来？</h1><p>多年来，<code>JavaScript</code> 经历了巨大的发展，使开发人员能够构建更强大、更高效、更用户友好的 Web 应用程序。随着新技术的出现，<code>JavaScript</code> 被推向极限，释放出曾经难以想象的功能。在本博客中，我们将探索 <code>JavaScript</code> 中一些最令人兴奋的新技术，并提供代码示例来帮助你入门。</p><h2 id="Deno：现代-JavaScript-运行时">Deno：现代 JavaScript 运行时</h2><p><code>Deno</code> 是 <code>JavaScript</code> 和 <code>TypeScript</code> 的新运行时，由 <code>Node.js</code> 的原始创建者 Ryan Dahl 构建。<code>Deno</code> 旨在通过专注于安全性、简单性和现代功能（如开箱即用的 <code>TypeScript</code> 支持）来解决 <code>Node</code> 的一些缺点。</p><p><strong>主要特点：</strong></p><ul><li>安全性：<code>Deno</code> 默认是沙盒化的，这意味着它需要明确的权限才能访问文件系统或网络。</li><li><code>TypeScript</code> 支持：<code>TypeScript</code> 原生支持，无需任何额外配置。</li><li>简化的模块系统：<code>Deno</code> 使用 <code>URL</code> 加载模块，无需使用 <code>npm</code> 等包管理器。</li></ul><p><strong>示例：</strong></p><p><code>Deno</code> 中的简单 <code>HTTP</code> 服务器</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">// 引入http服务module</span></span><br><span class="line"><span class="keyword">import</span> &#123; serve &#125; <span class="keyword">from</span> <span class="string">&quot;https://deno.land/std/http/server.ts&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> handler = (<span class="attr">req</span>: <span class="title class_">Request</span>): <span class="function"><span class="params">Response</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;Hello from Deno!&quot;</span>, &#123; <span class="attr">status</span>: <span class="number">200</span> &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Server running on http://localhost:8000&quot;</span>);</span><br><span class="line"><span class="keyword">await</span> <span class="title function_">serve</span>(handler, &#123; <span class="attr">port</span>: <span class="number">8000</span> &#125;);</span><br></pre></td></tr></table></figure><h2 id="TypeScript：使用静态类型增强-JavaScript">TypeScript：使用静态类型增强 JavaScript</h2><p>虽然 <code>TypeScript</code> 已经存在了一段时间，但它在开发人员中的受欢迎程度仍在不断提高。<code>TypeScript</code> 提供静态类型，有助于在开发过程中捕获错误，从而产生更易于维护和可扩展的代码。</p><p><strong>主要特点：</strong></p><ul><li>早期错误检测：TypeScript 有助于在编译时捕获与类型相关的错误。</li><li>更好的开发人员体验：现代 IDE 中改进的自动完成、文档和重构工具。</li></ul><p><strong>示例：</strong></p><p>具有函数类型的 <code>TypeScript</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 具有类型参数和返回类型的函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">greet</span>(<span class="params">name: string</span>): string &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`Hello, <span class="subst">$&#123;name&#125;</span>!`</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果我们尝试传递非字符串参数，TypeScript 将显示错误</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">greet</span>(<span class="string">&quot;JavaScript&quot;</span>));</span><br></pre></td></tr></table></figure><h2 id="WebAssembly-Wasm-：在浏览器中运行高性能代码">WebAssembly (Wasm)：在浏览器中运行高性能代码</h2><p><code>WebAssembly</code> 是一种低级二进制格式，允许开发人员以接近原生的速度直接在浏览器中运行 <code>C</code>、<code>C++</code>和 <code>Rust</code> 等语言的代码。它非常适合性能至关重要的应用程序，如游戏、图像/视频编辑或科学模拟。</p><p><strong>主要特点：</strong></p><ul><li>接近原生的性能：对于性能要求高的任务，<code>WebAssembly</code> 可以比 <code>JavaScript</code> 更快地运行代码。</li><li>跨平台：所有现代浏览器都支持 <code>WebAssembly</code>，确保兼容性。</li></ul><p><strong>示例：</strong></p><p>在 <code>JavaScript</code> 中使用 <code>WebAssembly</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="comment">// 加载WebAssembly</span></span><br><span class="line"><span class="keyword">const</span> goWasm = <span class="title function_">fetch</span>(<span class="string">&quot;example.wasm&quot;</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">response</span>) =&gt;</span> response.<span class="title function_">arrayBuffer</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化 WebAssembly 实例</span></span><br><span class="line">goWasm</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">(<span class="params">bytes</span>) =&gt;</span> <span class="title class_">WebAssembly</span>.<span class="title function_">instantiate</span>(bytes))</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">(<span class="params">results</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> &#123; add &#125; = results.<span class="property">instance</span>.<span class="property">exports</span>;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">2</span>, <span class="number">3</span>)); <span class="comment">// 输出：5（假设 add 函数从 WASM 导出）</span></span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure><h2 id="Svelte：革命性的前端框架">Svelte：革命性的前端框架</h2><p><code>Svelte</code> 是下一代框架，它将繁重的 <code>UI</code> 更新工作转移到编译时，生成最少且高度优化的 <code>JavaScript</code> 代码。与 <code>React</code> 或 <code>Vue</code> 等其他框架不同，<code>Svelte</code> 不使用虚拟 <code>DOM</code>，因此速度极快且轻量级。</p><p><strong>主要特点：</strong></p><ul><li>无虚拟 <code>DOM</code>：<code>Svelte</code> 将组件编译为直接操作 <code>DOM</code> 的高效命令式代码。</li><li>更小的包大小：由于采用基于编译器的方法，与其他框架相比，<code>Svelte</code> 生成的包大小要小得多。</li></ul><p><strong>示例：</strong></p><p><code>Svelte</code> 计数器组件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">&lt;script&gt;</span><br><span class="line">  <span class="keyword">let</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">    count += <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">decrement</span>(<span class="params"></span>) &#123;</span><br><span class="line">    count -= <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">on:click</span>=<span class="string">&#123;decrement&#125;</span>&gt;</span>-<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">on:click</span>=<span class="string">&#123;increment&#125;</span>&gt;</span>+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>每当计数发生变化时，此 <code>Svelte</code> 组件都会自动更新 <code>DOM</code>，并且 <code>Svelte</code> 会将其编译为针对浏览器优化的 <code>JavaScript</code>。</p><h2 id="React-服务器组件：优化性能">React 服务器组件：优化性能</h2><p><code>React</code> 服务器组件 (RSC) 是一项实验性功能，允许开发人员在服务器而不是客户端上渲染组件，从而提高大型应用程序的性能。</p><p><strong>主要功能：</strong></p><ul><li>减少 <code>JavaScript</code> 包：通过在服务器端渲染组件，<code>React</code> 服务器组件减少了发送到浏览器的 <code>JavaScript</code> 数量。</li><li>更快的初始加载：服务器渲染的组件可缩短初始加载时间，从而带来更好的用户体验。</li></ul><p><strong>示例：</strong></p><p><code>React</code> 中的服务器组件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="keyword">import</span> &#123; <span class="title class_">Suspense</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 模拟从 API 获取数据</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">fetchData</span> = (<span class="params"></span>) =&gt;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span></span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">resolve</span>(<span class="string">&quot;Hello from Server&quot;</span>), <span class="number">2000</span>)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ServerComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="title function_">fetchData</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;data&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">Suspense</span> <span class="attr">fallback</span>=<span class="string">&#123;</span>&lt;<span class="attr">div</span>&gt;</span>Loading...<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">ServerComponent</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Suspense</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这个例子中，React Server Components 与 Suspense 组件一起使用来异步处理数据提取，通过减少客户端的负载来提高应用程序的性能。</p><h2 id="TensorFlow-js：JavaScript-中的机器学习">TensorFlow.js：JavaScript 中的机器学习</h2><p><code>TesnorFlow.js</code> 将机器学习引入 <code>JavaScript</code>，允许你直接在浏览器或 <code>Node.js</code> 上训练和运行模型。它是构建智能 <code>Web</code> 应用程序的强大工具。</p><p><strong>主要特点：</strong></p><ul><li>在浏览器中运行模型：<code>TensorFlow.js</code> 允许你直接在浏览器中运行机器学习模型。</li><li>实时预测：非常适合实时应用，例如图像识别或情绪分析。</li></ul><p><strong>示例：</strong></p><p>将预训练模型与 <code>TensorFlow.js</code> 结合使用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">import</span> * <span class="keyword">as</span> tf <span class="keyword">from</span> <span class="string">&quot;@tensorflow/tfjs&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loadModel</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> model = <span class="keyword">await</span> tf.<span class="title function_">loadLayersModel</span>(<span class="string">&quot;https://example.com/model.json&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> inputTensor = tf.<span class="title function_">tensor</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]);</span><br><span class="line">    <span class="keyword">const</span> prediction = model.<span class="title function_">predict</span>(inputTensor);</span><br><span class="line">    prediction.<span class="title function_">print</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">loadModel</span>();</span><br></pre></td></tr></table></figure><p>在这个例子中，我们加载一个预先训练的 <code>TensorFlow</code> 模型并使用它在浏览器中进行预测。</p><h1>总结</h1><p><code>JavaScript</code> 不断发展，新技术和工具为开发人员开辟了令人兴奋的可能性。从 <code>Deno</code> 的现代运行时和 <code>TypeScript</code> 的静态类型到 <code>WebAssembly</code> 和 <code>TensorFlow.js</code> <code>提供的性能增强，JavaScript</code> 的未来前景一片光明。</p><p>无论你是想提高性能、使用机器学习构建更智能的应用程序，还是简化前端工作流程，这些新工具和技术都将帮助你保持领先地位。</p><p>随着 <code>JavaScript</code> 生态系统的发展，探索这些新兴技术并在项目中进行试验非常重要。你对这些创新的了解和实施越多，你就越有能力构建下一代 <code>Web</code> 应用程序。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;什么在塑造 Web 开发的未来？&lt;/h1&gt;
&lt;p&gt;多年来，&lt;code&gt;JavaScript&lt;/code&gt; 经历了巨大的发展，使开发人员能够构建更强大、更高效、更用户友好的 Web 应用程序。随着新技术的出现，&lt;code&gt;JavaScript&lt;/code&gt; 被推向极限，释放</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>利用 verdaccio 搭建私有npm平台及在内网中使用</title>
    <link href="https://www.webape.net/%E5%88%A9%E7%94%A8-verdaccio-%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89npm%E5%B9%B3%E5%8F%B0%E5%8F%8A%E5%9C%A8%E5%86%85%E7%BD%91%E4%B8%AD%E4%BD%BF%E7%94%A8/"/>
    <id>https://www.webape.net/%E5%88%A9%E7%94%A8-verdaccio-%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89npm%E5%B9%B3%E5%8F%B0%E5%8F%8A%E5%9C%A8%E5%86%85%E7%BD%91%E4%B8%AD%E4%BD%BF%E7%94%A8/</id>
    <published>2024-06-01T11:08:02.000Z</published>
    <updated>2025-10-23T07:46:18.232Z</updated>
    
    <content type="html"><![CDATA[<p><code>verdaccio</code> 的安装使用方法，其<a href="https://verdaccio.org/zh-CN/docs/what-is-verdaccio">官网</a>上有更详情的内容。</p><p>这里我给大家重点介绍一下使用 <code>docker</code> 来安装，还有一些个性化的配置，以及邮件通知的配置方法。</p><h1>安装</h1><p>首先拉取 <code>verdaccio</code> 的镜像：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull verdaccio/verdaccio</span><br></pre></td></tr></table></figure><p>利用 <code>docker-compose.yml</code> 来启动容器：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">verdaccio:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">verdaccio/verdaccio:latest</span></span><br><span class="line">        <span class="attr">container_name:</span> <span class="string">npm-verdaccio</span></span><br><span class="line">        <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">4873</span><span class="string">:4873</span></span><br><span class="line">        <span class="attr">volumes:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">./data/verdaccio/conf:/verdaccio/conf</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">./data/verdaccio/storage:/verdaccio/storage</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">./data/verdaccio/plugins:/verdaccio/plugins</span></span><br><span class="line">        <span class="attr">tty:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>在上面的配置中，我们将环境配置、库文件、插件目录都挂载到了本地当前对应的目录中，方便后面修改和数据的持久化。</p><p>运行启动容器：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p>之后，会看到挂载的目录会生成对应的文件，下面我们来修改配置文件：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 上游npm包获取地址</span></span><br><span class="line"><span class="attr">uplinks:</span></span><br><span class="line">    <span class="attr">npmjs:</span></span><br><span class="line">        <span class="attr">url:</span> <span class="string">https://registry.npm.taobao.org</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 包存储地址，这里需要配置当前的conf的上一层，否则会在当前conf目录下新建storage目录</span></span><br><span class="line"><span class="attr">storage:</span> <span class="string">../storage</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 一些自定义的web配置信息</span></span><br><span class="line"><span class="attr">web:</span></span><br><span class="line">    <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">title:</span> <span class="string">npm私有库</span></span><br><span class="line">    <span class="attr">logo:</span> <span class="string">/verdaccio/conf/logo.svg</span></span><br><span class="line">    <span class="attr">primary_color:</span> <span class="string">&quot;#de0000&quot;</span></span><br><span class="line">    <span class="attr">favicon:</span> <span class="string">/verdaccio/conf/favicon.ico</span></span><br><span class="line">    <span class="attr">pkgManagers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">pnpm</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">npm</span></span><br><span class="line">    <span class="attr">showInfo:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">showSettings:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">showThemeSwitch:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">showFooter:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">scriptsBodyAfter:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&quot;&lt;style&gt;&lt;/style&gt;&quot;</span></span><br></pre></td></tr></table></figure><h1>内网</h1><p>如果你的 <code>verdaccio</code> 是搭建在内网的，还需要将外网的第三方包下载安装转移到内网。</p><p>这个工作首先需要在可以连接互联网的设备上同样使用 <code>docker</code> 安装 <code>verdaccio</code> 环境。</p><p>安装成功后，默认会在本地启一个 4873 的 <code>http</code> 服务。</p><p>本地还要安装 <code>node</code> 环境，通过 <code>node</code> 的 <code>npm init</code> 创建本地项目，通过当前项目的根目录文件 <code>.npmrc</code>，如果没有可自行创建，内容为：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">registry=http://localhost:4873</span><br></pre></td></tr></table></figure><p>这样，在本地安装安装的项目，将会从私有平台 <code>4873</code> 上下载获取第三方包，<code>verdaccio</code> 通过其设置的上游链接，去互联网上下载后，会缓存放置在 <code>storage</code> 目录中。</p><p>这样操作之后，在 <code>verdaccio</code> 的 <code>storage</code> 目录中就会存在我们需要的第三方包，我们可以将其转移到内网环境中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo tar --exclude=storage/@sg --exclude=storage/@wgl --exclude=storage/.verdaccio-db.json -zcvf storage.tar storage</span><br></pre></td></tr></table></figure><p>上面的压缩命令，我们排除了本地测试的自定义包，和 <code>verdaccio</code> 的版本数据文件，是为了覆盖时不要把内网 <code>verdaccio</code> 里的私有数据覆盖了，只允许覆盖第三方包。</p><p>解压覆盖到内网 <code>verdaccio</code> 的 <code>storage</code> 目录后，重启内网的 <code>docker</code> 容器即可。</p><h1>通知</h1><p><code>verdaccio</code> 给我们提供了 <code>publish</code> 的通知功能，当有新包被推送时，可以及时通知相关引用使用者，下面是一个比较好的通知配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">notify:</span></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> [&#123; <span class="attr">&quot;Content-Type&quot;:</span> <span class="string">&quot;application/json&quot;</span> &#125;]</span><br><span class="line">    <span class="attr">endpoint:</span> <span class="string">http://172.17.0.1:5000/send_email</span></span><br><span class="line">    <span class="attr">content:</span> <span class="string">&#x27;&#123;&quot;subject&quot;:&quot;<span class="template-variable">&#123;&#123; publishedPackage &#125;&#125;</span>已推送&quot;,&quot;sendMail&quot;:&quot;`<span class="template-variable">&#123;&#123;#each versions&#125;&#125;</span> &#123;\&quot;enable\&quot;:\&quot;<span class="template-variable">&#123;&#123; sendMail.enable &#125;&#125;</span>\&quot;,\&quot;msg\&quot;:\&quot;<span class="template-variable">&#123;&#123;sendMail.msg&#125;&#125;</span>\&quot;,\&quot;to\&quot;:\&quot;<span class="template-variable">&#123;&#123;sendMail.to&#125;&#125;</span>\&quot;&#125;<span class="template-variable">&#123;&#123;/each&#125;&#125;</span>`&quot;,&quot;message&quot;:&quot;User：<span class="template-variable">&#123;&#123; publisher.name &#125;&#125;</span>&lt;br/&gt;Package：<span class="template-variable">&#123;&#123; publishedPackage &#125;&#125;</span>`<span class="template-variable">&#123;&#123;#each versions&#125;&#125;</span> &lt;br/&gt; Author：<span class="template-variable">&#123;&#123;author.name&#125;&#125;</span>&lt;br/&gt;Email：<span class="template-variable">&#123;&#123;author.email&#125;&#125;</span>&lt;br/&gt;Integrity：<span class="template-variable">&#123;&#123;dist.integrity&#125;&#125;</span>&lt;br/&gt;Tarball：<span class="template-variable">&#123;&#123;dist.tarball&#125;&#125;</span><span class="template-variable">&#123;&#123;/each&#125;&#125;</span>`&quot;,&quot;notify&quot;:true,&quot;message_format&quot;:&quot;text&quot;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>这里的 <code>endpoint</code> 是接口地址，我们可以使用 <code>express</code> 框架来建立一个发送邮件的服务。</p><p><code>index.js</code> 的服务文件内容如下：</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&quot;express&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> nodemailer = <span class="built_in">require</span>(<span class="string">&quot;nodemailer&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">&quot;body-parser&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果smtp服务器是ip，会报证书的一些问题，这里可以设置为0</span></span><br><span class="line"><span class="comment">// process.env.NODE_TLS_REJECT_UNAUTHORIZED = &quot;0&quot;;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = <span class="number">5000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 application/json 解析</span></span><br><span class="line">app.<span class="title function_">use</span>(bodyParser.<span class="title function_">json</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 application/x-www-form-urlencoded 解析</span></span><br><span class="line">app.<span class="title function_">use</span>(bodyParser.<span class="title function_">urlencoded</span>(&#123; <span class="attr">extended</span>: <span class="literal">true</span> &#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 格式化接收内容</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">baseFormat</span> = (<span class="params">obj</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> obj) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> obj[key] === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">            obj[key] = obj[key].<span class="title function_">replaceAll</span>(<span class="string">&quot;`&quot;</span>, <span class="string">&quot;&quot;</span>).<span class="title function_">trim</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 邮件配置</span></span><br><span class="line"><span class="keyword">const</span> emailConfig = &#123;</span><br><span class="line">    <span class="attr">defaultTo</span>: [<span class="string">&quot;webape@qq.com&quot;</span>],</span><br><span class="line">    <span class="attr">from</span>: <span class="string">&quot;webape@qq.com&quot;</span>,</span><br><span class="line">    <span class="attr">host</span>: <span class="string">&quot;smtp.qq.com&quot;</span>,</span><br><span class="line">    <span class="attr">port</span>: <span class="number">465</span>,</span><br><span class="line">    <span class="attr">user</span>: <span class="string">&quot;webape@qq.com&quot;</span>,</span><br><span class="line">    <span class="attr">pass</span>: <span class="string">&quot;******&quot;</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置路由</span></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&quot;/send_email&quot;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> body = <span class="title function_">baseFormat</span>(req.<span class="property">body</span>);</span><br><span class="line">    <span class="keyword">const</span> &#123; sendMail, subject, message &#125; = body;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;body&quot;</span>, body);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> sendMailObj = <span class="title function_">baseFormat</span>(<span class="title class_">JSON</span>.<span class="title function_">parse</span>(sendMail));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ([<span class="string">&quot;true&quot;</span>, <span class="string">&quot;&quot;</span>].<span class="title function_">indexOf</span>(sendMailObj.<span class="property">enable</span>) &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> defaultTo = emailConfig.<span class="property">defaultTo</span>;</span><br><span class="line">        <span class="comment">// 设置邮件内容</span></span><br><span class="line">        <span class="keyword">const</span> mailOptions = &#123;</span><br><span class="line">            <span class="attr">from</span>: emailConfig.<span class="property">from</span>,</span><br><span class="line">            <span class="attr">to</span>: sendMailObj.<span class="property">to</span></span><br><span class="line">                ? sendMailObj.<span class="property">to</span>.<span class="title function_">split</span>(<span class="string">&quot;,&quot;</span>).<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> item.<span class="title function_">trim</span>())</span><br><span class="line">                : defaultTo,</span><br><span class="line">            <span class="attr">subject</span>: subject,</span><br><span class="line">            <span class="attr">html</span>:</span><br><span class="line">                message.<span class="title function_">replaceAll</span>(<span class="string">&quot;`&quot;</span>, <span class="string">&quot;&quot;</span>) +</span><br><span class="line">                (sendMailObj.<span class="property">msg</span></span><br><span class="line">                    ? <span class="string">&#x27;&lt;div style=&quot;background-color:red;color:#fff&quot;&gt;&#x27;</span> +</span><br><span class="line">                      sendMailObj.<span class="property">msg</span> +</span><br><span class="line">                      <span class="string">&quot;&lt;/div&gt;&quot;</span></span><br><span class="line">                    : <span class="string">&quot;&quot;</span>),</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置邮件发送配置</span></span><br><span class="line">        <span class="keyword">const</span> transporter = nodemailer.<span class="title function_">createTransport</span>(&#123;</span><br><span class="line">            <span class="attr">host</span>: emailConfig.<span class="property">host</span>,</span><br><span class="line">            <span class="attr">port</span>: emailConfig.<span class="property">port</span>,</span><br><span class="line">            <span class="attr">auth</span>: &#123;</span><br><span class="line">                <span class="attr">user</span>: emailConfig.<span class="property">user</span>,</span><br><span class="line">                <span class="attr">pass</span>: emailConfig.<span class="property">pass</span>,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 发送邮件</span></span><br><span class="line">        transporter.<span class="title function_">sendMail</span>(mailOptions, <span class="function">(<span class="params">error, info</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (error) &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(error);</span><br><span class="line">                res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">send</span>(<span class="string">&quot;邮件发送失败&quot;</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Email sent: &quot;</span> + info.<span class="property">response</span>);</span><br><span class="line">                res.<span class="title function_">send</span>(<span class="string">&quot;邮件发送成功&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        res.<span class="title function_">send</span>(<span class="string">&quot;无需发送邮件&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动服务</span></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="string">&quot;172.17.0.1&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Server is running on http://172.17.0.1:<span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></details><p>可以看到上面的服务是绑定在 ip:172.17.0.1 上的，这个是 <code>docker</code> 的网关地址，使 <code>docker</code> 内部可以访问外部宿主机的 <code>http</code> 服务。</p><p>我们可以在和 <code>verdaccio</code> 同一服务器中后台启动该服务：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">在后台执行程序</span></span><br><span class="line">nohup node index.js &amp;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将该进程与当前 shell 分离，避免关闭当前 shell 连接后进程关闭</span></span><br><span class="line">disown</span><br></pre></td></tr></table></figure><p>由于 <code>verdaccio</code> 的通知是读取的项目的 <code>package.json</code> 的数据在我们的项目的 <code>package.json</code> 文件中，我们可以添加以下内容来配合邮件通知的发送行为和内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;sendMail&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span><span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;msg&quot;</span><span class="punctuation">:</span><span class="string">&quot;提示信息&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;to&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="string">&quot;webape@qq.com&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p><strong>配置说明:</strong></p><ul><li>当 <code>package.json</code> 中没有定义 <code>sendMail</code> 对象时，默认会向内置的默认用户发送通知邮件。</li><li>当 <code>sendMail.enable</code> 为 <code>true</code> 时，表示发送邮件，<code>false</code> 时表示不发送邮件，默认为 <code>true</code>。</li><li>用户可以通过 <code>sendMail.msg</code> 来在邮件中指定提示信息，例如，“此版本为测试版本，请勿使用”。</li><li>用户可以通过指定 <code>to</code> 来限定邮件的发送范围，为空数组时，默认会向内置的默认用户发送。</li></ul><h1>注意</h1><ol><li><p>私有库绑定域名后，用什么域名第一次访问，则默认就是哪个域名。我们一般还会配合绑定域名使用成功一个可以对外提供服务的站点，需要注意的时，绑定域名后，第一次访问后，再次访问需要使用该域名才能访问。但是我们可以使用环境变量 VERDACCIO_PUBLIC_URL 来指定域名地址。</p></li><li><p>私有库需要 <code>npm adduser</code> 添加用户后输入邮箱后半天没有反应，应该是 <code>htpasswd</code> 文件写权限问题。可以给其 <code>775</code> 的权限。</p></li><li><p>使用 <code>pnpm</code> 时的缓存问题。因为 <code>pnpm</code> 本身也会创建本地的存储库，当想在外网缓存外部库转到内网中时，<code>.nprc</code> 配置 registry=[地址]，执行 <code>pnpm store prune</code>，<code>pnpm store path</code> 查看缓存库位置删除，删除 <code>node_modules</code> 和 <code>pnpm-lock.yaml</code>，后执行 <code>pnpm install</code>。</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;code&gt;verdaccio&lt;/code&gt; 的安装使用方法，其&lt;a href=&quot;https://verdaccio.org/zh-CN/docs/what-is-verdaccio&quot;&gt;官网&lt;/a&gt;上有更详情的内容。&lt;/p&gt;
&lt;p&gt;这里我给大家重点介绍一下使用 &lt;code&gt;</summary>
      
    
    
    
    <category term="工具" scheme="https://www.webape.net/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="verdaccio" scheme="https://www.webape.net/tags/verdaccio/"/>
    
    <category term="notice邮件通知" scheme="https://www.webape.net/tags/notice%E9%82%AE%E4%BB%B6%E9%80%9A%E7%9F%A5/"/>
    
  </entry>
  
  <entry>
    <title>Docker 网络详解2</title>
    <link href="https://www.webape.net/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A32/"/>
    <id>https://www.webape.net/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A32/</id>
    <published>2024-05-21T15:44:14.000Z</published>
    <updated>2025-10-23T07:46:18.228Z</updated>
    
    <content type="html"><![CDATA[<p>在我之前关于 <code>docker</code> 网络的文章中，我介绍了使用 <code>docker CLI</code> 进行网络管理的基础知识。但在现实生活中，你可能不会以这种方式工作，你将通过 <code>docker-compose</code> 配置来编排所需的所有容器。</p><p>这就是本文的用武之地 - 让我们看看如何在现实生活中使用网络。涵盖使用 <code>docker-compose</code> 进行网络管理的基础知识、如何将网络用于多存储库/多 <code>docker-compose</code> 项目、微服务以及如何利用它们来测试不同的问题场景，如有限的网络吞吐量、丢失的数据包等。</p><p>让我们从一些非常基本的东西开始，如果你已经熟悉 <code>docker-compose</code>，你可能想要跳过下面的一些部分。</p><h1>Docker-compose 基础知识</h1><p>使用 <code>docker-compose</code> 开放端口</p><p>你可能想要做的第一件事就是简单地开放一个端口：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">phpmyadmin:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">phpmyadmin</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">8080</span><span class="string">:80</span></span><br></pre></td></tr></table></figure><p>对于刚接触 <code>Docker</code> 的人来说，暴露意味着向外界开放一个端口。你可以通过 <code>IP</code> 限制它，但默认情况下，这意味着每个人都可以访问它。端口暴露在你的网络接口上，而不是容器接口上。在上面的例子中，你可以在端口 8080（localhost:8080）上访问 PhpMyAdmin 容器的端口 80。</p><p>如你所见，这非常简单，你只需传递要暴露的端口，遵循与 <code>docker CLI</code> 中相同的想法，即 localPort:containerPort。添加本地端口的监听接口显然也是可能的：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">phpmyadmin:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">phpmyadmin</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span><span class="string">:8080:80</span></span><br></pre></td></tr></table></figure><p>当你不想将某些服务暴露给外部时，这可能会很方便。</p><h1>在 docker-compose 文件中连接容器</h1><p>正如 <code>docker</code> 网络文章中提到的，<code>docker-compose</code> 默认创建一个网络。你可以通过创建一个非常简单的 <code>docker-compose</code> 配置文件轻松检查这一点：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">phpmyadmin:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">phpmyadmin</span></span><br><span class="line">        <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">8080</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">PMA_HOSTS=db</span></span><br></pre></td></tr></table></figure><p>让我们运行它并看看会发生什么：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br><span class="line">Creating network &quot;myexampleproject_default&quot; with the default driver</span><br><span class="line">Pulling db (mariadb:10.3)...</span><br><span class="line">(...)</span><br></pre></td></tr></table></figure><p>你可能在第一行就注意到了，为该项目创建了一个名为 myexampleproject_default 的默认网络。它在 <code>docker CLI</code> 中也可见：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">docker network ls</span><br><span class="line">NETWORK ID     NAME                      DRIVER    SCOPE</span><br><span class="line">a9979ee462fb   bridge                    bridge    local</span><br><span class="line">d8b7eab3d297   myexampleproject_default  bridge    local</span><br><span class="line">17c76d995120   host                      host      local</span><br><span class="line">8224bb92dd9b   none                      null      local</span><br></pre></td></tr></table></figure><p>此 <code>docker-compose.yaml</code> 中的所有容器都与此网络连接。这意味着，它们可以轻松相互通信，并且主机可以通过名称解析：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker-compose exec phpmyadmin bash</span><br><span class="line">root@b362dbe238ac:/var/www/html# getent hosts db</span><br><span class="line">172.18.0.3      db</span><br></pre></td></tr></table></figure><h1>Docker-compose 网络和面向微服务的架构</h1><p>在编写面向微服务的项目时，能够在开发环境中模拟至少一部分生产方法非常方便。这包括分离和连接容器组，以及测试网络延迟问题、连接问题等。</p><h2 id="将容器划分为单独的网络">将容器划分为单独的网络</h2><p>但是，如果你不希望容器能够相互通信怎么办？也许你正在编写一个系统，其中一个部分应该对另一个部分隐藏？实际上，系统的这些部分是使用 AWS VPC 或类似机制分离的，但在开发机器上测试它会很好，对吗？</p><p>没问题，让我们看看这个配置文件：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service1-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">service1-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br><span class="line">    <span class="attr">service2-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">service2-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">81</span><span class="string">:80</span></span><br></pre></td></tr></table></figure><p>如你所见，我们有两个独立的服务，每个服务都包含一个 web 和 db 容器。我们希望仅从其 web 服务访问 db，因此 service2-web 无法直接访问 service1-db。</p><p>现在让我们检查一下它是如何工作的：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker-compose exec service1-web ash</span><br><span class="line">/ # getent hosts service1-db</span><br><span class="line">172.19.0.2        service1-db  service1-db</span><br><span class="line">/ # getent hosts service2-db</span><br><span class="line">172.19.0.5        service2-db  service2-db</span><br></pre></td></tr></table></figure><p>不幸的是，这些服务并没有按照我们所希望的方式分开。</p><p><img src="/static/images/docker-network.png" alt="docker-network.png"></p><p>不用担心，只需添加非常简单的更改即可实现：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service1-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service1</span></span><br><span class="line">    <span class="attr">service1-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service1</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">web</span></span><br><span class="line">    <span class="attr">service2-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service2</span></span><br><span class="line">    <span class="attr">service2-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">81</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service2</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">web</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">    <span class="attr">service1:</span></span><br><span class="line">    <span class="attr">service2:</span></span><br><span class="line">    <span class="attr">web:</span></span><br></pre></td></tr></table></figure><p>我们引入了三个不同的网络（第 31-33 行）——每个服务一个，Web 服务共享一个。为什么我们需要第三个网络？这是允许 service1-web 和 service2-web 之间通信所必需的。我们还为每个服务添加了网络配置（第 7-8、13-15、20-21、26-28 行）。</p><p>现在让我们检查一下 service1-web 如何解析名称：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dco exec service1-web ash</span><br><span class="line">/ # getent hosts service2-web</span><br><span class="line">172.22.0.2        service2-web  service2-web</span><br><span class="line">/ # getent hosts service2-db</span><br><span class="line">/ # getent hosts service1-db</span><br><span class="line">172.20.0.3        service1-db  service1-db</span><br></pre></td></tr></table></figure><p><img src="/static/images/docker-network2.png" alt="docker-network2.png"></p><p>如你所见，我们可以通过引入网络并仅将选定的容器连接在一起，轻松实现容器之间的分离。</p><h2 id="在多个-docker-compose-文件之间连接容器">在多个 docker-compose 文件之间连接容器</h2><p>通常，上述项目会拆分到 <code>git</code> 存储库之间，或者至少拆分到 <code>docker-compose.yaml</code> 文件之间。因此，开发人员可以单独启动每个服务。我们如何连接这些服务？让我们来看看。</p><p>假设我们决定将之前使用的项目拆分为两个单独的存储库。一个用于 service1，另一个用于 service2。这意味着我们有两个 <code>docker-compose.yaml</code> 文件：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#service1/docker-compose.yaml</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service1-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">service1-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br></pre></td></tr></table></figure><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#service2/docker-compose.yaml</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service2-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">service2-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">81</span><span class="string">:80</span></span><br></pre></td></tr></table></figure><p>如果我们启动这两个配置，service1-web 和 service2-web 将无法相互通信，因为它们将被添加到两个不同的网络：每个 docker-compose.yaml 文件默认创建自己的网络。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br><span class="line">Creating network &quot;service1_default&quot; with the default driver</span><br><span class="line">Creating service1_service1-web_1 ... done</span><br><span class="line">Creating service1_service1-db_1  ... done</span><br></pre></td></tr></table></figure><p>让我们首先重新添加 service1 的网络配置并进行一些小的修改：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service1-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service1</span></span><br><span class="line">    <span class="attr">service1-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service1</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">web</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">    <span class="attr">service1:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">shared-web</span></span><br></pre></td></tr></table></figure><p>我们在第 20 行添加了一些配置。在本例中，我想为我的 Web 网络指定一个固定名称。默认情况下，名称由 PROJECTNAME_NETWORKNAME 组成，项目名称默认为目录名称。我们所在的目录可能对不同的开发人员有不同的名称，因此安全的选择是强制使用这个名称。</p><p>现在，对于 service2，我们需要采取一些不同的行动：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">service2-db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service2</span></span><br><span class="line">    <span class="attr">service2-web:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginxdemos/hello</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">81</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">networks:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">service2</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">web</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">    <span class="attr">service2:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">        <span class="attr">external:</span> <span class="literal">true</span> <span class="comment">#needs to be created by other file</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">shared-web</span></span><br></pre></td></tr></table></figure><p>如你在第 20-21 行中看到的，在本例中我们配置了一个外部网络。这意味着，docker-compose 不会尝试创建它，如果不可用，它将失败。但是一旦可用，它就会重新使用它。</p><p>就是这样。Service1 和 2 Web 容器可以相互访问，但它们的数据库是分开的。两者也可以在单独的存储库中开发。</p><p>作为上述内容的扩展，你可以查看容器别名以使路由更容易，或者内部路由以进一步隔离服务。</p><h1>混沌测试</h1><p>如你所知，当发生中断时，问题不是是否会发生，而是何时发生。最好为这种情况做好准备，并测试系统在出现不同问题时的行为。如果某些数据包丢失或延迟增加，会发生什么？也许服务离线？</p><p>混沌测试就是为此做好准备。</p><p>我强烈建议你查看 <code>Pumba</code>，这是一个允许你暂停服务、终止服务的项目，但也会添加网络延迟、丢失、损坏等。</p><p>完整描述 <code>pumba</code> 需要大量时间，所以让我们只看一个非常简单的网络延迟模拟。</p><p>让我们启动一个 ping 8.8.8.8 的容器：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -it --rm --name demo-ping alpine ping 8.8.8.8</span><br></pre></td></tr></table></figure><p>现在，查看输出，在单独的控制台选项卡中运行以下命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pumba netem --duration 5s --tc-image gaiadocker/iproute2 delay --time 3000 demo-ping</span><br></pre></td></tr></table></figure><p>就这样！</p><p>你还可以在几分钟内实施其他混沌测试。</p><h1>总结</h1><p><code>Docker</code> 和 <code>docker-compose</code> 是模拟不同网络配置的绝佳工具，无需设置服务器或虚拟机。命令和配置非常容易使用。结合 <code>Pumba</code> 等外部工具，你还可以测试问题情况并为中断做好准备。</p><hr/><p><strong>相关文章：</strong></p><ul><li><a href="/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A31/">Docker 网络详解 1</a></li><li><a href="/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A32/">Docker 网络详解 2</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在我之前关于 &lt;code&gt;docker&lt;/code&gt; 网络的文章中，我介绍了使用 &lt;code&gt;docker CLI&lt;/code&gt; 进行网络管理的基础知识。但在现实生活中，你可能不会以这种方式工作，你将通过 &lt;code&gt;docker-compose&lt;/code&gt; 配置来编排所</summary>
      
    
    
    
    <category term="docker" scheme="https://www.webape.net/categories/docker/"/>
    
    
    <category term="网络" scheme="https://www.webape.net/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>Docker 网络详解1</title>
    <link href="https://www.webape.net/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A31/"/>
    <id>https://www.webape.net/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A31/</id>
    <published>2024-05-20T15:29:09.000Z</published>
    <updated>2025-10-23T07:46:18.228Z</updated>
    
    <content type="html"><![CDATA[<p>你是否曾经想过 <code>Docker</code> 中的网络是如何工作的？也许你对使用 <code>Docker</code> 的网络层可以做的鲜为人知的事情感兴趣？以下是一些有趣的事实和用例，可能有助于日常使用。</p><h1>开放端口</h1><p>让我们从基础开始。端口开放是 <code>Docker</code> 网络中最常用的东西，但你了解它的全部内容吗？</p><p>让我们看一个简单的命令，如下所示：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 127.0.0.1:80:8080/tcp ubuntu bash</span><br></pre></td></tr></table></figure><p>这是什么意思？好吧，可能有点棘手，但它的意思是：</p><p>在端口 <code>80</code> 上监听 <code>127.0.0.1</code> 上的 <code>TCP</code> 连接，并将流量转发到容器内的端口 <code>8080</code>。</p><p>如果我们稍微简化一下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 80:8080/tcp ubuntu bash</span><br></pre></td></tr></table></figure><p>我们省略了 <code>IP</code> 部分，现在 <code>Docker</code> 将监听所有接口，因此可以从外部访问该服务。</p><p>我们可以进一步更改符号，然后运行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 80:8080/udp ubuntu bash</span><br></pre></td></tr></table></figure><p>这将转发 <code>udp</code> 连接。另一个选项是 <code>sctp</code>，但它并未广泛用于与 <code>Web</code> 相关的东西。<code>TCP</code> 显然是最常见的，因此如果我们跳过 /protocol 部分 - 它将默认设置为 <code>TCP</code>。</p><p>如果我们只运行这个会发生什么：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 8080 ubuntu bash</span><br></pre></td></tr></table></figure><p>这会将 <code>TCP</code> 流量从随机选择的端口转发到容器中的端口 <code>8080</code>。等等？随机？！我怎么知道使用了哪个端口？</p><p>只需查看 <code>docker ps</code> - 有一列：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker ps</span></span><br><span class="line">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES</span><br><span class="line">2f82dac833ae        mariadb:10.3        &quot;docker-entrypoint.s…&quot;   10 days ago         Up 9 hours          3306/tcp               project_db_1</span><br><span class="line">86f00e7f41a2        phpmyadmin          &quot;/docker-entrypoint.…&quot;   10 days ago         Up 9 hours          0.0.0.0:8080-&gt;80/tcp   project_phpmyadmin_1</span><br><span class="line">31ea70729fbf        redis:6             &quot;docker-entrypoint.s…&quot;   6 weeks ago         Up 9 hours          6379/tcp               project_redis_1</span><br></pre></td></tr></table></figure><p><code>PORTS</code> 列中包含你需要的所有信息，甚至更多。它还显示未转发的开放端口。此类端口无法从外部访问（<code>docker</code> 网络除外，但这会改变），但如果你想转发它，你可以获得此信息。</p><p>你还可以运行 <code>docker port</code> 来检查给定容器的映射：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker port project_phpmyadmin_1</span></span><br><span class="line">80/tcp -&gt; 0.0.0.0:8080</span><br></pre></td></tr></table></figure><p>但是它缺少未映射端口的部分，所以我想你不会经常使用该命令 😉</p><p>还有最后一件事我们需要提及，那就是 <code>docker run</code> 的 <code>-P</code> 参数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -P ubuntu bash</span></span><br></pre></td></tr></table></figure><p><code>-P</code> 在主机上的随机端口上开放 <code>Dockerfile</code> 中提到的所有端口。</p><h1>连接容器</h1><p>让我们启动一个简单的 <code>Web</code> 服务器：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name test_web nginx:alpine</span><br></pre></td></tr></table></figure><p>如果我们启动第二个容器，比如说 <code>ubuntu</code>，在其上安装 <code>curl</code> 并尝试访问网页：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker run -t -i --rm ubuntu bash</span><br><span class="line">apt update</span><br><span class="line">apt install curl</span><br><span class="line">curl test_web</span><br></pre></td></tr></table></figure><p>这行不通，名称未解析！我们可以使用 <code>docker inspect</code> 并检查 test_web 容器的 <code>IP</code> 是否为 172.17.0.2，然后运行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl 172.17.0.2</span><br></pre></td></tr></table></figure><p>而且它可以工作。因此连接性有限，但这是可能的。</p><p>如果你熟悉 <code>docker-compose</code>，这可能会令人困惑。<code>docker-compose</code> 中的服务可以使用其名称轻松相互通信！如果你有一个简单的文件，例如：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.6&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">db:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">mariadb:10.3</span></span><br><span class="line">        <span class="attr">environment:</span> <span class="string">...</span></span><br><span class="line">    <span class="attr">phpmyadmin:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">phpmyadmin</span></span><br><span class="line">        <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">8080</span><span class="string">:80</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">PMA_HOSTS=db</span></span><br></pre></td></tr></table></figure><p>然后 <code>phpmyadmin</code> 显然可以使用其名称 db 连接到 db 服务！原因很简单，它适用于同一网络内的容器，默认容器除外。</p><p>让我们尝试一下！</p><p>我们可以通过运行以下命令创建一个新网络：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network create test</span><br></pre></td></tr></table></figure><p>并将现有容器连接到它并运行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker network connect test test_web</span><br><span class="line">docker network connect test NameOfYourBashContainer</span><br></pre></td></tr></table></figure><p>我假设测试 <code>bash</code> 容器仍然处于活动状态，你只需在上面的第二行中使用它的名称即可。现在切换回它并运行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl test_web</span><br></pre></td></tr></table></figure><p>可以正常工作！请注意，只要两个容器共享同一网络，就无需开放端口即可从第二个容器访问它。</p><p>通过运行以下命令断开其中一个容器与新创建的网络的连接：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network disconnect test NameOfYourBashContainer</span><br></pre></td></tr></table></figure><p>并且名称将不再解析！</p><p>还可以在创建容器时将其连接到网络，只需将网络作为输入选项之一传递即可：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -t -i --rm --network test ubuntu bash</span><br></pre></td></tr></table></figure><hr/><p><strong>相关文章：</strong></p><ul><li><a href="/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A31/">Docker 网络详解 1</a></li><li><a href="/Docker-%E7%BD%91%E7%BB%9C%E8%AF%A6%E8%A7%A32/">Docker 网络详解 2</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;你是否曾经想过 &lt;code&gt;Docker&lt;/code&gt; 中的网络是如何工作的？也许你对使用 &lt;code&gt;Docker&lt;/code&gt; 的网络层可以做的鲜为人知的事情感兴趣？以下是一些有趣的事实和用例，可能有助于日常使用。&lt;/p&gt;
&lt;h1&gt;开放端口&lt;/h1&gt;
&lt;p&gt;让我们从基础</summary>
      
    
    
    
    <category term="docker" scheme="https://www.webape.net/categories/docker/"/>
    
    
    <category term="网络" scheme="https://www.webape.net/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>开发一个 chrome 浏览器插件</title>
    <link href="https://www.webape.net/%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA-chrome-%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/"/>
    <id>https://www.webape.net/%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA-chrome-%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/</id>
    <published>2024-05-09T19:34:58.000Z</published>
    <updated>2025-10-23T07:46:18.233Z</updated>
    
    <content type="html"><![CDATA[<p>我们来学习开发一个 <code>chrome</code> 浏览器插件，更加详情的教程说明，可以<a href="https://developer.chrome.com/docs/extensions/whats-new?hl=zh-cn">查看</a></p><p>首先，必须先有一个入口页面，这里我们定义为 <code>popup.html</code>，在这个入口页中，我们可以写入打开插件时的展示的页面内容。</p><p>在这个入口 <code>html</code> 页面中，我们可以引入一些自定义的资源文件，如 <code>js</code>、<code>css</code> 文件，来实现完善交互和美化界面的目的。</p><p>那 <code>chrome</code> 浏览器是如何找到这个入口页面的呢？我们来认识一下 <code>chrome</code> 浏览器插件的必要配置文件 <code>manifest.json</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;WebApe-2.6 API Test Main&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;WebApe2.6的api测试工具，方便web开发与后端接口联调和测试人员进行接口测试工作，浏览器需Chrome 88+，快捷打开方式：Ctrl+Shift+Y&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;manifest_version&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;default_popup&quot;</span><span class="punctuation">:</span> <span class="string">&quot;popup.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;default_icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assets/img/logo.png&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;options_page&quot;</span><span class="punctuation">:</span> <span class="string">&quot;popup.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;icons&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;16&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assets/img/logo.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;32&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assets/img/logo.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;48&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assets/img/logo.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;128&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assets/img/logo.png&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;commands&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;_execute_action&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;suggested_key&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Ctrl+Shift+Y&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;mac&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MacCtrl+Shift+Y&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Opens popup.html&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;storage&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;declarativeContent&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;activeTab&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;scripting&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;tabs&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;sidePanel&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;side_panel&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;default_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;popup.html&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;background&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;service_worker&quot;</span><span class="punctuation">:</span> <span class="string">&quot;src/background/worker.js&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;content_scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;matches&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&lt;all_urls&gt;&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;js&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/content/script.js&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;run_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;document_idle&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;matches&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&lt;all_urls&gt;&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;js&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;lib/bundle.js&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;run_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;document_idle&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>以上的配置，我们需要注意几个重要参数：</p><ul><li><code>name</code>：插件名称</li><li><code>manifest_version</code>：适配插件的版本，目前主要是 3</li><li><code>action</code>：定义了入口文件和图标</li><li><code>permissions</code>：需要使用到的权限</li><li><code>background</code>：后台可执行的 <code>js</code> 文件，相当于插件的服务端</li><li><code>content_scripts</code>：默认加载插件时，会自动加载到页面中的脚本文件</li></ul><p>上面我们常用的权限有：</p><ul><li><code>activeTab</code>：允许插件在当前标签页执行操作。</li><li><code>storage</code>：允许插件访问 <code>Chrome</code> 浏览器的本地存储。</li><li><code>tabs</code>：允许插件访问和修改浏览器标签页。</li><li><code>cookies</code>：允许插件读取和修改浏览器的 cookie 数据。</li><li><code>webRequest</code>：允许插件监视和修改网络请求。</li><li><code>webNavigation</code>：允许插件获取浏览器导航信息。</li><li><code>notifications</code>：允许插件创建通知。</li><li><code>identity</code>：允许插件获取用户的身份信息。</li><li><code>contextMenus</code>：允许插件添加右键菜单项。</li><li><code>unlimitedStorage</code>：允许插件请求无限制的存储空间。</li><li><code>sidePanel</code>： 侧边栏。</li></ul><p>于是我们可以定义这样的目录结构</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">www</span><br><span class="line">├─assets                静态资源文件</span><br><span class="line">├─dist                  构建目的目录</span><br><span class="line">├─lib                   扩展包文件目录</span><br><span class="line">├─src                   代码主目录</span><br><span class="line">│  ├─background         后端代码</span><br><span class="line">│  │  └─worker.js</span><br><span class="line">│  ├─business           业务代码</span><br><span class="line">│  │  └─popup.js</span><br><span class="line">│  └─content            内容加载代码</span><br><span class="line">│     └─script.js</span><br><span class="line">├─.gitignore            git忽略配置文件</span><br><span class="line">├─main.js               npm执行主文件</span><br><span class="line">├─manifest.json         插件配置文件</span><br><span class="line">├─package-lock.json     npm锁文件</span><br><span class="line">├─package.json          npm包文件</span><br><span class="line">└─popup.html            弹出静态html</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里大家也可以看到，这里我使用了 <code>node</code> 去辅助生成我的 <code>chrome</code> 插件，也可以不用，这里我只是想可以一键压缩及去掉代码中的注释才加上去的。</p><p>其中 <code>main.js</code> 中的代码为</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; promisify &#125; = <span class="built_in">require</span>(<span class="string">&quot;util&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> readdir = <span class="title function_">promisify</span>(fs.<span class="property">readdir</span>);</span><br><span class="line"><span class="keyword">const</span> stat = <span class="title function_">promisify</span>(fs.<span class="property">stat</span>);</span><br><span class="line"><span class="keyword">const</span> readFile = <span class="title function_">promisify</span>(fs.<span class="property">readFile</span>);</span><br><span class="line"><span class="keyword">const</span> writeFile = <span class="title function_">promisify</span>(fs.<span class="property">writeFile</span>);</span><br><span class="line"><span class="keyword">const</span> mkdir = <span class="title function_">promisify</span>(fs.<span class="property">mkdir</span>);</span><br><span class="line"><span class="keyword">const</span> rmdir = <span class="title function_">promisify</span>(fs.<span class="property">rm</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="attr">minify</span>: minifyJS &#125; = <span class="built_in">require</span>(<span class="string">&quot;uglify-js&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CleanCSS</span> = <span class="built_in">require</span>(<span class="string">&quot;clean-css&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> htmlMinifier = <span class="built_in">require</span>(<span class="string">&quot;html-minifier&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> distDir = <span class="string">&quot;dist&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查创建目录</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">checkAndCreateDistDir</span>(<span class="params">dir</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> stats = <span class="keyword">await</span> <span class="title function_">stat</span>(dir);</span><br><span class="line">        <span class="keyword">if</span> (stats.<span class="title function_">isDirectory</span>()) &#123;</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">rmdir</span>(dir, &#123; <span class="attr">recursive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="keyword">if</span> (err.<span class="property">code</span> !== <span class="string">&quot;ENOENT&quot;</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> err;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">mkdir</span>(dir);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拷贝文件</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">copyFiles</span>(<span class="params">dir</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> filesToCopy = [</span><br><span class="line">        <span class="string">&quot;assets&quot;</span>,</span><br><span class="line">        <span class="string">&quot;lib&quot;</span>,</span><br><span class="line">        <span class="string">&quot;src&quot;</span>,</span><br><span class="line">        <span class="string">&quot;manifest.json&quot;</span>,</span><br><span class="line">        <span class="string">&quot;popup.html&quot;</span>,</span><br><span class="line">        <span class="string">&quot;web_cmd_x86.o&quot;</span>,</span><br><span class="line">        <span class="string">&quot;web_cmd_arm.o&quot;</span>,</span><br><span class="line">    ];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">copyFile</span> = <span class="keyword">async</span> (<span class="params">source, target</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> contents = <span class="keyword">await</span> <span class="title function_">readFile</span>(source);</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">writeFile</span>(target, contents);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">copyFolder</span> = <span class="keyword">async</span> (<span class="params">source, target</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">mkdir</span>(target, &#123; <span class="attr">recursive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">        <span class="keyword">const</span> files = <span class="keyword">await</span> <span class="title function_">readdir</span>(source);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> file <span class="keyword">of</span> files) &#123;</span><br><span class="line">            <span class="keyword">const</span> current = path.<span class="title function_">resolve</span>(source, file);</span><br><span class="line">            <span class="keyword">const</span> dest = path.<span class="title function_">resolve</span>(target, file);</span><br><span class="line">            <span class="keyword">const</span> stats = <span class="keyword">await</span> <span class="title function_">stat</span>(current);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (stats.<span class="title function_">isDirectory</span>()) &#123;</span><br><span class="line">                <span class="keyword">await</span> <span class="title function_">copyFolder</span>(current, dest);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">await</span> <span class="title function_">copyFile</span>(current, dest);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> file <span class="keyword">of</span> filesToCopy) &#123;</span><br><span class="line">        <span class="keyword">const</span> sourcePath = path.<span class="title function_">resolve</span>(file);</span><br><span class="line">        <span class="keyword">const</span> destPath = path.<span class="title function_">resolve</span>(dir, file);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">const</span> stats = <span class="keyword">await</span> <span class="title function_">stat</span>(sourcePath);</span><br><span class="line">        <span class="keyword">if</span> (stats.<span class="title function_">isDirectory</span>()) &#123;</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">copyFolder</span>(sourcePath, destPath);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">copyFile</span>(sourcePath, destPath);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 压缩处理</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">minifyFiles</span>(<span class="params">dir</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> files = fs.<span class="title function_">readdirSync</span>(dir);</span><br><span class="line"></span><br><span class="line">    files.<span class="title function_">forEach</span>(<span class="function">(<span class="params">file</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> filePath = path.<span class="title function_">join</span>(dir, file);</span><br><span class="line">        <span class="keyword">const</span> stat = fs.<span class="title function_">statSync</span>(filePath);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (stat.<span class="title function_">isDirectory</span>()) &#123;</span><br><span class="line">            <span class="title function_">minifyFiles</span>(filePath);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> ext = path.<span class="title function_">extname</span>(file);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (ext === <span class="string">&quot;.js&quot;</span>) &#123;</span><br><span class="line">                <span class="keyword">let</span> code = fs.<span class="title function_">readFileSync</span>(filePath, <span class="string">&quot;utf8&quot;</span>);</span><br><span class="line">                <span class="keyword">const</span> &#123; error, <span class="attr">code</span>: minifiedCode &#125; = <span class="title function_">minifyJS</span>(code);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!error) &#123;</span><br><span class="line">                    fs.<span class="title function_">writeFileSync</span>(filePath, minifiedCode);</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Minified <span class="subst">$&#123;filePath&#125;</span>`</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`Error minifying <span class="subst">$&#123;filePath&#125;</span>: <span class="subst">$&#123;error&#125;</span>`</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ext === <span class="string">&quot;.css&quot;</span>) &#123;</span><br><span class="line">                <span class="keyword">let</span> code = fs.<span class="title function_">readFileSync</span>(filePath, <span class="string">&quot;utf8&quot;</span>);</span><br><span class="line">                <span class="keyword">const</span> minifiedCode = <span class="keyword">new</span> <span class="title class_">CleanCSS</span>().<span class="title function_">minify</span>(code).<span class="property">styles</span>;</span><br><span class="line"></span><br><span class="line">                fs.<span class="title function_">writeFileSync</span>(filePath, minifiedCode);</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Minified <span class="subst">$&#123;filePath&#125;</span>`</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ext === <span class="string">&quot;.html&quot;</span>) &#123;</span><br><span class="line">                <span class="keyword">let</span> code = fs.<span class="title function_">readFileSync</span>(filePath, <span class="string">&quot;utf8&quot;</span>);</span><br><span class="line">                <span class="keyword">const</span> minifiedCode = htmlMinifier.<span class="title function_">minify</span>(code, &#123;</span><br><span class="line">                    <span class="attr">collapseWhitespace</span>: <span class="literal">true</span>,</span><br><span class="line">                    <span class="attr">removeComments</span>: <span class="literal">true</span>,</span><br><span class="line">                    <span class="attr">minifyJS</span>: <span class="literal">true</span>,</span><br><span class="line">                    <span class="attr">minifyCSS</span>: <span class="literal">true</span>,</span><br><span class="line">                &#125;);</span><br><span class="line"></span><br><span class="line">                fs.<span class="title function_">writeFileSync</span>(filePath, minifiedCode);</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Minified <span class="subst">$&#123;filePath&#125;</span>`</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">main</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">checkAndCreateDistDir</span>(distDir);</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">copyFiles</span>(distDir);</span><br><span class="line">        <span class="title function_">minifyFiles</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;dist&quot;</span>));</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Task completed successfully.&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;An error occurred:&quot;</span>, err);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">main</span>();</span><br></pre></td></tr></table></figure></details><p><code>package.json</code> 文件中的内容为</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ts2.6-api-test&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.1.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;main.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node main.js&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;webape@qq.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ISC&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;WebApe2.6的接口测试chrome插件&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;clean-css&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.3.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;html-minifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;uglify-js&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.19.3&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如何来实现插件功能呢？这里我们来写一个基本的示例，在 <code>popup.html</code> 中加入一个按钮</p><figure class="highlight html"><table><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">button</span> @<span class="attr">click</span>=<span class="string">&quot;buttonClick()&quot;</span>&gt;</span>点击事件<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在引入的 <code>business</code> 业务代码中的 <code>popup.js</code> 中有 <code>js</code> 代码</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">buttonClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    chrome.<span class="property">runtime</span>.<span class="title function_">sendMessage</span>(</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">action</span>: <span class="string">&quot;ifWebApe&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="keyword">function</span> (<span class="params">response</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;ifWebApe-response&quot;</span>, response);</span><br><span class="line">            <span class="keyword">if</span> (!response.<span class="property">backData</span>.<span class="property">length</span> || !response.<span class="property">backData</span>[<span class="number">0</span>].<span class="property">result</span>) &#123;</span><br><span class="line">                layer.<span class="title function_">alert</span>(</span><br><span class="line">                    <span class="string">&quot;当前页签不是WebApe的页面，请先打开WebApe的页面并登录后再使用插件！&quot;</span>,</span><br><span class="line">                    &#123;</span><br><span class="line">                        <span class="attr">title</span>: <span class="string">&quot;提示&quot;</span>,</span><br><span class="line">                        <span class="attr">closeBtn</span>: <span class="number">0</span>,</span><br><span class="line">                        <span class="attr">btn</span>: [],</span><br><span class="line">                        <span class="attr">success</span>: <span class="keyword">function</span> (<span class="params">layero, index</span>) &#123;</span><br><span class="line">                            <span class="comment">// 监听警告框的 close 事件</span></span><br><span class="line">                            layero.<span class="title function_">on</span>(<span class="string">&quot;close&quot;</span>, <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">                                <span class="comment">// 阻止默认行为，即不允许关闭</span></span><br><span class="line">                                e.<span class="title function_">preventDefault</span>();</span><br><span class="line">                                e.<span class="title function_">stopPropagation</span>();</span><br><span class="line">                            &#125;);</span><br><span class="line">                        &#125;,</span><br><span class="line">                    &#125;</span><br><span class="line">                );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><p>在上面的代码中，<code>chrome.runtime.sendMessage</code> 就是向后端接口发送了一个消息，现在我们来看后端如何接收与返回</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ifWebApeFunction</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">&quot;slimContent&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">chrome.<span class="property">runtime</span>.<span class="property">onMessage</span>.<span class="title function_">addListener</span>(<span class="keyword">function</span> (<span class="params">request, sender, sendResponse</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (request.<span class="property">action</span> === <span class="string">&quot;ifWebApe&quot;</span>) &#123;</span><br><span class="line">        chrome.<span class="property">tabs</span>.<span class="title function_">query</span>(</span><br><span class="line">            &#123; <span class="attr">active</span>: <span class="literal">true</span>, <span class="attr">currentWindow</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">            <span class="keyword">function</span> (<span class="params">tabs</span>) &#123;</span><br><span class="line">                chrome.<span class="property">scripting</span></span><br><span class="line">                    .<span class="title function_">executeScript</span>(&#123;</span><br><span class="line">                        <span class="attr">target</span>: &#123; <span class="attr">tabId</span>: tabs[<span class="number">0</span>].<span class="property">id</span> &#125;,</span><br><span class="line">                        <span class="attr">func</span>: ifWebApeFunction,</span><br><span class="line">                        <span class="attr">args</span>: [],</span><br><span class="line">                    &#125;)</span><br><span class="line">                    .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">                        <span class="title function_">sendResponse</span>(&#123; <span class="attr">backData</span>: res &#125;);</span><br><span class="line">                    &#125;)</span><br><span class="line">                    .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">                        <span class="title function_">sendResponse</span>(&#123; <span class="attr">backData</span>: err &#125;);</span><br><span class="line">                    &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 保持消息通道打开以便异步发送响应</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></details><p>至于在我们的内容代码中，因为直接插入到了打开的网页之中，可以立即检测，如在 <code>content/script.js</code> 中</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 检测版本</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getChromeVersion</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> arr = navigator.<span class="property">userAgent</span>.<span class="title function_">split</span>(<span class="string">&quot; &quot;</span>);</span><br><span class="line">    <span class="keyword">var</span> chromeVersion = <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="regexp">/chrome/i</span>.<span class="title function_">test</span>(arr[i])) chromeVersion = arr[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (chromeVersion) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Number</span>(chromeVersion.<span class="title function_">split</span>(<span class="string">&quot;/&quot;</span>)[<span class="number">1</span>].<span class="title function_">split</span>(<span class="string">&quot;.&quot;</span>)[<span class="number">0</span>]);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_">getChromeVersion</span>()) &#123;</span><br><span class="line">    <span class="keyword">var</span> version = <span class="title function_">getChromeVersion</span>();</span><br><span class="line">    <span class="keyword">if</span> (version &lt; <span class="number">88</span>) &#123;</span><br><span class="line">        <span class="title function_">alert</span>(</span><br><span class="line">            <span class="string">&quot;你使用的谷歌浏览器版本过低，为了更好地体验请将浏览器升级到最新&gt;=88版本！&quot;</span></span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我们来学习开发一个 &lt;code&gt;chrome&lt;/code&gt; 浏览器插件，更加详情的教程说明，可以&lt;a href=&quot;https://developer.chrome.com/docs/extensions/whats-new?hl=zh-cn&quot;&gt;查看&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;首</summary>
      
    
    
    
    <category term="web应用" scheme="https://www.webape.net/categories/web%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="chrome" scheme="https://www.webape.net/tags/chrome/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 浅拷贝与深拷贝：示例和最佳实践</title>
    <link href="https://www.webape.net/JavaScript-%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D%EF%BC%9A%E7%A4%BA%E4%BE%8B%E5%92%8C%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    <id>https://www.webape.net/JavaScript-%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D%EF%BC%9A%E7%A4%BA%E4%BE%8B%E5%92%8C%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</id>
    <published>2024-03-25T16:46:50.000Z</published>
    <updated>2025-10-23T07:46:18.228Z</updated>
    
    <content type="html"><![CDATA[<p>在 <code>JavaScript</code> 中使用对象和数组时，创建数据结构的副本是一项常见任务。然而，开发人员在决定浅拷贝和深拷贝时经常面临挑战。误解这些差异可能会导致代码中出现意想不到的副作用。让我们深入研究这些概念、它们的区别以及何时使用它们。</p><h1>什么是浅拷贝？</h1><p>浅拷贝会创建一个新对象，其中包含原始对象的顶级属性的副本。对于原始属性（例如数字、字符串、布尔值），会复制值本身。但是，对于对象属性（如数组或嵌套对象），只会复制引用，而不会复制实际数据。</p><p>这意味着虽然新对象有自己的顶级属性副本，但嵌套对象或数组在原始对象和副本之间仍然共享。</p><h2 id="浅拷贝示例">浅拷贝示例</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> original = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;Alice&quot;</span>,</span><br><span class="line">    <span class="attr">details</span>: &#123;</span><br><span class="line">        <span class="attr">age</span>: <span class="number">25</span>,</span><br><span class="line">        <span class="attr">city</span>: <span class="string">&quot;Wonderland&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Shallow copy</span></span><br><span class="line"><span class="keyword">const</span> shallowCopy = &#123; ...original &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Modify the nested object in the shallow copy</span></span><br><span class="line">shallowCopy.<span class="property">details</span>.<span class="property">city</span> = <span class="string">&quot;Looking Glass&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Original object is also affected</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(original.<span class="property">details</span>.<span class="property">city</span>); <span class="comment">// Output: &quot;Looking Glass&quot;</span></span><br></pre></td></tr></table></figure><h2 id="如何使用扩展运算符">如何使用扩展运算符</h2><h3 id="创建浅拷贝："><code>(...)</code> 创建浅拷贝：</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> shallowCopy = &#123; ...originalObject &#125;;</span><br></pre></td></tr></table></figure><h3 id="使用-Object-assign-：">使用 <code>Object.assign()</code>：</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> shallowCopy = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, originalObject);</span><br></pre></td></tr></table></figure><p>虽然这些方法快速且简单，但它们不适用于深度嵌套的对象。</p><h1>什么是深层复制？</h1><p>深层复制会复制原始对象的每个属性和子属性。这可确保副本完全独立于原始对象，并且对副本的更改不会影响原始对象。</p><p>处理嵌套对象或数组等复杂数据结构时，深层复制必不可少，尤其是在数据完整性至关重要的情况下。</p><h2 id="深层复制示例">深层复制示例</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> original = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;Alice&quot;</span>,</span><br><span class="line">    <span class="attr">details</span>: &#123;</span><br><span class="line">        <span class="attr">age</span>: <span class="number">25</span>,</span><br><span class="line">        <span class="attr">city</span>: <span class="string">&quot;Wonderland&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Deep copy using JSON methods</span></span><br><span class="line"><span class="keyword">const</span> deepCopy = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(original));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Modify the nested object in the deep copy</span></span><br><span class="line">deepCopy.<span class="property">details</span>.<span class="property">city</span> = <span class="string">&quot;Looking Glass&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Original object remains unchanged</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(original.<span class="property">details</span>.<span class="property">city</span>); <span class="comment">// Output: &quot;Wonderland&quot;</span></span><br></pre></td></tr></table></figure><h2 id="如何创建深层复制">如何创建深层复制</h2><h3 id="使用-JSON-stringify-和-JSON-parse-：">使用 <code>JSON.stringify()</code> 和 <code>JSON.parse()</code>：</h3><p>将对象转换为 <code>JSON</code> 字符串，然后将其解析回新对象。</p><p><strong>限制：</strong></p><p>无法处理循环引用。</p><p>忽略函数、未定义或符号等属性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> deepCopy = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(originalObject));</span><br></pre></td></tr></table></figure><h3 id="使用库：">使用库：</h3><p>像 <code>Lodash</code> 这样的库提供了强大的深度克隆方法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> _ = <span class="built_in">require</span>(<span class="string">&quot;lodash&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> deepCopy = _.<span class="title function_">cloneDeep</span>(originalObject);</span><br></pre></td></tr></table></figure><h3 id="自定义递归函数：">自定义递归函数：</h3><p>为了获得完全控制权，你可以编写递归函数来克隆嵌套对象。</p><h1>浅拷贝和深拷贝的比较</h1><table><thead><tr><th>特性</th><th>浅拷贝</th><th>深拷贝</th></tr></thead><tbody><tr><td>范围</td><td>仅复制顶层属性。</td><td>复制所有层级，包括嵌套数据。</td></tr><tr><td>引用</td><td>嵌套引用是共享的。</td><td>嵌套引用是独立的。</td></tr><tr><td>性能</td><td>更快更轻量。</td><td>由于递归操作而更慢。</td></tr><tr><td>使用场景</td><td>扁平或最小嵌套对象。</td><td>深层嵌套对象或不可变结构。</td></tr></tbody></table><h2 id="何时使用浅拷贝">何时使用浅拷贝</h2><ul><li>平面对象：处理没有嵌套属性的简单对象时。</li><li>性能：当速度至关重要，并且你不需要处理深层嵌套的数据时。</li><li>临时更改：当你打算修改顶级属性但共享嵌套数据时。</li></ul><p><strong>示例用例</strong></p><p>复制用户的设置对象以进行快速调整：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userSettings = &#123; <span class="attr">theme</span>: <span class="string">&quot;dark&quot;</span>, <span class="attr">layout</span>: <span class="string">&quot;grid&quot;</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> updatedSettings = &#123; ...userSettings, <span class="attr">layout</span>: <span class="string">&quot;list&quot;</span> &#125;;</span><br></pre></td></tr></table></figure><h2 id="何时使用深度复制">何时使用深度复制</h2><ul><li>复杂结构：适用于具有多层嵌套的对象。</li><li>避免副作用：当你需要确保副本中的更改不会影响原始内容时。</li><li>状态管理：在 <code>React</code> 或 <code>Redux</code> 等框架中，不变性至关重要。</li></ul><p><strong>示例用例</strong></p><p>复制游戏或应用程序的状态：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">const</span> gameState = &#123;</span><br><span class="line">    <span class="attr">level</span>: <span class="number">5</span>,</span><br><span class="line">    <span class="attr">inventory</span>: &#123;</span><br><span class="line">        <span class="attr">weapons</span>: [<span class="string">&quot;sword&quot;</span>, <span class="string">&quot;shield&quot;</span>],</span><br><span class="line">        <span class="attr">potions</span>: <span class="number">3</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Deep copy ensures no side effects</span></span><br><span class="line"><span class="keyword">const</span> savedState = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(gameState));</span><br></pre></td></tr></table></figure><h2 id="常见错误和陷阱">常见错误和陷阱</h2><ol><li>假设浅拷贝总是足够的：</li></ol><p>开发人员经常错误地对嵌套对象使用浅拷贝方法，导致原始数据发生意外更改。</p><ol start="2"><li>过度使用 <code>JSON</code> 方法：</li></ol><p>虽然 <code>JSON.stringify</code>/<code>JSON.parse</code> 很简单，但它并不适用于所有对象（例如，包含方法或循环引用的对象）。</p><ol start="3"><li>忽视性能：</li></ol><p>深拷贝方法可能会更慢，尤其是对于大型对象，因此请谨慎使用它们。</p><h1>总结</h1><p>了解浅拷贝和深拷贝之间的区别对于编写无错误的 <code>JavaScript</code> 代码至关重要。浅拷贝对于平面结构很有效，而深拷贝对于复杂的嵌套对象则必不可少。根据你的数据结构和应用程序需求选择适当的方法，并通过了解每种方法的局限性来避免潜在的陷阱。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在 &lt;code&gt;JavaScript&lt;/code&gt; 中使用对象和数组时，创建数据结构的副本是一项常见任务。然而，开发人员在决定浅拷贝和深拷贝时经常面临挑战。误解这些差异可能会导致代码中出现意想不到的副作用。让我们深入研究这些概念、它们的区别以及何时使用它们。&lt;/p&gt;
&lt;h1</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="浅拷贝与深拷贝" scheme="https://www.webape.net/tags/%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D/"/>
    
  </entry>
  
  <entry>
    <title>vue2 使用 jest+@vue/test-utils 单元测试用例方法</title>
    <link href="https://www.webape.net/vue2-%E4%BD%BF%E7%94%A8-jest-vue-test-utils-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E6%96%B9%E6%B3%95/"/>
    <id>https://www.webape.net/vue2-%E4%BD%BF%E7%94%A8-jest-vue-test-utils-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E6%96%B9%E6%B3%95/</id>
    <published>2024-03-21T11:13:30.000Z</published>
    <updated>2025-10-23T07:46:18.230Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章主要是说我们如何在 <code>vue2</code> 的环境下，使用 <code>jest</code> 配合 <code>@vue/test-utils</code> 来进行单元测试的内容。</p><h1>创建项目</h1><p>首先，我们使用 <code>vue</code> 的创建一个项目，在后面的选择单元测试功能的步骤时选择使用 <code>jest</code> 进行单元测试。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vue create demo</span><br></pre></td></tr></table></figure><p>创建完成后，会有一些关键的依赖：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;@vue/cli-plugin-unit-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;~5.0.8&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;@vue/vue2-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;babel-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.5.1&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^27.5.1&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>为了配合我们的功能测试，例如 <code>mock</code> 接口数据、保证所有的 <code>promise</code> 都被处理完、浏览器的一内置函数使用等功能，需我们安装另外的库</p><p>安装 <code>flush-promises</code>，保证所有的 <code>promise</code> 都被处理完。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i flush-promises -D</span><br></pre></td></tr></table></figure><h1>测试用例</h1><p>我们可以编写下面一个测试用例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; mount &#125; <span class="keyword">from</span> <span class="string">&quot;@vue/test-utils&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 因为在被测试组件中，使用了Vuex的功能，需要模拟其功能，避免在运行时报错</span></span><br><span class="line">jest.<span class="title function_">mock</span>(<span class="string">&quot;../../packages/store&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> mockState = &#123;</span><br><span class="line">        <span class="attr">priv</span>: &#123; <span class="attr">license</span>: &#123; <span class="attr">MAXSESSION</span>: <span class="number">5</span> &#125; &#125;,</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> mockStore = &#123; <span class="attr">state</span>: mockState, <span class="attr">commit</span>: jest.<span class="title function_">fn</span>() &#125;;</span><br><span class="line">    <span class="keyword">return</span> mockStore;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引入被测试的组件</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ProtocolProxyPolicy</span> <span class="keyword">from</span> <span class="string">&quot;../../packages/views/policy.vue&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引入等待所有promise处理完的库</span></span><br><span class="line"><span class="keyword">import</span> flushPromises <span class="keyword">from</span> <span class="string">&quot;flush-promises&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引入定义的接口文件，同时进行模拟拦截</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">ApiCommon</span> <span class="keyword">from</span> <span class="string">&quot;../../packages/api/common&quot;</span>;</span><br><span class="line">jest.<span class="title function_">mock</span>(<span class="string">&quot;../../packages/api/common&quot;</span>);</span><br><span class="line"><span class="comment">// 自定义mock接口数据</span></span><br><span class="line"><span class="title class_">ApiCommon</span>.<span class="property">show</span>.<span class="title function_">mockImplementation</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(&#123;</span><br><span class="line">        <span class="attr">total</span>: <span class="number">12</span>,</span><br><span class="line">        <span class="attr">rows</span>: [</span><br><span class="line">            &#123; <span class="attr">protocol</span>: <span class="string">&quot;HTTP&quot;</span>, <span class="attr">task_id</span>: <span class="number">1</span> &#125;,</span><br><span class="line">            &#123; <span class="attr">protocol</span>: <span class="string">&quot;HTTP&quot;</span>, <span class="attr">task_id</span>: <span class="number">2</span> &#125;,</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">result</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&quot;访问控制策略列表测试&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 加载组件</span></span><br><span class="line">    <span class="keyword">const</span> wrapper = <span class="title function_">mount</span>(<span class="title class_">ProtocolProxyPolicy</span>, &#123;</span><br><span class="line">        <span class="attr">propsData</span>: &#123;</span><br><span class="line">            <span class="attr">group</span>: <span class="string">&quot;web&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="title function_">it</span>(<span class="string">&quot;验证是否正常渲染策略组&quot;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">        <span class="comment">// 等待所有的promise执行完</span></span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">flushPromises</span>();</span><br><span class="line">        <span class="title function_">expect</span>(</span><br><span class="line">            wrapper.<span class="title function_">findAll</span>(<span class="string">&quot;.el-table__body-wrapper tbody &gt; tr&quot;</span>).<span class="property">length</span></span><br><span class="line">        ).<span class="title function_">toBe</span>(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 或其他测试角度</span></span><br><span class="line">        <span class="comment">//expect(wrapper.vm.$data.tableData.length).toBe(4)</span></span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Vuex-的-mock">Vuex 的 mock</h2><p>请注意上面我们 <code>mock</code> 了 <code>Vuex</code> 的 <code>store</code> 的数据，如果不 <code>mock</code> 的话，在我们的被测试组件中有如下代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">&quot;@/store&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> maxSession = <span class="built_in">parseInt</span>(store.<span class="property">state</span>.<span class="property">priv</span>.<span class="property">license</span>[<span class="string">&quot;MAXSESSION&quot;</span>] ?? <span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>如果不 <code>mock</code> 的话，<code>store.state.priv</code> 一直不存在的。</p><p>如果被测试的组件，包含了第三方库，而第三方库中，会根据从 <code>window.sessionStorage</code> 中获取到的值进行渲染，这里我们进行测试组件时，需要预置 <code>window.sessionStorage</code> 对象值。</p><p>这里并不能像上面的那样去 <code>mock Vuex</code>，因为上面的 <code>Vuex</code> 的运用是在本组件中的，并不能影响第三方库中的取值方法，所以我需要需要使和到 <code>Jest</code> 的 <code>setupFiles</code> 配置 setupFiles</p><p>该配置选项允许你在测试运行之前执行一个或多个模块，这些模块通常用于全局设置和初始化操作，比如这里我们可以统一设置 <code>window</code> 的一些操作对象</p><p>文件： <code>/test/global.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义全局sessionStorage对象</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="variable language_">window</span>, <span class="string">&quot;sessionStorage&quot;</span>, &#123;</span><br><span class="line">    <span class="attr">value</span>: &#123;</span><br><span class="line">        <span class="attr">store</span>: &#123;&#125;,</span><br><span class="line">        <span class="title function_">setItem</span>(<span class="params">key, value</span>) &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">store</span>[key] = value;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title function_">getItem</span>(<span class="params">key</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">store</span>[key];</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title function_">removeItem</span>(<span class="params">key</span>) &#123;</span><br><span class="line">            <span class="keyword">delete</span> <span class="variable language_">this</span>.<span class="property">store</span>[key];</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title function_">getAll</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">store</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title function_">clear</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">store</span> = &#123;&#125;;</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">sessionStorage</span>.<span class="title function_">setItem</span>(<span class="string">&quot;priv&quot;</span>, <span class="string">&#x27;&#123;&quot;proto_proxy&quot;:&quot;rw&quot;&#125;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>然后在 <code>package.json</code> 中配置</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;preset&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@vue/cli-plugin-unit-jest&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;moduleNameMapper&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;^@tests/(.*)$&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;rootDir&gt;/tests/$1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;^@packages/(.*)$&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;rootDir&gt;/packages/$1&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;transformIgnorePatterns&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;/node_modules[/\\\\]/&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;setupFiles&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;&lt;rootDir&gt;/tests/global.js&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>其中 <code>setupFiles</code> 就是在每个用例测试之前都会执行的文件内容。</p><div class="tips"><p><strong>提示</strong></p><p>如果要对组件中的 <code>input</code> 的 <code>checkbox</code> 点击后进行测试，建议不要使用 <code>wrapper.trigger('click')</code>，有时候并不能生效，可以使用：</p><code>wrapper.find('input[type="checkbox"]').setChecked(true);<p>await wrapper.vm.$nextTick();<br></code></p></div><h2 id="JSDOM-的-IntersectionObserver">JSDOM 的 IntersectionObserver</h2><p>这时如果我们执行单元测试用例，会报错：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ReferenceError: IntersectionObserver is not defined</span><br></pre></td></tr></table></figure><p>这个错误表明在你的测试环境中缺少 <code>IntersectionObserver</code>，这通常是因为 <code>JSDOM</code> 环境不支持 <code>IntersectionObserver</code>。<code>IntersectionObserver</code> 是用来监听元素与视口交叉信息的 <code>API</code>，在浏览器中提供支持，但在 <code>Node.js</code> 环境中并不原生支持。</p><p>为了解决这个问题，你可以模拟 <code>IntersectionObserver</code>，或者使用第三方库来模拟它，以便在测试环境中使用。一个常见的解决方案是使用 <code>intersection-observer</code> 这个 <code>polyfill</code>。</p><p>你可以按照以下步骤来解决这个问题：</p><p>安装 <code>intersection-observer</code>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install intersection-observer</span><br></pre></td></tr></table></figure><p>在你的测试文件顶部引入 <code>polyfill</code>，并在 <code>beforeAll</code> 中全局设置：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">import</span> <span class="string">&quot;intersection-observer&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">beforeAll</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">IntersectionObserver</span> = jest.<span class="title function_">fn</span>(<span class="function">() =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">observe</span>: jest.<span class="title function_">fn</span>(),</span><br><span class="line">        <span class="attr">unobserve</span>: jest.<span class="title function_">fn</span>(),</span><br><span class="line">        <span class="attr">disconnect</span>: jest.<span class="title function_">fn</span>(),</span><br><span class="line">    &#125;));</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="统一引入">统一引入</h2><p>这样就基本可以满足测试需要了，但我们还要注意的一点时，在测试用例的开头，需要统一引入我们的常在 <code>main.js</code> 中定义的内容，如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ElementUI</span> <span class="keyword">from</span> <span class="string">&quot;element-ui&quot;</span>;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">use</span>(<span class="title class_">ElementUI</span>, &#123; <span class="attr">size</span>: <span class="string">&quot;small&quot;</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ProtocolProxyPolicy</span> <span class="keyword">from</span> <span class="string">&quot;../packages&quot;</span>;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">use</span>(<span class="title class_">ProtocolProxyPolicy</span>);</span><br></pre></td></tr></table></figure><p>我们可以将其定义在 <code>/test/main.js</code> 中，然后在测试用例文件中引用：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&quot;../main.js&quot;</span>);</span><br></pre></td></tr></table></figure><p>当然我们也可以把 <code>intersection-observer</code> 的内容也放到这个 <code>main.js</code> 文件中。</p><h2 id="mount-与-shallowMount">mount 与 shallowMount</h2><p>在使用 <code>@vue/test-utils</code> 的过程中，我们需要挂载组件 <code>@vue/test-utils</code> 提供了 <code>mount</code> 和 <code>shallowMount</code> 两个方法用于挂载 <code>Vue</code> 组件进行测试，它们之间的主要区别在于挂载的深度和性能方面。</p><ul><li>mount：</li></ul><p><code>mount</code> 方法会挂载整个组件树，包括所有子组件，模拟了真实的渲染环境。这意味着 <code>mount</code> 会渲染组件及其所有子组件，可以用于测试组件及其子组件之间的交互。由于渲染了整个组件树，<code>mount</code> 的性能比较低，适合测试较复杂的组件。</p><ul><li>shallowMount：</li></ul><p><code>shallowMount</code> 方法只挂载了被测试组件本身，不会渲染其子组件。这意味着 <code>shallowMount</code> 只测试被挂载组件本身的行为，而不涉及其子组件。由于不渲染子组件，<code>shallowMount</code> 的性能比较高，适合测试简单的组件或者只关注被测试组件自身逻辑的情况。</p><p>选择使用 <code>mount</code> 还是 <code>shallowMount</code> 取决于你的测试需求。如果需要测试整个组件树的交互和行为，可以选择 <code>mount</code>；</p><p>如果只需要测试被挂载组件本身的行为，可以选择 <code>shallowMount</code>。通常建议优先使用 <code>shallowMount</code>，因为它能提供更快的测试速度，除非你需要测试整个组件树的交互。</p><h2 id="Dom-挂载">Dom 挂载</h2><p>当被测试组件中有使用 <code>document</code> 对象的内容时，如果不挂载到 <code>document</code> 中，就会出现不能获取 <code>document</code> 的变化的情况，仅用 <code>wrapper</code> 只能获取到当前组件的 <code>html</code>，当需要获取在组件外的 <code>html</code> 时，我们就需要支持 <code>document</code> 的获取。</p><p>我们可以像这样写：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">describe</span>(<span class="string">&quot;客户端策略列表测试&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> wrapper;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        wrapper = <span class="title function_">mount</span>(<span class="title class_">ProtocolProxyPolicy</span>, &#123;</span><br><span class="line">            <span class="attr">propsData</span>: &#123;</span><br><span class="line">                <span class="attr">group</span>: <span class="string">&quot;file&quot;</span>,</span><br><span class="line">                <span class="attr">showTab</span>: [<span class="string">&quot;client&quot;</span>],</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="comment">// 需要加上这个配置</span></span><br><span class="line">            <span class="attr">attachTo</span>: <span class="variable language_">document</span>.<span class="property">body</span>,</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        wrapper.<span class="title function_">destroy</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h1>额外的工具库</h1><p>当我们使用 <code>wrapper</code>，进行查找元素时，会有大量的重复、语义不太友好的代码存在，我们可以封装类似于像 <code>jquery</code> 一样的便捷 <code>DOM</code> 查询工具，来帮助我们简化单元测试用例。</p><p>例如下面就是两个针对表格和表单的工具类，更多逻辑可以自已封装。</p><p>form.js</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">form</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">wObj</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">wObj</span> = wObj;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">wrapper</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">wObj</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">in</span>(<span class="params">index</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">wObj</span> = <span class="variable language_">this</span>.<span class="property">wObj</span>.<span class="title function_">at</span>(index);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">input</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="attr">value</span>: <span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> getVal =</span><br><span class="line">                    <span class="variable language_">this</span>.<span class="property">wObj</span>.<span class="title function_">find</span>(<span class="string">&#x27;input[type=&quot;text&quot;]&#x27;</span>).<span class="property">element</span>.<span class="property">value</span>;</span><br><span class="line">                <span class="keyword">return</span> value !== <span class="literal">undefined</span> ? getVal === <span class="title class_">String</span>(value) : getVal;</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="attr">disabled</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> (</span><br><span class="line">                    <span class="variable language_">this</span>.<span class="property">wObj</span></span><br><span class="line">                        .<span class="title function_">find</span>(<span class="string">&#x27;input[type=&quot;text&quot;]&#x27;</span>)</span><br><span class="line">                        .<span class="title function_">attributes</span>(<span class="string">&quot;disabled&quot;</span>) === <span class="string">&quot;disabled&quot;</span></span><br><span class="line">                );</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">radio</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="attr">checked</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> (</span><br><span class="line">                    <span class="variable language_">this</span>.<span class="property">wObj</span></span><br><span class="line">                        .<span class="title function_">findAll</span>(<span class="string">&quot;label.el-radio&quot;</span>)</span><br><span class="line">                        .<span class="title function_">at</span>(key)</span><br><span class="line">                        .<span class="title function_">classes</span>(<span class="string">&quot;is-checked&quot;</span>) === <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="attr">disabled</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> (</span><br><span class="line">                    <span class="variable language_">this</span>.<span class="property">wObj</span></span><br><span class="line">                        .<span class="title function_">findAll</span>(<span class="string">&quot;label.el-radio&quot;</span>)</span><br><span class="line">                        .<span class="title function_">at</span>(key)</span><br><span class="line">                        .<span class="title function_">classes</span>(<span class="string">&quot;is-disabled&quot;</span>) === <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="attr">open</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> (</span><br><span class="line">                    <span class="variable language_">this</span>.<span class="property">wObj</span>.<span class="title function_">find</span>(<span class="string">&quot;div.el-switch&quot;</span>).<span class="title function_">classes</span>(<span class="string">&quot;is-checked&quot;</span>) ===</span><br><span class="line">                    <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">$f</span> = (<span class="params">wObj</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> obj = <span class="keyword">new</span> <span class="title function_">form</span>(wObj);</span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></details><p>像这样使用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; $f &#125; <span class="keyword">from</span> <span class="string">&quot;../class/form&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&quot;验证基本信息的回显&quot;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">flushPromises</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> fDom = wrapper</span><br><span class="line">        .<span class="title function_">findAll</span>(<span class="string">&quot;.policy-edit-form form.el-form&quot;</span>)</span><br><span class="line">        .<span class="title function_">at</span>(<span class="number">0</span>)</span><br><span class="line">        .<span class="title function_">findAll</span>(<span class="string">&quot;.el-form-item&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> data = listDataObj.<span class="property">data</span>[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> protocolType = $f(fDom).<span class="title function_">in</span>(<span class="number">0</span>).<span class="title function_">input</span>().<span class="title function_">value</span>(data.<span class="property">protocol</span>);</span><br><span class="line">    <span class="keyword">const</span> taskId = $f(fDom).<span class="title function_">in</span>(<span class="number">1</span>).<span class="title function_">input</span>().<span class="title function_">value</span>(data.<span class="property">task_id</span>);</span><br><span class="line">    <span class="keyword">const</span> name = $f(fDom).<span class="title function_">in</span>(<span class="number">3</span>).<span class="title function_">input</span>().<span class="title function_">value</span>(data.<span class="property">name</span>);</span><br><span class="line">    <span class="keyword">const</span> desc = $f(fDom).<span class="title function_">in</span>(<span class="number">4</span>).<span class="title function_">input</span>().<span class="title function_">value</span>(data.<span class="property">description</span>);</span><br><span class="line">    <span class="keyword">const</span> family = $f(fDom).<span class="title function_">in</span>(<span class="number">5</span>).<span class="title function_">radio</span>().<span class="title function_">checked</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">const</span> status = $f(fDom).<span class="title function_">in</span>(<span class="number">6</span>).<span class="title function_">switch</span>().<span class="title function_">open</span>();</span><br><span class="line">    <span class="title function_">expect</span>(protocolType &amp;&amp; taskId &amp;&amp; name &amp;&amp; desc &amp;&amp; family &amp;&amp; status).<span class="title function_">toBe</span>(</span><br><span class="line">        <span class="literal">true</span></span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>table.js</p><details><summary>点击查看完整代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">table</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">wObj</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">wObj</span> = wObj;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">wrapper</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">wObj</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">tr</span>(<span class="params">index</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">wObj</span> = <span class="variable language_">this</span>.<span class="property">wObj</span>.<span class="title function_">findAll</span>(<span class="string">&quot;tr&quot;</span>).<span class="title function_">at</span>(index);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">td</span>(<span class="params">index</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">wObj</span> = <span class="variable language_">this</span>.<span class="property">wObj</span>.<span class="title function_">findAll</span>(<span class="string">&quot;td&quot;</span>).<span class="title function_">at</span>(index);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">$t</span> = (<span class="params">wObj</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> obj = <span class="keyword">new</span> <span class="title function_">table</span>(wObj);</span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></details><p>像这样使用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">import</span> &#123; $t &#125; <span class="keyword">from</span> <span class="string">&quot;../class/table&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&quot;验证日志开关正常回显&quot;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">flushPromises</span>();</span><br><span class="line">    <span class="keyword">const</span> tDom = wrapper.<span class="title function_">find</span>(<span class="string">&quot;.el-table__body-wrapper tbody&quot;</span>);</span><br><span class="line">    <span class="title function_">expect</span>($t(tDom).<span class="title function_">tr</span>(<span class="number">0</span>).<span class="title function_">td</span>(<span class="number">15</span>).<span class="title function_">wrapper</span>().<span class="title function_">text</span>().<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&quot;&quot;</span>)).<span class="title function_">toBe</span>(</span><br><span class="line">        <span class="string">&quot;日志：已开启&quot;</span>.<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr/><p>更多文档可参考官方网站：</p><ul><li><a href="https://v1.test-utils.vuejs.org/zh/">Vue Test Utils</a></li><li><a href="https://www.jestjs.cn/">Jes</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这篇文章主要是说我们如何在 &lt;code&gt;vue2&lt;/code&gt; 的环境下，使用 &lt;code&gt;jest&lt;/code&gt; 配合 &lt;code&gt;@vue/test-utils&lt;/code&gt; 来进行单元测试的内容。&lt;/p&gt;
&lt;h1&gt;创建项目&lt;/h1&gt;
&lt;p&gt;首先，我们使用 &lt;code&gt;</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="vue" scheme="https://www.webape.net/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>科学上网，搭建 trojan 代理环境</title>
    <link href="https://www.webape.net/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91%EF%BC%8C%E6%90%AD%E5%BB%BA-trojan-%E4%BB%A3%E7%90%86%E7%8E%AF%E5%A2%83/"/>
    <id>https://www.webape.net/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91%EF%BC%8C%E6%90%AD%E5%BB%BA-trojan-%E4%BB%A3%E7%90%86%E7%8E%AF%E5%A2%83/</id>
    <published>2023-10-16T14:43:54.000Z</published>
    <updated>2025-10-23T07:46:18.234Z</updated>
    
    <content type="html"><![CDATA[<p>这里为什么没有再推荐使用 <code>shadowsocks</code> 了，因为经过实际的使用，<code>shadowsocks</code> 的标识太过于明显，很容易被识别拦截。</p><p>今天我给大家推荐 <code>trojan-go</code> 的代理环境及安装，请合理使用，科学上网！</p><p>实际服务器强烈建议安装内核版本大于 <code>4.9</code> 的 linux 发行版本，例如 <strong>ubuntu 22.0</strong> ，因为启用 BBR 会极大提高网络传输效率。</p><h1>服务端</h1><h2 id="下载-trojan-go">下载 trojan-go:</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://github.com/p4gefau1t/trojan-go/releases/download/v0.10.6/trojan-go-linux-amd64.zip</span><br><span class="line">mkdir -p /root/trojan-go &amp;&amp; unzip -d /root/trojan-go/ trojan-go-linux-amd64.zip</span><br></pre></td></tr></table></figure><h2 id="拷贝配置文件出-trojan-目录：">拷贝配置文件出 trojan 目录：</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /root/trojan-go/example/server.json /root/trojan-go/config.json</span><br></pre></td></tr></table></figure><h2 id="修改配置文件：">修改配置文件：</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;run_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;server&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;local_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.0.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;local_port&quot;</span><span class="punctuation">:</span> <span class="number">1443</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remote_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;127.0.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remote_port&quot;</span><span class="punctuation">:</span> <span class="number">80</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;123456&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ssl&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;cert&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/www/server/panel/vhost/ssl/trojan.webape.net/fullchain.pem&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/www/server/panel/vhost/ssl/trojan.webape.net/privkey.pem&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;sni&quot;</span><span class="punctuation">:</span> <span class="string">&quot;trojan.webape.net&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;fallback_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;127.0.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;fallback_port&quot;</span><span class="punctuation">:</span> <span class="number">80</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reuse_session&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;session_ticket&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;session_timeout&quot;</span><span class="punctuation">:</span> <span class="number">600</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;plain_http_response&quot;</span><span class="punctuation">:</span> <span class="string">&quot;HTTP/1.1 200 OK\r\n\r\n&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tcp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;no_delay&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;keep_alive&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;prefer_ipv4&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;fast_open&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;fast_open_qlen&quot;</span><span class="punctuation">:</span> <span class="number">20</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;tcp_window_scale&quot;</span><span class="punctuation">:</span> <span class="number">65535</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;mux&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;concurrency&quot;</span><span class="punctuation">:</span> <span class="number">8</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;idle_timeout&quot;</span><span class="punctuation">:</span> <span class="number">60</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;router&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;block&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;geoip:private&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;geoip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/usr/share/trojan-go/geoip.dat&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;geosite&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/usr/share/trojan-go/geosite.dat&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;websocket&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>其中 <code>80</code> 是一个网站 <code>http</code> 地址，<code>1443</code> 也是一个 <code>ssl</code> 的站点，配置了 <code>ssl</code> 证书，需要注意该证书的过期后要重新申请</p><p>除了 SSL 证书外，Trojan-Go 的路由功能需要 geoip.dat 文件来根据 IP 地址进行地理定位和路由规则判断</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir -p /usr/share/trojan-go/</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载完整的 GeoIP 数据库</span></span><br><span class="line">sudo wget -O /usr/share/trojan-go/geoip.dat https://github.com/v2fly/geoip/releases/latest/download/geoip.dat</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载 Geosite 数据库文件</span></span><br><span class="line">sudo wget -O /usr/share/trojan-go/geosite.dat https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat</span><br></pre></td></tr></table></figure><h2 id="切换内核拥塞控制算法-BBR">切换内核拥塞控制算法 BBR</h2><p>TCP BBR(Bottleneck Bandwidth and Round-trip propagation time)是由 Google 开发的一种 TCP 拥塞控制算法，旨在显著提高互联网传输速度，同时减少网络延迟。BBR 的核心思想是基于网络的瓶颈带宽和往返时间(RTT)来控制网络流量，而不是依赖于传统方法中的数据包丢失作为网络拥塞的主要指标。BBR 拥塞控制算法在内核 <code>4.9</code> 版本正式发布，用户只需要简单开启即可。BBR 积极地争取带宽使用权，能更快突破 GFW 的限速窗口，其对抗 GFW 限速、丢包等轻度干扰效果明显。现在 BBR 算法存在多个版本，如需最新版本可能需要重新编译内核模块。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看内核版本</span></span><br><span class="line">uname -r</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;net.core.default_qdisc=fq&quot; | sudo tee -a /etc/sysctl.conf</span><br><span class="line">echo &quot;net.ipv4.tcp_congestion_control=bbr&quot; | sudo tee -a /etc/sysctl.conf</span><br><span class="line">sudo sysctl -p</span><br></pre></td></tr></table></figure><h2 id="启动服务">启动服务</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/root/trojan-go/trojan-go -config /root/trojan-go/config.json</span><br></pre></td></tr></table></figure><p>如果跳出了，则需要检测端口和配置是否正确</p><h2 id="开机自启动">开机自启动</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /usr/lib/systemd/system/trojan-go.service</span><br></pre></td></tr></table></figure><p>写入内容：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=trojan-go</span><br><span class="line">After=network.target nss-lookup.target</span><br><span class="line">Wants=network-online.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">ExecStart=/root/trojan-go/trojan-go -config /root/trojan-go/config.json</span><br><span class="line">Restart=on-failure</span><br><span class="line">RestartSec=10</span><br><span class="line">RestartPreventExitStatus=23</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p><strong>启动</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start trojan-go</span><br></pre></td></tr></table></figure><p><strong>关闭</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop trojan-go</span><br></pre></td></tr></table></figure><p><strong>设置开机自启动</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable trojan-go</span><br></pre></td></tr></table></figure><h1>客户端</h1><h2 id="安装-trojan">安装 trojan</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install trojan</span><br></pre></td></tr></table></figure><h2 id="客户端配置">客户端配置</h2><p>从服务端安装的文件中复制一份客户端示例配置 <code>/root/trojan-go/example/client.json</code> 到本地的 <code>/etc/trojan/config.json</code> 中新建配置文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;run_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;client&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;local_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;127.0.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;local_port&quot;</span><span class="punctuation">:</span> <span class="number">1080</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remote_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.3.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remote_port&quot;</span><span class="punctuation">:</span> <span class="number">2443</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;123456&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ssl&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;sni&quot;</span><span class="punctuation">:</span> <span class="string">&quot;trojan.webape.net&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;mux&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;router&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;bypass&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;geoip:cn&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;geoip:private&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;geosite:cn&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;geosite:private&quot;</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;block&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;geosite:category-ads&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;proxy&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;geosite:geolocation-!cn&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;default_policy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;proxy&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;geoip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/usr/share/trojan-go/geoip.dat&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;geosite&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/usr/share/trojan-go/geosite.dat&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="启动服务-2">启动服务</h2><p><strong>启动</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start trojan</span><br></pre></td></tr></table></figure><p><strong>关闭</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop trojan</span><br></pre></td></tr></table></figure><p><strong>设置开机自启动</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable trojan</span><br></pre></td></tr></table></figure><h2 id="验证">验证</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --socks5 127.0.0.1:1080 http://www.google.com -k</span><br></pre></td></tr></table></figure><p>能正确返回则表示配置正常</p><h2 id="使用">使用</h2><p>浏览器使用，配合 <code>switchyOmega</code> 浏览器插件，添加代理</p><ul><li>代理协议：socks5</li><li>代理服务器：127.0.0.1</li><li>代理端口：1080</li></ul><h1>简便应用</h1><p>在上面的过程中，我们也看到了，还需要安装客户端，而用客户端还是基于 <code>socks</code> 的，如果想要 <code>http</code> 的代理，并且在像 <code>windows</code> 环境中，就不是那么好用了。</p><p>如何解决这个问题呢，有一款应用非常好用，适合 <code>windows</code> 和 <code>ubuntu</code> 环境，就是<a href="https://github.com/MatsuriDayo/nekoray/releases">nekoray</a>，还有适合安卓环境的包。</p><p>安装后，新建配置并填写服务端的地址、端口、<code>trojan</code> 的密码后，这样默认本地就有了一个 <code>2080</code> 端口的代码服务</p><p>这样例如我们在使用 <code>switchyOmega</code> 浏览器插件中，就可以添加代理</p><ul><li>代理协议：http</li><li>代理服务器：127.0.0.1</li><li>代理端口：2080</li></ul><p>而且在设置 <code>git</code> 、<code>npm</code> 等代理时，也更加方便好用，而且在 <code>nekoray</code> 面板上，也可以直接设置全局代理。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这里为什么没有再推荐使用 &lt;code&gt;shadowsocks&lt;/code&gt; 了，因为经过实际的使用，&lt;code&gt;shadowsocks&lt;/code&gt; 的标识太过于明显，很容易被识别拦截。&lt;/p&gt;
&lt;p&gt;今天我给大家推荐 &lt;code&gt;trojan-go&lt;/code&gt; 的代理环</summary>
      
    
    
    
    <category term="工具" scheme="https://www.webape.net/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="trojan" scheme="https://www.webape.net/tags/trojan/"/>
    
  </entry>
  
  <entry>
    <title>vue 使用 webpack 构建应用优化</title>
    <link href="https://www.webape.net/vue-%E4%BD%BF%E7%94%A8-webpack-%E6%9E%84%E5%BB%BA%E5%BA%94%E7%94%A8%E4%BC%98%E5%8C%96/"/>
    <id>https://www.webape.net/vue-%E4%BD%BF%E7%94%A8-webpack-%E6%9E%84%E5%BB%BA%E5%BA%94%E7%94%A8%E4%BC%98%E5%8C%96/</id>
    <published>2023-09-06T14:05:04.000Z</published>
    <updated>2025-10-23T07:46:18.230Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 <code>webpack</code> 构建的 <code>vue</code> 应用中，我们常面临一个重要的性能问题：</p><p>打包构建的单个静态文件太大，导致加载请求的时间长，页面加载速度慢。</p><p>我们有两个解决方向：</p><p><strong>1. 构建分包</strong></p><p>即将我们引入的一些第三方向和资源进行分包处理，构建成多个小包，避免一个文件过大，可以随着页面变化的请求而变化</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">    <span class="attr">configureWebpack</span>: &#123;</span><br><span class="line">        <span class="attr">optimization</span>: &#123;</span><br><span class="line">            <span class="attr">runtimeChunk</span>: <span class="string">&quot;single&quot;</span>,</span><br><span class="line">            <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">                <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">                <span class="attr">maxInitialRequests</span>: <span class="number">10</span>,</span><br><span class="line">                <span class="comment">// 单位是字节</span></span><br><span class="line">                <span class="attr">minSize</span>: <span class="number">1024</span> * <span class="number">1</span> * <span class="number">1000</span>,</span><br><span class="line">                <span class="comment">// 单位是字节</span></span><br><span class="line">                <span class="attr">maxSize</span>: <span class="number">1024</span> * <span class="number">2</span> * <span class="number">1000</span>,</span><br><span class="line">                <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">                    <span class="attr">vendors</span>: &#123;</span><br><span class="line">                        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">                        <span class="attr">priority</span>: -<span class="number">10</span>,</span><br><span class="line">                        <span class="title function_">name</span>(<span class="params"><span class="variable language_">module</span></span>) &#123;</span><br><span class="line">                            <span class="keyword">const</span> matchResult = <span class="variable language_">module</span>.<span class="property">context</span>.<span class="title function_">match</span>(</span><br><span class="line">                                <span class="regexp">/[\\/]node_modules[\\/](.*?)([\\/]|$)/</span></span><br><span class="line">                            );</span><br><span class="line">                            <span class="keyword">const</span> packageName = matchResult</span><br><span class="line">                                ? matchResult[<span class="number">1</span>]</span><br><span class="line">                                : <span class="string">&quot;&quot;</span>;</span><br><span class="line">                            <span class="keyword">return</span> <span class="string">`npm.<span class="subst">$&#123;packageName.replace(<span class="string">&quot;@&quot;</span>, <span class="string">&quot;&quot;</span>)&#125;</span>`</span>;</span><br><span class="line">                        &#125;,</span><br><span class="line">                    &#125;,</span><br><span class="line">                &#125;,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 以避免构建后的代码中出现未转译的第三方依赖</span></span><br><span class="line">    <span class="attr">transpileDependencies</span>: [<span class="regexp">/node_modules[/\\\\]/</span>],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>2. 使用 GZip 压缩</strong></p><p>在我们的 <code>webpack</code> 配置中，可以这样配置</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">const</span> <span class="title class_">CompressionPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;compression-webpack-plugin&quot;</span>);</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">        process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&quot;production&quot;</span></span><br><span class="line">            ? <span class="keyword">new</span> <span class="title class_">CompressionPlugin</span>(&#123;</span><br><span class="line">                  <span class="attr">test</span>: <span class="regexp">/\.(js|css)?$/i</span>,</span><br><span class="line">                  <span class="attr">algorithm</span>: <span class="string">&quot;gzip&quot;</span>,</span><br><span class="line">                  <span class="attr">minRatio</span>: <span class="number">1</span>,</span><br><span class="line">                  <span class="attr">deleteOriginalAssets</span>: <span class="literal">true</span>,</span><br><span class="line">              &#125;)</span><br><span class="line">            : <span class="string">&quot;&quot;</span>,</span><br><span class="line">    ],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面我们使用了<code>&quot;compression-webpack-plugin&quot;: &quot;^10.0.0&quot;</code>,插件来完成 <code>gzip</code> 的压缩</p><p>在使用 <code>nginx</code> 的服务端环境，还需要配置进行支持：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">gzip on;</span><br><span class="line">gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;</span><br><span class="line">gzip_static on;</span><br></pre></td></tr></table></figure><p>这样，就可以提大地提高访问加载速度。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在使用 &lt;code&gt;webpack&lt;/code&gt; 构建的 &lt;code&gt;vue&lt;/code&gt; 应用中，我们常面临一个重要的性能问题：&lt;/p&gt;
&lt;p&gt;打包构建的单个静态文件太大，导致加载请求的时间长，页面加载速度慢。&lt;/p&gt;
&lt;p&gt;我们有两个解决方向：&lt;/p&gt;
&lt;p&gt;&lt;stro</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="vue" scheme="https://www.webape.net/tags/vue/"/>
    
    <category term="webpack" scheme="https://www.webape.net/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>vue本地计算md5与分片上传的重点</title>
    <link href="https://www.webape.net/vue%E6%9C%AC%E5%9C%B0%E8%AE%A1%E7%AE%97md5%E4%B8%8E%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0%E7%9A%84%E9%87%8D%E7%82%B9/"/>
    <id>https://www.webape.net/vue%E6%9C%AC%E5%9C%B0%E8%AE%A1%E7%AE%97md5%E4%B8%8E%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0%E7%9A%84%E9%87%8D%E7%82%B9/</id>
    <published>2023-08-09T14:41:12.000Z</published>
    <updated>2025-10-23T07:46:18.231Z</updated>
    
    <content type="html"><![CDATA[<p>我们在使用 vue 做上传文件的功能的时候，有一种比较复杂的需求场景是这样的：</p><ul><li>客户端本地需要要先计算文件的 <code>md5</code> 值，传递给后端进行比对，如果已经存在，则极速完成上传</li><li>文件需要分片传输，而且需要控制并发量</li><li>需要支持文件夹上传</li></ul><p>下面我们就上面几个需求需要关注的重点说明一下：</p><ol><li>对于本地计算 md5，可以安装库 <code>spark-md5</code>，然后引入</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">SparkMD5</span> <span class="keyword">from</span> <span class="string">&quot;spark-md5&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">    <span class="attr">methods</span>: &#123;</span><br><span class="line">        <span class="comment">// 计算文件的md5值</span></span><br><span class="line">        <span class="title function_">getMd5</span>(<span class="params">file, relativePath</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">let</span> blobSlice =</span><br><span class="line">                        <span class="title class_">File</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slice</span> ||</span><br><span class="line">                        <span class="title class_">File</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">mozSlice</span> ||</span><br><span class="line">                        <span class="title class_">File</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">webkitSlice</span>,</span><br><span class="line">                    chunkSize = <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>,</span><br><span class="line">                    chunks = <span class="title class_">Math</span>.<span class="title function_">ceil</span>(file.<span class="property">size</span> / chunkSize),</span><br><span class="line">                    currentChunk = <span class="number">0</span>,</span><br><span class="line">                    spark = <span class="keyword">new</span> <span class="title class_">SparkMD5</span>.<span class="title class_">ArrayBuffer</span>(),</span><br><span class="line">                    fileReader = <span class="keyword">new</span> <span class="title class_">FileReader</span>();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">const</span> <span class="title function_">loadNext</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">                    <span class="keyword">let</span> start = currentChunk * chunkSize,</span><br><span class="line">                        end =</span><br><span class="line">                            start + chunkSize &gt;= file.<span class="property">size</span></span><br><span class="line">                                ? file.<span class="property">size</span></span><br><span class="line">                                : start + chunkSize;</span><br><span class="line"></span><br><span class="line">                    fileReader.<span class="title function_">readAsArrayBuffer</span>(</span><br><span class="line">                        blobSlice.<span class="title function_">call</span>(file, start, end)</span><br><span class="line">                    );</span><br><span class="line">                &#125;;</span><br><span class="line"></span><br><span class="line">                fileReader.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">                    spark.<span class="title function_">append</span>(e.<span class="property">target</span>.<span class="property">result</span>);</span><br><span class="line">                    currentChunk++;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 这里可以加一些实时更新md5百分比进度的逻辑</span></span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> (currentChunk &lt; chunks) &#123;</span><br><span class="line">                        <span class="title function_">loadNext</span>();</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;finished loading&quot;</span>);</span><br><span class="line">                        <span class="title function_">resolve</span>(spark.<span class="title function_">end</span>());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;;</span><br><span class="line"></span><br><span class="line">                fileReader.<span class="property">onerror</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">                    <span class="title function_">reject</span>(<span class="string">&quot;计算失败&quot;</span>);</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&quot;md5计算失败&quot;</span>);</span><br><span class="line">                &#125;;</span><br><span class="line"></span><br><span class="line">                <span class="title function_">loadNext</span>();</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="2"><li>并发上传</li></ol><p>文件需要并发上传，我们要清楚一个浏览器的重要知识点，就是一个浏览器的 <code>TCP</code> 并发连接数是有限制的，一般是 <code>6</code> 个，这样我们的并发就千万不要超过这个值，具体可以在浏览器的 <code>network</code> 请求列表中，查看右侧的时间线，如果有是在“开始连接”中有很大的时间占用，就说明这个并发数太大了，没有控制好。</p><p>当我们一次性选择多个文件时，当开始上传时是同时并发上传吗？当然不是的，因为如果全部文件同时上传，而每个文件又要分片上传处理，这样就达不到控制上传并发的目的，这样我们一般的做法是一次只处理一个文件，每个文件再去控制并发数来达到控制整体并发的目的。</p><ol start="3"><li>文件夹与文件</li></ol><p>对于上传文件夹，在 <code>html</code> 中，我们给触发元素添加 <code>webkitdirectory</code> 属性，需要注意的是，一般个按钮不能同时实现上传文件夹和上传文件的功能，一般做法是二选一，用不同的按钮来实现不同的目的，在有拖拽的场景时，可以通过监听拖拽事件，来实现同时处理文件和文件夹的目的，不过还是需要自已打标识</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">    <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">getRemainFileCount</span>();</span><br><span class="line">        <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> that = <span class="variable language_">this</span>;</span><br><span class="line">            <span class="variable language_">document</span></span><br><span class="line">                .<span class="title function_">querySelector</span>(<span class="string">&quot;.el-upload-dragger&quot;</span>)</span><br><span class="line">                .<span class="title function_">addEventListener</span>(<span class="string">&quot;drop&quot;</span>, <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">                    <span class="keyword">let</span> items = e.<span class="property">dataTransfer</span>.<span class="property">items</span>;</span><br><span class="line">                    <span class="variable language_">this</span>.$nextTick(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">                        <span class="keyword">const</span> fileList = [];</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">const</span> <span class="title function_">processItem</span> = <span class="keyword">async</span> (<span class="params">item</span>) =&gt; &#123;</span><br><span class="line">                            <span class="keyword">if</span> (item.<span class="property">kind</span> === <span class="string">&quot;file&quot;</span>) &#123;</span><br><span class="line">                                <span class="keyword">let</span> entry = item.<span class="title function_">webkitGetAsEntry</span>();</span><br><span class="line">                                <span class="comment">// 重新对待选文件列表fileList赋值，不分文件夹内的文件还是一级文件</span></span><br><span class="line">                                <span class="keyword">await</span> that.<span class="title function_">getFileEntry</span>(entry, fileList);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 这里要等待所有的异步都处理完后，才能下一步，不能单步await</span></span><br><span class="line">                        <span class="keyword">const</span> promises = [];</span><br><span class="line">                        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; items.<span class="property">length</span>; i++) &#123;</span><br><span class="line">                            promises.<span class="title function_">push</span>(<span class="title function_">processItem</span>(items[i]));</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(promises);</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 这里可以对fileList之后的逻辑进行进一步的处理</span></span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="attr">methods</span>: &#123;</span><br><span class="line">        <span class="comment">// 获取文件条目</span></span><br><span class="line">        <span class="keyword">async</span> <span class="title function_">getFileEntry</span>(<span class="params">entry, fileList</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (entry.<span class="property">isFile</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">                    entry.<span class="title function_">file</span>(</span><br><span class="line">                        <span class="function">(<span class="params">file</span>) =&gt;</span> &#123;</span><br><span class="line">                            <span class="comment">// 比要求的限制多处理1个，避免过多无效地处理逻辑</span></span><br><span class="line">                            <span class="keyword">if</span> (fileList.<span class="property">length</span> &lt;= <span class="variable language_">this</span>.<span class="property">fileLimit</span>) &#123;</span><br><span class="line">                                <span class="keyword">let</span> path = entry.<span class="property">fullPath</span>.<span class="title function_">substring</span>(<span class="number">1</span>);</span><br><span class="line">                                <span class="comment">// 由于子文件的webkitRelativePath为空，并且File只读，这里构造新File来赋值相对路径</span></span><br><span class="line">                                <span class="keyword">let</span> newFile = <span class="keyword">new</span> <span class="title class_">File</span>([file], file.<span class="property">name</span>, &#123;</span><br><span class="line">                                    <span class="attr">type</span>: file.<span class="property">type</span>,</span><br><span class="line">                                &#125;);</span><br><span class="line">                                <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(</span><br><span class="line">                                    newFile,</span><br><span class="line">                                    <span class="string">&quot;webkitRelativePath&quot;</span>,</span><br><span class="line">                                    &#123;</span><br><span class="line">                                        <span class="attr">writable</span>: <span class="literal">false</span>,</span><br><span class="line">                                        <span class="comment">// 这里兼容点击上传和拖拽上传，相对路径保持一致，点击上传单文件的相对路径为空</span></span><br><span class="line">                                        <span class="attr">value</span>:</span><br><span class="line">                                            path.<span class="title function_">indexOf</span>(<span class="string">&quot;/&quot;</span>) &gt; -<span class="number">1</span> ? path : <span class="string">&quot;&quot;</span>,</span><br><span class="line">                                    &#125;</span><br><span class="line">                                );</span><br><span class="line">                                <span class="keyword">const</span> fileItem = &#123;</span><br><span class="line">                                    <span class="attr">name</span>: path,</span><br><span class="line">                                    <span class="attr">percentage</span>: <span class="number">0</span>,</span><br><span class="line">                                    <span class="attr">raw</span>: newFile,</span><br><span class="line">                                    <span class="attr">size</span>: newFile.<span class="property">size</span>,</span><br><span class="line">                                    <span class="attr">status</span>: <span class="string">&quot;ready&quot;</span>,</span><br><span class="line">                                    <span class="attr">uid</span>: newFile.<span class="property">uid</span>,</span><br><span class="line">                                &#125;;</span><br><span class="line">                                fileList.<span class="title function_">push</span>(fileItem);</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="title function_">resolve</span>();</span><br><span class="line">                        &#125;,</span><br><span class="line">                        <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">                            <span class="variable language_">console</span>.<span class="title function_">log</span>(e);</span><br><span class="line">                            <span class="title function_">reject</span>(e);</span><br><span class="line">                        &#125;</span><br><span class="line">                    );</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">let</span> reader = entry.<span class="title function_">createReader</span>();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">let</span> entries = <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">                    reader.<span class="title function_">readEntries</span>(resolve, reject);</span><br><span class="line">                &#125;);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 处理文件夹中的每个文件或子文件夹</span></span><br><span class="line">                <span class="keyword">let</span> promises = entries.<span class="title function_">map</span>(<span class="function">(<span class="params">entry</span>) =&gt;</span></span><br><span class="line">                    <span class="variable language_">this</span>.<span class="title function_">getFileEntry</span>(entry, fileList)</span><br><span class="line">                );</span><br><span class="line">                <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(promises);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>对于文件夹的整体处理，需要关注属性 <code>webkitRelativePath</code> ，然后整体赋值其相对路径，让后端能够正常处理。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我们在使用 vue 做上传文件的功能的时候，有一种比较复杂的需求场景是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端本地需要要先计算文件的 &lt;code&gt;md5&lt;/code&gt; 值，传递给后端进行比对，如果已经存在，则极速完成上传&lt;/li&gt;
&lt;li&gt;文件需要分片传输，而且需要控制并</summary>
      
    
    
    
    <category term="javascript" scheme="https://www.webape.net/categories/javascript/"/>
    
    
    <category term="vue" scheme="https://www.webape.net/tags/vue/"/>
    
    <category term="分片上传" scheme="https://www.webape.net/tags/%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0/"/>
    
    <category term="本地md5计算" scheme="https://www.webape.net/tags/%E6%9C%AC%E5%9C%B0md5%E8%AE%A1%E7%AE%97/"/>
    
  </entry>
  
  <entry>
    <title>git提交代码后同步到svn仓库</title>
    <link href="https://www.webape.net/git%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81%E5%90%8E%E5%90%8C%E6%AD%A5%E5%88%B0svn%E4%BB%93%E5%BA%93/"/>
    <id>https://www.webape.net/git%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81%E5%90%8E%E5%90%8C%E6%AD%A5%E5%88%B0svn%E4%BB%93%E5%BA%93/</id>
    <published>2023-07-14T11:35:08.000Z</published>
    <updated>2025-10-23T07:46:18.229Z</updated>
    
    <content type="html"><![CDATA[<h1>需求场景</h1><p>对于程序员来说，版本控制工具有 <code>git</code> 和 <code>svn</code> 可以选择，<code>svn</code> 由于其细致的权限管理配置，在很多公司中仍然有应用场景，但在实际项目开发中，<code>git</code> 由于其优越的分布式架构更受程序员的欢迎。在大多数程序开发过程中，都会推崇 <code>git</code> 优秀的版本控制思想。</p><p>这样就有了一个需求场景，在日常开发中想使用 <code>git</code>,但公司的版本控制又要求存储使用 <code>svn</code>。</p><h1>实现步骤</h1><p>整体思路步骤为：</p><h2 id="1-自已搭建一个-git-服务器，用于存储各个员工的-git-提交">1. 自已搭建一个 git 服务器，用于存储各个员工的 git 提交</h2><ul><li>这里建议使用 <code>gitea</code> 来搭建，以下是 <code>docker</code> 的方式</li></ul><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">    <span class="attr">gitea-server:</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">gitea/gitea:latest</span></span><br><span class="line">        <span class="attr">container_name:</span> <span class="string">gitea</span></span><br><span class="line">        <span class="attr">environment:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">USER_UID=1000</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">USER_GID=1000</span></span><br><span class="line">        <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">        <span class="attr">volumes:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">./data:/data</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">/etc/timezone:/etc/timezone:ro</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">/etc/localtime:/etc/localtime:ro</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="number">3000</span><span class="string">:3000</span></span><br></pre></td></tr></table></figure><h2 id="2-在-git-的-hook-点-post-receive-中添加-git-同步-svn-的脚本逻辑">2. 在 git 的 hook 点 post-receive 中添加 git 同步 svn 的脚本逻辑</h2><p>在 <code>post-receive</code> 的挂载点目录中默认有一个 <code>test.sh</code>，可以在里面修改后挂载 <code>python</code> 执行脚本</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">echo $&#123;data&#125; | while read p_commit c_commit branch; do</span><br><span class="line">        result=$(echo $&#123;branch&#125;| grep &quot;main&quot;)</span><br><span class="line">        if [ &quot;$result&quot; != &quot;&quot; ];then</span><br><span class="line">                python /data/git/repositories/wang_guoliang/test-git.git/hooks/post-receive.d/sync-svn.py $c_commit $p_commit</span><br><span class="line">        fi</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>其主要步骤是：</p><ul><li>将当次提交的内容检出到指定的缓存目录中</li><li>将缓存目录中的文件拷贝到 <code>git svn</code> 目录</li><li>在 <code>svn</code> 目录中提交记录</li></ul><p>在 <code>svn</code> 客户端目录要向服务端提交记录，可以用 <code>svn</code>，也可以用 <code>git svn</code>，这里我们使用 <code>git svn</code> 来做，利用其可以指定工作目录的的特点来实现</p><p><code>git-svn</code> 默认包含在 <code>Git</code> 的安装包中，不过在 <code>Ubuntu</code> 中，<code>git-svn</code> 是作为一个独立的 <code>Package</code> 需要额外安装的。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install git-svn</span><br></pre></td></tr></table></figure><p>以下是完整的脚本代码：</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># post-receive</span></span><br><span class="line"><span class="comment"># 提交后git同步svn提交记录</span></span><br><span class="line"><span class="comment"># 注意 svn 对接目录需要修改所属用户为git</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">argv = sys.argv</span><br><span class="line"></span><br><span class="line"><span class="comment"># 加载配置</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;/data/git-svn-config.json&quot;</span>, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;UTF-8&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    git_svn_config = json.load(f)</span><br><span class="line"></span><br><span class="line"><span class="comment"># git 检出临时目录</span></span><br><span class="line">shell_git_tmp_dir = git_svn_config[<span class="string">&#x27;dir&#x27;</span>][<span class="string">&#x27;git_svn_tmp&#x27;</span>]</span><br><span class="line"><span class="comment"># svn 中转目录</span></span><br><span class="line">shell_git_svn_dir = git_svn_config[<span class="string">&#x27;dir&#x27;</span>][<span class="string">&#x27;git_svn&#x27;</span>]</span><br><span class="line"><span class="comment"># svn 地址</span></span><br><span class="line">svn_repository = git_svn_config[<span class="string">&#x27;svn&#x27;</span>][<span class="string">&#x27;repository&#x27;</span>]</span><br><span class="line"><span class="comment"># svn 默认用户名</span></span><br><span class="line">shell_svn_username = git_svn_config[<span class="string">&#x27;svn&#x27;</span>][<span class="string">&#x27;default_uname&#x27;</span>]</span><br><span class="line"><span class="comment"># svn 默认密码</span></span><br><span class="line">shell_svn_password = git_svn_config[<span class="string">&#x27;svn&#x27;</span>][<span class="string">&#x27;default_pwd&#x27;</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="keyword">if</span> <span class="keyword">not</span> os.path.exists(shell_git_tmp_dir):</span><br><span class="line">    os.makedirs(shell_git_tmp_dir)</span><br><span class="line"><span class="comment"># 初始中转目录</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(shell_git_svn_dir):</span><br><span class="line">    os.makedirs(shell_git_svn_dir)</span><br><span class="line"><span class="comment"># 初始git svn项目</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(shell_git_svn_dir + <span class="string">&#x27;/.git&#x27;</span>):</span><br><span class="line">    git_svn_init_result = subprocess.getoutput(</span><br><span class="line">        <span class="string">&#x27;echo &#x27;</span> +</span><br><span class="line">        shell_svn_password +</span><br><span class="line">        <span class="string">&#x27; | git svn clone &#x27;</span> +</span><br><span class="line">        svn_repository +</span><br><span class="line">        <span class="string">&#x27; &#x27;</span> +</span><br><span class="line">        shell_git_svn_dir +</span><br><span class="line">        <span class="string">&#x27; --username &#x27;</span> +</span><br><span class="line">        shell_svn_username)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;git_svn_init_result: &#x27;</span> + git_svn_init_result)</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="keyword">def</span> <span class="title function_">add_commit</span>(<span class="params">line</span>):</span><br><span class="line">    lineArr = line.split(<span class="string">&#x27; - &#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(lineArr)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;rm -rf &#x27;</span> + shell_git_tmp_dir + <span class="string">&#x27;/.&#x27;</span>)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;GIT_WORK_TREE=&#x27;</span> + shell_git_tmp_dir + <span class="string">&#x27; git checkout &#x27;</span> + argv[<span class="number">1</span>] + <span class="string">&#x27; -f &#x27;</span>)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;rm -rf &#x27;</span> + shell_git_tmp_dir + <span class="string">&#x27;/.git&#x27;</span>)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;rm -rf &#x27;</span> + shell_git_svn_dir + <span class="string">&#x27;/*&#x27;</span>)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;cp -r &#x27;</span> + shell_git_tmp_dir + <span class="string">&#x27;/. &#x27;</span> + shell_git_svn_dir + <span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">    subprocess.getoutput(<span class="string">&#x27;git --work-tree=&#x27;</span> + shell_git_svn_dir + <span class="string">&#x27; --git-dir=&#x27;</span> + shell_git_svn_dir + <span class="string">&#x27;/.git add . &#x27;</span>)</span><br><span class="line">    result_commit = subprocess.getoutput(</span><br><span class="line">        <span class="string">&#x27;git --work-tree=&#x27;</span> +</span><br><span class="line">        shell_git_svn_dir +</span><br><span class="line">        <span class="string">&#x27; --git-dir=&#x27;</span> +</span><br><span class="line">        shell_git_svn_dir +</span><br><span class="line">        <span class="string">&#x27;/.git commit -m &quot;&#x27;</span> +</span><br><span class="line">        lineArr[<span class="number">2</span>] +</span><br><span class="line">        <span class="string">&#x27;&quot;&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;svn-commit: &#x27;</span> + result_commit)</span><br><span class="line">    <span class="keyword">global</span> shell_svn_username</span><br><span class="line">    shell_svn_username = lineArr[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 根据git用户名获取svn用户信息</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">find_svn_info</span>(<span class="params">git_uname, notFound=&#123;&#125;</span>):</span><br><span class="line">    user_list = git_svn_config[<span class="string">&#x27;user&#x27;</span>]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(user_list)):</span><br><span class="line">        <span class="keyword">if</span> user_list[i][<span class="string">&#x27;git&#x27;</span>][<span class="string">&#x27;uname&#x27;</span>] == git_uname:</span><br><span class="line">            <span class="keyword">return</span> user_list[i][<span class="string">&#x27;svn&#x27;</span>]</span><br><span class="line">    <span class="keyword">return</span> notFound</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="keyword">global</span> shell_svn_username</span><br><span class="line">    <span class="keyword">global</span> shell_svn_password</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 查询git仓库的日志并提交记录</span></span><br><span class="line">    log_list = subprocess.getoutput(<span class="string">&#x27;git log main --pretty=format:&quot;%h - %an - %s&quot; &#x27;</span> + argv[<span class="number">1</span>] + <span class="string">&#x27; ^&#x27;</span> + argv[<span class="number">2</span>])</span><br><span class="line">    <span class="built_in">list</span> = log_list.splitlines()</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">list</span>:</span><br><span class="line">        add_commit(line)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 设置svn提交的帐户和密码</span></span><br><span class="line">    svn_user_info = find_svn_info(shell_svn_username)</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&#x27;uname&#x27;</span> <span class="keyword">in</span> svn_user_info:</span><br><span class="line">        shell_svn_username = svn_user_info[<span class="string">&#x27;uname&#x27;</span>]</span><br><span class="line">        shell_svn_password = svn_user_info[<span class="string">&#x27;pwd&#x27;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 向svn仓库推送</span></span><br><span class="line">    result_push = subprocess.getoutput(</span><br><span class="line">        <span class="string">&#x27;echo &#x27;</span> +</span><br><span class="line">        shell_svn_password +</span><br><span class="line">        <span class="string">&#x27; | git --work-tree=&#x27;</span> +</span><br><span class="line">        shell_git_svn_dir +</span><br><span class="line">        <span class="string">&#x27; --git-dir=&#x27;</span> +</span><br><span class="line">        shell_git_svn_dir +</span><br><span class="line">        <span class="string">&#x27;/.git svn dcommit --username &#x27;</span> +</span><br><span class="line">        shell_svn_username)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;svn-push: &#x27;</span> + result_push)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>配置文件<code>git-svn-config.json</code>内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;git&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wgl&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wang_guoliang@webape.net&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;svn&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wgl&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;pwd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123456&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;git&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usr1@webape.net&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;svn&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;pwd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123456&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;git&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user2&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usr2@webape.net&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;svn&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user2&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;pwd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123456&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dir&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;git_chk&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/tmp/test-git-check&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;git_svn_tmp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/tmp/test-git-tmp&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;git_svn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/tmp/test-git&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;svn&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;repository&quot;</span><span class="punctuation">:</span> <span class="string">&quot;svn://10.28.34.219/cloud-platform&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;default_uname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;default_pwd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;123456&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;需求场景&lt;/h1&gt;
&lt;p&gt;对于程序员来说，版本控制工具有 &lt;code&gt;git&lt;/code&gt; 和 &lt;code&gt;svn&lt;/code&gt; 可以选择，&lt;code&gt;svn&lt;/code&gt; 由于其细致的权限管理配置，在很多公司中仍然有应用场景，但在实际项目开发中，&lt;code&gt;git&lt;/c</summary>
      
    
    
    
    
  </entry>
  
</feed>
