<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>celsius的个人博客</title>
  
  <subtitle>星星之火可以燎原</subtitle>
  <link href="http://celsius/atom.xml" rel="self"/>
  
  <link href="http://celsius/"/>
  <updated>2023-12-12T07:59:48.061Z</updated>
  <id>http://celsius/</id>
  
  <author>
    <name>Yunzhong Zhang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>谷歌浏览器使用AppEmit中间件调用OCX控件，网页获取读卡器信息</title>
    <link href="http://celsius/2023/12/11/%E8%B0%B7%E6%AD%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BD%BF%E7%94%A8AppEmit%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%B0%83%E7%94%A8OCX%E6%8E%A7%E4%BB%B6%EF%BC%8C%E7%BD%91%E9%A1%B5%E8%8E%B7%E5%8F%96%E8%AF%BB%E5%8D%A1%E5%99%A8%E4%BF%A1%E6%81%AF/"/>
    <id>http://celsius/2023/12/11/%E8%B0%B7%E6%AD%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BD%BF%E7%94%A8AppEmit%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%B0%83%E7%94%A8OCX%E6%8E%A7%E4%BB%B6%EF%BC%8C%E7%BD%91%E9%A1%B5%E8%8E%B7%E5%8F%96%E8%AF%BB%E5%8D%A1%E5%99%A8%E4%BF%A1%E6%81%AF/</id>
    <published>2023-12-11T08:31:05.000Z</published>
    <updated>2023-12-12T07:59:48.061Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>@<a href="%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">TOC</a></p><hr><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>废话不多说，我又来搞前端网页连接读卡器了，这次的设备是明华智能支付终端，虽然叫支付终端，但是还支持读取 银行卡，身份证，接触类IC卡，非接触类IC卡，二维码，记录一下技术方案</p><hr><h1 id="一、开发方案"><a href="#一、开发方案" class="headerlink" title="一、开发方案"></a>一、开发方案</h1><p>前端想要连接硬件，那必须要硬件的驱动支持并提供对应的方案，目前我知道的web方案的话有</p><ul><li>ocx控件，基于ActiveX技术，对于前端来说最大的缺点就是<strong>只能运行在IE内核内</strong>，并且<strong>比较挑运行环境</strong></li><li>websocket，驱动在本地开启一个socket服务，页面通过websocket连接，可以实现双向通信</li><li>占用一个本地网络端口，页面通过请求本地ip + 对应端口获取数据，优点是接入非常简单</li></ul><p> 而这次就遇到了最难搞的ocx控件，而接入ocx最简单的方法就是让用户在IE内核的浏览器内运行（比如IE，Edge IE模式，360浏览器兼容模式），但是IE内核那及其恶心的兼容性，让我放弃了这个方法，转而去网上寻找能在谷歌浏览器运行ocx的方法。</p><p>如果你有像我一样去找的话，就会发现大部分的回答都是通过谷歌浏览器napi的办法去调用ocx，这个办法当然是能行的，但是谷歌浏览器在46版本之后就不支持napi了，然而我用的谷歌浏览器版本已经是119，不可能去降版本（更不可能让用户去降版本），只好继续寻找，最终，发现了一个神奇的东西——中间件</p><h1 id="二、中间件"><a href="#二、中间件" class="headerlink" title="二、中间件"></a>二、中间件</h1><h2 id="1-选择中间件"><a href="#1-选择中间件" class="headerlink" title="1. 选择中间件"></a>1. 选择中间件</h2><p>这里的中间件，指的是硬件驱动和网页之间连接的一个转发层，类似于桥接模式，目前我在网上找到了两款中间件</p><ul><li>AppEmit（<a href="https://www.appemit.com/">https://www.appemit.com/</a>）</li><li>猿大师（<a href="http://www.yuanmaster.com/xiazai/">http://www.yuanmaster.com/xiazai/</a>）</li></ul><p>我大致看了一下，两个中间件的功能都挺牛逼的，能够让网页调用office，调用桌面程序功能，当然也能调用ocx对应的函数，不过猿大师要收费，AppEmit的免费版只是有弹窗，因此选择AppEmit</p><h2 id="2-接入中间件"><a href="#2-接入中间件" class="headerlink" title="2. 接入中间件"></a>2. 接入中间件</h2><p>首先还是去看文档，AppEmit提供的demo还是挺多的，并且，对于ocx的接入也有两个方案</p><ol><li>将ocx控件所对应的桌面程序函数提取出来，转换为dll文件，然后通过中间件直接调用</li><li>在谷歌浏览器中，嵌入一个IE内核进来调用ocx控件，并通过中间件来控制调用以及通信，这里我用的就是这个方案</li></ol><p> 完整代码以及安装程序我都放在了github上，地址在最后，这里放一下核心代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><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">&lt;script setup&gt;</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> &#123; ref &#125; <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span></span><br><span class="line"><span class="keyword">const</span> info = ref(<span class="literal">null</span>)</span><br><span class="line"><span class="keyword">const</span> getData = <span class="function">(<span class="params">val</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 获取传过来的数据</span></span><br><span class="line">  info.value = val</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">window</span>.getData = getData</span><br><span class="line"><span class="keyword">const</span> openDevice = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  AE.InitApp(<span class="string">&#x27;ws://localhost:80/appemit?cid=00000-1&amp;sid=1&amp;flag=1&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> openPage = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> ReqPar7 = &#123;</span><br><span class="line">    <span class="string">&quot;emit&quot;</span>: <span class="string">&quot;open&quot;</span>, <span class="string">&quot;Obj&quot;</span>: <span class="string">&quot;web&quot;</span>, <span class="string">&quot;AppType&quot;</span>: <span class="number">1</span>, <span class="string">&quot;pos&quot;</span>: <span class="number">1</span>, <span class="string">&quot;AppId&quot;</span>: <span class="number">1</span>, <span class="string">&quot;par&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;URL&quot;</span>: <span class="string">&#x27;/index.htm&#x27;</span>, <span class="string">&quot;htmlStr&quot;</span>: <span class="literal">null</span>, <span class="string">&quot;HttpServer_startUrl&quot;</span>: <span class="literal">null</span>                                         <span class="comment">//&quot;HttpServer_startUrl&quot;:&quot;/demo/htmlDemo/ocx.html&quot;   //使用本地html</span></span><br><span class="line">      , <span class="string">&quot;embed_object&quot;</span>: &#123;                               <span class="comment">//多个object可以设置embed_object为数组 </span></span><br><span class="line">        <span class="string">&quot;objName&quot;</span>: <span class="string">&quot;objIDCard&quot;</span></span><br><span class="line">        , <span class="string">&quot;reg&quot;</span>: <span class="literal">true</span>    <span class="comment">//null 不执行注册   1强制操作 true 未注册则注册  false 卸载注册 (重启AppEmit后，就不能加载dll了)  </span></span><br><span class="line">        , <span class="string">&quot;asAdmin&quot;</span>: <span class="number">0</span>   <span class="comment">//1需要管理员权限注册 0不需要 ,win7管理员身份会有提示确认窗口</span></span><br><span class="line">        <span class="comment">//  ,&quot;pid&quot;:&quot;292A53CD-DA8F-46A5-808D-B286F2759C37&quot; //使用私有插件,则需要增加下列信息 如果dll/ocx文件改变了，需要更新appemit登陆窗口中我的应用里面的sha1值</span></span><br><span class="line">        , <span class="string">&quot;dllFile&quot;</span>: <span class="string">&quot;/MT8ActiveX.ocx&quot;</span>      <span class="comment">//为了安全，限制为AppEmit文件夹，不能注册或者注销其它文件夹下面的ocx</span></span><br><span class="line">        , <span class="string">&quot;CLASSES_ROOT&quot;</span>: <span class="string">&quot;HKEY_CLASSES_ROOT\\真实的ReportProj1.ReportX\\CLSID&quot;</span>   <span class="comment">//可选，reg= true检查是否已经注册,一定要是\\分隔</span></span><br><span class="line">        , <span class="string">&quot;clsId&quot;</span>: <span class="string">&quot;10EC554B-357B-4188-9E5E-AC5039454D8B&quot;</span>                           <span class="comment">// 可选，reg= true检查是否已经注册</span></span><br><span class="line">        <span class="comment">//   ,&quot;AuthKey&quot;:&quot;A1-ZneY-2qGoXRfc7h6GZZxBB2gceORBwyhoxsA6GK5agLtIAwLhh6BnK61W8fORVNv&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">  AE.OpenApp(ReqPar7);</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="xml"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">alt</span>=<span class="string">&quot;Vue logo&quot;</span> <span class="attr">class</span>=<span class="string">&quot;logo&quot;</span> <span class="attr">src</span>=<span class="string">&quot;@/assets/logo.svg&quot;</span> <span class="attr">width</span>=<span class="string">&quot;125&quot;</span> <span class="attr">height</span>=<span class="string">&quot;125&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;btn-group&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">div</span> @<span class="attr">click</span>=<span class="string">&quot;openDevice&quot;</span>&gt;</span>建立中间件连接<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">div</span> @<span class="attr">click</span>=<span class="string">&quot;openPage&quot;</span>&gt;</span>开启读卡页面<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>返回的数据：<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span>&#123;&#123; info &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;AppEmbed1&quot;</span>  <span class="attr">style</span>=<span class="string">&quot;border: solid 2px #F0F0F0; min-width:30px;min-height:20px;width:600px;height:400px&quot;</span> &gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>运行页面<br><img src="https://img-blog.csdnimg.cn/direct/c1e9fa7664de48aea1ec4e46c6ec2b6a.png#pic_center" alt="在这里插入图片描述"><br>整个流程简单来说的话就是</p><ol><li>安装中间件</li><li>注册并登录中间件（发布之后修改cip和clientKey，就不需要这一步了）</li><li>通过websocket与中间件建立连接</li><li>向中间件发送一串json命令，其参数包括触发类型，触发参数，详见文档，这里我们的命令就是使用IE内核打开我们调用ocx控件的页面，同时运行ocx控件</li><li>在调用ocx控件的页面进行硬件连接，获取读卡信息等操作，并在获取数据之后，通过emitBack方法返回给我们的主页面</li><li>主页面拿到数据，进行对应的操作<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1>其实搞这么老多，最好的办法还是用钞能力让硬件厂家搞一下websocket支持，因为ocx控件有点挑运行环境<br>完整代码：<a href="https://github.com/Ceslsius/cardReader-demo">https://github.com/Ceslsius/cardReader-demo</a></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue 读卡器 中间件" scheme="http://celsius/tags/vue-%E8%AF%BB%E5%8D%A1%E5%99%A8-%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>vue网页调用华视电子身份证阅读器cvr-100uc</title>
    <link href="http://celsius/2023/11/02/vue%E7%BD%91%E9%A1%B5%E8%B0%83%E7%94%A8%E5%8D%8E%E8%A7%86%E7%94%B5%E5%AD%90%E8%BA%AB%E4%BB%BD%E8%AF%81%E9%98%85%E8%AF%BB%E5%99%A8cvr-100uc/"/>
    <id>http://celsius/2023/11/02/vue%E7%BD%91%E9%A1%B5%E8%B0%83%E7%94%A8%E5%8D%8E%E8%A7%86%E7%94%B5%E5%AD%90%E8%BA%AB%E4%BB%BD%E8%AF%81%E9%98%85%E8%AF%BB%E5%99%A8cvr-100uc/</id>
    <published>2023-11-02T08:25:26.000Z</published>
    <updated>2023-12-12T07:50:26.851Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>@<a href="%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">TOC</a></p><hr><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>接到一个前端调用身份证读卡器的需求，提供的设备是华视CVR-100uc，记录一下开发过程</p><hr><h1 id="一、寻找开发包"><a href="#一、寻找开发包" class="headerlink" title="一、寻找开发包"></a>一、寻找开发包</h1><p>前端要调用硬件，肯定是需要硬件驱动支持的，因此我们需要去官网找找对应的驱动和开发包<br><a href="http://www.chinaidcard.com/cxxz.html">服务下载_华视电子</a><br>解压之后的web文件夹里就有对应前端控件，不过问题来了，这里提供的是ocx控件，而ocx控件在谷歌浏览器中并不能运行，只能运行在IE内核的浏览器里面</p><hr><p>没办法，在网上再搜一搜有没有其他解决办法，最后发现，华视电子官方提供的2022年的web开发包不是ocx控件，而是一个exe安装文件，于是我在网上找了一圈，总算找到了这个安装包（这个安装包我也放github上了），安装之后查看demo，发现他的原理是在本地占用一个端口，通过接口请求返回数据</p><h1 id="二、代码实现"><a href="#二、代码实现" class="headerlink" title="二、代码实现"></a>二、代码实现</h1><p>运行安装包后，直接请求对应的本地地址就行了，代码如下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><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></pre></td><td class="code"><pre><span class="line">&lt;script setup&gt;</span><br><span class="line"><span class="keyword">import</span> &#123; RouterLink, RouterView, stringifyQuery &#125; <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</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> HelloWorld <span class="keyword">from</span> <span class="string">&#x27;./components/HelloWorld.vue&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;ref&#125; <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span></span><br><span class="line"><span class="keyword">const</span> info = ref(<span class="literal">null</span>)</span><br><span class="line"><span class="keyword">const</span> openDevice = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  axios.get(<span class="string">&quot;http://localhost:19196/openDevice&quot;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(res)</span><br><span class="line">    <span class="keyword">if</span> (res.data.resultFlag == <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&#x27;建立链接成功&#x27;</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="keyword">const</span> readCard = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  axios.get(<span class="string">&quot;http://localhost:19196/readCard&quot;</span>).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (res.data.resultFlag == <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> obj = res.data</span><br><span class="line">      <span class="comment">// 处理身份证照片</span></span><br><span class="line">      obj.identityPic = <span class="string">&#x27;data:image/jpeg;base64,&#x27;</span> + obj.identityPic</span><br><span class="line">      <span class="comment">// 处理身份证类型</span></span><br><span class="line">      <span class="keyword">if</span> (obj.certType == <span class="string">&quot; &quot;</span>) &#123;</span><br><span class="line">        obj.certType = <span class="string">&quot;身份证&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (obj.certType == <span class="string">&quot;I&quot;</span>) &#123;</span><br><span class="line">        obj.certType = <span class="string">&quot;外国人居住证&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (obj.certType == <span class="string">&quot;J&quot;</span>) &#123;</span><br><span class="line">        obj.certType = <span class="string">&quot;港澳台居住证&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        obj.certType = <span class="string">&quot;未知&quot;</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      info.value = obj</span><br><span class="line">      <span class="built_in">console</span>.log(info.value)</span><br><span class="line">      <span class="built_in">console</span>.log(obj)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&#x27;读卡失败&#x27;</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="keyword">const</span> CloseDevice = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  axios.get(<span class="string">&quot;http://localhost:19196/CloseDevice&quot;</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="xml"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">alt</span>=<span class="string">&quot;Vue logo&quot;</span> <span class="attr">class</span>=<span class="string">&quot;logo&quot;</span> <span class="attr">src</span>=<span class="string">&quot;@/assets/logo.svg&quot;</span> <span class="attr">width</span>=<span class="string">&quot;125&quot;</span> <span class="attr">height</span>=<span class="string">&quot;125&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;btn-group&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">div</span> @<span class="attr">click</span>=<span class="string">&quot;openDevice&quot;</span>&gt;</span>建立链接<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">div</span> @<span class="attr">click</span>=<span class="string">&quot;readCard&quot;</span>&gt;</span>读取信息<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">div</span> @<span class="attr">click</span>=<span class="string">&quot;CloseDevice&quot;</span>&gt;</span>关闭连接<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">:src</span>=<span class="string">&quot;info.identityPic&quot;</span> <span class="attr">v-if</span>=<span class="string">&quot;info&quot;</span>/&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span>&#123;&#123; info ? JSON.stringify(info) : &#x27;&#x27; &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  <span class="comment">&lt;!-- &lt;RouterView /&gt; --&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>不过这里要验证一下发到服务器上之后能不能请求本地的网络端口，注意这里需要https</p><hr><h1 id="代码仓库地址"><a href="#代码仓库地址" class="headerlink" title="代码仓库地址"></a>代码仓库地址</h1><p><a href="https://github.com/Ceslsius/ID-demo">https://github.com/Ceslsius/ID-demo</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue 读卡器" scheme="http://celsius/tags/vue-%E8%AF%BB%E5%8D%A1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>uniapp，vue3，h5端使用zxing扫描二维码，条形码</title>
    <link href="http://celsius/2023/09/20/uniapp%EF%BC%8Cvue3%EF%BC%8Ch5%E7%AB%AF%E4%BD%BF%E7%94%A8zxing%E6%89%AB%E6%8F%8F%E4%BA%8C%E7%BB%B4%E7%A0%81%EF%BC%8C%E6%9D%A1%E5%BD%A2%E7%A0%81/"/>
    <id>http://celsius/2023/09/20/uniapp%EF%BC%8Cvue3%EF%BC%8Ch5%E7%AB%AF%E4%BD%BF%E7%94%A8zxing%E6%89%AB%E6%8F%8F%E4%BA%8C%E7%BB%B4%E7%A0%81%EF%BC%8C%E6%9D%A1%E5%BD%A2%E7%A0%81/</id>
    <published>2023-09-20T09:22:34.000Z</published>
    <updated>2023-09-20T06:28:31.165Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><hr><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>废话不多说，使用uniapp时scan方法不支持h5端，因此h5端需要自己引入三方库，我这里用的是<a href="https://github.com/zxing-js">zxing</a><br>完整代码在最后</p><hr><h1 id="一、引入zxing"><a href="#一、引入zxing" class="headerlink" title="一、引入zxing"></a>一、引入zxing</h1><p>这里有两种方法，npm和直接使用js文件，按需使用</p><h2 id="npm"><a href="#npm" class="headerlink" title="npm"></a>npm</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add @zxing/library</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; BrowserMultiFormatReader &#125; <span class="keyword">from</span> <span class="string">&#x27;@zxing/library&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="直接使用js文件"><a href="#直接使用js文件" class="headerlink" title="直接使用js文件"></a>直接使用js文件</h2><p>js地址：<a href="https://unpkg.com/@zxing/library@latest/umd/index.min.js">https://unpkg.com/@zxing/library@latest/umd/index.min.js</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; BrowserMultiFormatReader &#125; <span class="keyword">from</span> <span class="string">&#x27;@/utils/zxing.js&#x27;</span></span><br></pre></td></tr></table></figure><h1 id="二、功能实现"><a href="#二、功能实现" class="headerlink" title="二、功能实现"></a>二、功能实现</h1><p>直接使用zxing提供的方法就行，默认参数就够用，如果需要自定义，比如修改视频流清晰度之类的可以看看文档</p><h2 id="1-获取摄像头"><a href="#1-获取摄像头" class="headerlink" title="1. 获取摄像头"></a>1. 获取摄像头</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这里的处理是因为uniapp会对video标签进行二次封装</span></span><br><span class="line">  <span class="keyword">var</span> video = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;video_nav_id&#x27;</span>).getElementsByTagName(<span class="string">&#x27;video&#x27;</span>)[<span class="number">0</span>]</span><br><span class="line">  video.setAttribute(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;video_id&#x27;</span>)</span><br><span class="line">  video.setAttribute(<span class="string">&#x27;class&#x27;</span>, <span class="string">&#x27;video_calss&#x27;</span>)</span><br><span class="line">  codeReader.value = <span class="keyword">new</span> BrowserMultiFormatReader();</span><br><span class="line">  codeReader.value.listVideoInputDevices().then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (res.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      videoInputDevices.value = res</span><br><span class="line">      <span class="comment">// 这里默认选择最后一个摄像头</span></span><br><span class="line">      deviceId.value = res[res.length - <span class="number">1</span>].deviceId</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 没有可用的摄像头</span></span><br><span class="line">      <span class="comment">// todo</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;).catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.error(err)</span><br><span class="line">  &#125;)</span><br></pre></td></tr></table></figure><h2 id="2-开始扫描"><a href="#2-开始扫描" class="headerlink" title="2.开始扫描"></a>2.开始扫描</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    codeReader.value.decodeFromVideoDevice(deviceId.value, <span class="string">&#x27;video_id&#x27;</span>, <span class="function">(<span class="params">res, err</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (res) handleScanningResult(res)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    uni.showToast(&#123; <span class="attr">title</span>: <span class="string">`初始化失败<span class="subst">$&#123;err&#125;</span>`</span>, <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span> &#125;);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><hr><h1 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h1><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="xml"><span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;camera_page&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;camera_content&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;code_close&quot;</span> @<span class="attr">click</span>=<span class="string">&quot;closeClick()&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="comment">&lt;!-- &lt;img src=&quot;../../static/close.png&quot;&gt; --&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;loop_line&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;video_nav&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">video</span> <span class="attr">id</span>=<span class="string">&quot;video_nav_id&quot;</span> <span class="attr">autoplay</span> <span class="attr">:controls</span>=<span class="string">&quot;false&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">video</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span>&gt;</span><span class="javascript"></span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">import</span> &#123; onLoad &#125; <span class="keyword">from</span> <span class="string">&quot;@dcloudio/uni-app&quot;</span>;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">import</span> &#123; ref &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">import</span> &#123; BrowserMultiFormatReader &#125; <span class="keyword">from</span> <span class="string">&#x27;@zxing/library&#x27;</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="comment">// import &#123; BrowserMultiFormatReader &#125; from &#x27;@/utils/zxing.js&#x27;</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> codeReader = ref(<span class="literal">null</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> deviceId = ref(<span class="literal">null</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> videoInputDevices = ref([])</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> initEvent = <span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="comment">// 这里的处理是因为uniapp会对video标签进行二次封装</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="keyword">var</span> video = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;video_nav_id&#x27;</span>).getElementsByTagName(<span class="string">&#x27;video&#x27;</span>)[<span class="number">0</span>]</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  video.setAttribute(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;video_id&#x27;</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  video.setAttribute(<span class="string">&#x27;class&#x27;</span>, <span class="string">&#x27;video_calss&#x27;</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  codeReader.value = <span class="keyword">new</span> BrowserMultiFormatReader();</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  codeReader.value.listVideoInputDevices().then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="keyword">if</span> (res.length &gt; <span class="number">0</span>) &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      videoInputDevices.value = res</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      <span class="comment">// 这里默认选择最后一个摄像头</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">      deviceId.value = res[res.length - <span class="number">1</span>].deviceId</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    &#125; <span class="keyword">else</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      <span class="comment">// 没有可用的摄像头</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">      <span class="comment">// todo</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">    &#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125;).catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="built_in">console</span>.error(err)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125;)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  startScanning()</span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">onLoad(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    initEvent();</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125;, <span class="number">3000</span>);</span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;);</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> startScanning = <span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    codeReader.value.decodeFromVideoDevice(deviceId.value, <span class="string">&#x27;video_id&#x27;</span>, <span class="function">(<span class="params">res, err</span>) =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      <span class="keyword">if</span> (res) handleScanningResult(res)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    &#125;)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125; <span class="keyword">catch</span> (err) &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    uni.showToast(&#123; <span class="attr">title</span>: <span class="string">`初始化失败<span class="subst">$&#123;err&#125;</span>`</span>, <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span> &#125;);</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">let</span> startTime = <span class="literal">null</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> handleScanningResult = <span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="keyword">if</span> (!startTime) startTime = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime()</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="comment">// 间隔时间500ms</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="keyword">const</span> time = <span class="number">500</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="keyword">if</span> (<span class="keyword">new</span> <span class="built_in">Date</span>().getTime() - startTime &gt;= time) &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    startTime = <span class="literal">null</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="comment">// 这里处理扫描结果</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="built_in">console</span>.log(res.text)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  &#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="comment">// 停止扫描</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> stopScanning = <span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  codeReader.value.reset()</span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"><span class="keyword">const</span> closeClick = <span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  <span class="comment">// todo</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">scoped</span> <span class="attr">lang</span>=<span class="string">&quot;scss&quot;</span>&gt;</span><span class="css"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.camera_page</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">100vw</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.camera_content</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">position</span>: relative;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.code_close</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">50</span>rpx;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">50</span>rpx;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">position</span>: absolute;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">left</span>: <span class="number">30</span>rpx;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">top</span>: <span class="number">30</span>rpx;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">z-index</span>: <span class="number">999999</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.code_close</span>&gt;<span class="selector-tag">img</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">display</span>: block;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.loop_line</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">3px</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">80%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">background-color</span>: aliceblue;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">box-shadow</span>: <span class="number">1px</span> -<span class="number">4px</span> <span class="number">25px</span> <span class="number">7px</span> <span class="built_in">rgba</span>(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.5</span>);</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">position</span>: absolute;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">left</span>: <span class="number">50%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">top</span>: <span class="number">20%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">transform</span>: <span class="built_in">translateX</span>(-<span class="number">50%</span>);</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">animation</span>: myfirst <span class="number">3s</span> infinite;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">z-index</span>: <span class="number">999999</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="keyword">@keyframes</span> myfirst &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="selector-tag">from</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">    <span class="attribute">top</span>: <span class="number">20%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  &#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="selector-tag">to</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">    <span class="attribute">top</span>: <span class="number">80%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  &#125;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-class">.video_nav</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml"><span class="selector-id">#video_nav_id</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">height</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span></span></span><br><span class="line"><span class="css"><span class="xml">:<span class="built_in">deep</span>(.uni-video-cover) &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">  display: none;</span></span></span><br><span class="line"><span class="css"><span class="xml">&#125;</span></span></span><br><span class="line"><span class="css"><span class="xml"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p><code>注意事项：</code><br>摄像头权限需要https协议才能调用，也即</p><ol><li>部署到服务器时，域名需要SSL证书，也就是域名以https开头</li><li>本地开发调试时，需要开启https并加载本地证书，这里可以看看我的另一篇笔记<a href="https://blog.csdn.net/charlie_y/article/details/133066515">vue项目，uniapp项目开启https，加载本地证书</a></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue" scheme="http://celsius/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>vue项目，uniapp项目开启https，加载本地证书</title>
    <link href="http://celsius/2023/09/20/vue%E9%A1%B9%E7%9B%AE%EF%BC%8Cuniapp%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%90%AFhttps%EF%BC%8C%E5%8A%A0%E8%BD%BD%E6%9C%AC%E5%9C%B0%E8%AF%81%E4%B9%A6/"/>
    <id>http://celsius/2023/09/20/vue%E9%A1%B9%E7%9B%AE%EF%BC%8Cuniapp%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%90%AFhttps%EF%BC%8C%E5%8A%A0%E8%BD%BD%E6%9C%AC%E5%9C%B0%E8%AF%81%E4%B9%A6/</id>
    <published>2023-09-20T07:35:49.000Z</published>
    <updated>2023-09-20T06:42:37.400Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><hr><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>废话不多说，前端开发调用摄像头，获取地址位置时需要https的问题，在本地运行时可以参考这篇文章操作<br>我这里用的mkcert<br>如果你使用的vite，那么可以直接使用<a href="https://github.com/vitejs/vite-plugin-basic-ssl">vitejs/plugin-basic-ssl</a></p><hr><h1 id="一、通过mkcert创建本地证书并加载"><a href="#一、通过mkcert创建本地证书并加载" class="headerlink" title="一、通过mkcert创建本地证书并加载"></a>一、通过mkcert创建本地证书并加载</h1><h2 id="1-安装mkcert"><a href="#1-安装mkcert" class="headerlink" title="1. 安装mkcert"></a>1. 安装mkcert</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g mkcert</span><br></pre></td></tr></table></figure><h2 id="2-生成证书"><a href="#2-生成证书" class="headerlink" title="2. 生成证书"></a>2. 生成证书</h2><figure class="highlight bash"><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">mkcert create-ca</span><br><span class="line">mkcert create-cert</span><br></pre></td></tr></table></figure><p>这时，你应该能看到生成了四个文件</p><blockquote><p>ca.crt<br>ca.key<br>cert.crt<br>cert.key</p></blockquote><h2 id="3-加载证书"><a href="#3-加载证书" class="headerlink" title="3. 加载证书"></a>3. 加载证书</h2><ol><li>mac<br>双击ca.cert，在弹出的界面选择Test CA，选择“信任”，然后选择“始终信任”，最后输入密码确认即可</li><li>windows<br>双击ca.crt，在弹出的界面中选择安装证书，选择“当前用户”或“本地计算机”均可，关键的是下一步，选择“将所有的证书都放入下列存储”，并且选择为“受信任的根证书颁发机构”，然后一路确定即可<h1 id="二、在vue或uniapp中使用刚刚的证书"><a href="#二、在vue或uniapp中使用刚刚的证书" class="headerlink" title="二、在vue或uniapp中使用刚刚的证书"></a>二、在vue或uniapp中使用刚刚的证书</h1><h2 id="1-vue2"><a href="#1-vue2" class="headerlink" title="1.vue2"></a>1.vue2</h2>将刚刚生成的cert.crt和cert.key两个拷贝到项目的src/ssl文件夹中</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vue.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="attr">open</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">https</span>: &#123;</span><br><span class="line">      <span class="attr">cert</span>: fs.readFileSync(path.join(__dirname, <span class="string">&#x27;src/ssl/cert.crt&#x27;</span>)),</span><br><span class="line">      <span class="attr">key</span>: fs.readFileSync(path.join(__dirname, <span class="string">&#x27;src/ssl/cert.key&#x27;</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><h2 id="2-vue3"><a href="#2-vue3" class="headerlink" title="2.vue3"></a>2.vue3</h2><p>将刚刚生成的cert.crt和cert.key两个拷贝到项目的src/ssl文件夹中</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vite.config.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span></span><br><span class="line"><span class="keyword">import</span> vue <span class="keyword">from</span> <span class="string">&#x27;@vitejs/plugin-vue&#x27;</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> defineConfig(&#123;</span><br><span class="line">  <span class="attr">plugins</span>: [vue()],</span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">alias</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;@&#x27;</span>: path.join(__dirname, <span class="string">&#x27;src&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">server</span>: &#123;</span><br><span class="line">    <span class="attr">open</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">https</span>: &#123;</span><br><span class="line">      <span class="comment">// 主要是下面两行的配置文件，不要忘记引入 fs 和 path 两个对象</span></span><br><span class="line">      <span class="attr">cert</span>: fs.readFileSync(path.join(__dirname, <span class="string">&#x27;src/ssl/cert.crt&#x27;</span>)),</span><br><span class="line">      <span class="attr">key</span>: fs.readFileSync(path.join(__dirname, <span class="string">&#x27;src/ssl/cert.key&#x27;</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><h2 id="3-uniapp"><a href="#3-uniapp" class="headerlink" title="3.uniapp"></a>3.uniapp</h2><p>uniapp则有点不一样，需要手动复制cert.crt和cert.key两个文件的内容到manifest.json中</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// manifest.json</span></span><br><span class="line"><span class="string">&quot;h5&quot;</span> : &#123;</span><br><span class="line">        <span class="string">&quot;devServer&quot;</span> : &#123;</span><br><span class="line">            <span class="string">&quot;https&quot;</span> : &#123;</span><br><span class="line">                <span class="string">&quot;cert&quot;</span> : <span class="string">&quot;-----BEGIN CERTIFICATE-----\nMIIDTDCCAjSgAwIBAgIFNzg2OTEwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH\nVGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\nBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjMwOTE5MDcw\nNDA0WhcNMjQwOTE4MDcwNDA0WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm4YK3YNrZY1ppyPlW6nMLpX66Mk0G\npgYQazD/bgZbHgkjPo1tSg53WlSKh/Au5t4gQZIquIjxtbNEYzqZl8PM0gG4G6z/\nLTq+2g+gOaPirr7Xn1aoOi9ScYXPB+1/InJ7GSF3mHakXcl6tLcKgudJjIxdQkKG\nwRTcDfd3uvrll69HAuBCT1aFVQ4e/fT4rkHKGkHEc5jeCx96pYXdlsO+OWea4EZM\nHA5aSGbWqA5fy6CG6YzbqJmaRfxy9uy7iRVQlSDjya7nfzyqrQj4LOvjjBqw+a6y\nBRyFkVYwx+1z8i7lm0Gf6mVJHAzNbn4HdSS0++z3/Tl86aAYSXXA11YhAgMBAAGj\nWzBZMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG\nAQUFBwMBBggrBgEFBQcDAjAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ\nKoZIhvcNAQELBQADggEBAA6KRRKZQY1o6e63vGJYcnzKfDCM2L/SzkytJMRPY083\n+HIQQ35crpe8NmWLP+O9WFPzN5dWmL52HZ59c1Qtv9VeNWRfx93PEI1G8NX+t4//\ngeD4dFRhiFnCDxz004Q0KvjVHjY11Z+irvfVyPDa+/6PVSLgyBNsQN/1p2A2gtQB\nZk01NRk1H4o/1ys9CTHwhEqioaiVy5FJ4bspWlKIs0uGBxOAtsXX2MEEneWffIKK\nOx25xFIKdW9zJsyowebELh97qRsI1Kw8NfeCw/3pxJlQozU0hZyujf3qMBsXXu8m\n7Zvz3s0C+VZww9TOOeYZOm/WobHj2Mog8sooNPfiS/s=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIDXjCCAkagAwIBAgIFNTA3NTkwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH\nVGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\nBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjMwOTE5MDcw\nMzQ4WhcNMjQwOTE4MDcwMzQ4WjBeMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQG\nEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\nbzEQMA4GA1UEChMHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBALfx9Wt6MeovAGs0gLysmqQTW+NGjgpM8P2NvcAYFUok0bSvEfCcd95lnePh\nuQbtUbWs3dkGK1C5KexTtVOeAbM3DYcef1uks9+dzShB6zOUlFWGF/tzNvf8imMC\nRxWuMSr7CAR4GfhmXscFMSeGCtW1rVZhB5+DxZm19lY2bwpiVIRnrnX7zZRdlLBH\nywTCxH8vjJmc3HkqlSO8d2xrA7eQTZpkzMcPm4boOFyoqPEpM1SQXB2oh7byN4zU\nsH2f7Y7baqxGqkCe8aretVdXYdsr8C3PnUS8BUqlUosg9CY9Ax7AMur8K78zF0ws\nG4nFBvm3mE7WM2hGwJBZcsiHNuECAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAO\nBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAELHcueFTazDfzofj0V3\nOgyx5CQAQcCNu6cDBOkH13Ytvzi5rnttQhFGLACPPLT/vuanTXTuP7ko2dPDIVLn\nE0V8YCZfFt29cGRooHRkQzvuJC/CrphSw/zwfRz5Oi9qKzeJZ3+4hze/sbD5XnXo\nGnoOJ/aKn7WTw6YS88AFbgUyqb6QQyoHIMNQcPE1H8Lo6ICL45ngjCxoss9qtak5\ntGX7wGgLW2fuj/6kvmfe0bEQp20HDUiiM5Eog8yONr8QCj5omO1RkL1YAI2fYQhQ\nOqVIelbijX9ehKBe3Gh6ZW1p6Mw6zweBRU0XYgDyJtij2hf4gQTpauQ5/AC1o2bU\nvjQ=\n-----END CERTIFICATE-----\n&quot;</span>,</span><br><span class="line">                <span class="string">&quot;key&quot;</span> : <span class="string">&quot;-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA5uGCt2Da2WNaacj5VupzC6V+ujJNBqYGEGsw/24GWx4JIz6N\nbUoOd1pUiofwLubeIEGSKriI8bWzRGM6mZfDzNIBuBus/y06vtoPoDmj4q6+159W\nqDovUnGFzwftfyJyexkhd5h2pF3JerS3CoLnSYyMXUJChsEU3A33d7r65ZevRwLg\nQk9WhVUOHv30+K5ByhpBxHOY3gsfeqWF3ZbDvjlnmuBGTBwOWkhm1qgOX8ughumM\n26iZmkX8cvbsu4kVUJUg48mu5388qq0I+Czr44wasPmusgUchZFWMMftc/Iu5ZtB\nn+plSRwMzW5+B3UktPvs9/05fOmgGEl1wNdWIQIDAQABAoIBAQDh23OTToVoqd+1\nomwAkaNvnjrljPV67oj6INIy3gALvMs1liil5A9YxJ31UtwsFZ7wMRT7A8C7q4Mu\nAGBABo39XOdfontOuYFBWHJ2zEw9IbMBnhcYxKqsXO+IQvobJ6iUMVZi91wAmNrT\nVdvFxZ7afUqGpJ4N0u5EAsm2b8ynMSqflyo00bNTWzIjOPqsJSLGuOXUmh9SLvEx\nFA0Jv/FW1D6GVbnODGTUVyGPPv4EXC1u7G6S68raW8oCU/VznC1gN6IQGBYSGzJJ\nQTh6uEfTbUOlw0tYfL4H/WthVT8bW2M98ol8t+BYptXFMBmgteaZKhf0TE/PdAyd\nNwhbPhtBAoGBAP4WZaaqVNYX7bY79kgpSGmo5IwY2zJkwjaEMXkSado+WXLfRSRM\nV99btQSgKDRZbl6SKBDD5w6X8/MmMnjNJ3HtCFY/9tx/6rPhutP2SUt/30sbh3Xa\nMBcoMWJMQaZlWmQmNUrtuyT1Qowsgv88jcDfYGYPaFePNE6ZS5mYlOapAoGBAOie\nZYfHM3RN3ULFEMOBnAjakdLclicJ33u9dIhdN91goG7IaCL+iBy8M/ctUTACpFfa\ni7lXVhccBnpX5W663DjAkpWa4ecwE04Mi/5Pe8nWsPKI+NgYg/zH3D5gYROKOXst\nIwi0Ku0wiskI8KShuJ8x9/RG+xSyix+Nuj0uDTa5AoGBANIvKtVsz5Lu5mg6P+e/\nprQozy6JHUIZkYLRZa92qPOtYgchZKu7L3GIBj0WU0vCswcuNlBwDcHHtzhrX5MI\nelnGr3/Rl98dPcShtQ/ECyuWUttpoWu/Gsr9J6Td2ezQqK5x4OHjuEzBSdR0ucRz\njft/jNnAYxsTTjy6mIwtaPdJAoGAI3SJGyHkmNSJZfTtci8eHkdaNJ6EkFyz4INm\n8KXsruRcb6ZCIRtUmbSh4wnYNw4gCHf6FfzW1VzjK+Cbyr3WdRv8+6qIxhkQyDOn\nXtK0lnYPsFfqPd22uF+qFSPRiA3oZvrhPSFg7ZgZfzzKmP1BS5ptJaFwAZ6ZD+0l\nNs//GZkCgYEA0NdSF/XomDS7NLh1B2vWW/tcCoOmv+FQ3/mtnnx4JNs9KEDKazSb\nA6jUyuzmQfwHJrHTLbSoVcvPwSmdU+4mvd4ktomg4CTp/DjgHAjPveMsWiv/++v/\n8VtcXkc/M/c0AMrdZBv9J2jXngbz8rvOrK/QZpsaoohlOAsHjFl4lWg=\n-----END RSA PRIVATE KEY-----\n&quot;</span></span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="string">&quot;proxy&quot;</span> : &#123;&#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&quot;async&quot;</span> : &#123;</span><br><span class="line">            <span class="string">&quot;delay&quot;</span> : <span class="string">&quot;10000&quot;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&quot;router&quot;</span> : &#123;</span><br><span class="line">            <span class="string">&quot;base&quot;</span> : <span class="string">&quot;./&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>到此，证书生成安装结束了，项目跑起来就ok了</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue" scheme="http://celsius/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>JS反混淆相关知识</title>
    <link href="http://celsius/2023/06/06/JS%E5%8F%8D%E6%B7%B7%E6%B7%86%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/"/>
    <id>http://celsius/2023/06/06/JS%E5%8F%8D%E6%B7%B7%E6%B7%86%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/</id>
    <published>2023-06-06T07:29:22.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近学习了一下JS反混淆的知识，记录一下</p><hr><h1 id="一、JSA加密"><a href="#一、JSA加密" class="headerlink" title="一、JSA加密"></a>一、JSA加密</h1><ul><li><p>比较基础的加密，只对变量进行了修改，通常与代码压缩一起使用</p></li><li><p> JSA加密示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">o00</span>(<span class="params">$</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&quot;\x1b[32m%s\x1b[0m&quot;</span>, $)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">o01</span>(<span class="params">$</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&quot;\x1b[41m%s\x1b[0m&quot;</span>, $)</span><br><span class="line">&#125;</span><br><span class="line">o00(<span class="string">&quot;logR&quot;</span>);</span><br><span class="line">o01(<span class="string">&quot;logG&quot;</span>)</span><br></pre></td></tr></table></figure></li><li><p> 这种加密直接丢给JS NICE</p><h1 id="二、JS-NICE"><a href="#二、JS-NICE" class="headerlink" title="二、JS NICE"></a>二、JS NICE</h1></li><li><p>JS Nice 是一款让经过混淆处理的 JavaScript 代码可读性更好的工具。它使用一种新型的用于 JavaScript 代码美化的去混淆和去压缩引擎。JSNice 采用先进的机器学习和程序分析技术，从可用的开源项目学习命名和类型规律。</p></li><li><p>经过测试，其只能做到通用型的一些格式化，重命名等操作，不过试试也可能会有惊喜</p></li><li><p>地址：<a href="http://jsnice.org/">http://jsnice.org/</a></p><h1 id="三、base62编码"><a href="#三、base62编码" class="headerlink" title="三、base62编码"></a>三、base62编码</h1></li><li><p>最明显的特征是生成的代码以 eval(function(p,a,c,k,e,r)) 开头</p></li><li><p> 这类混淆的关键思想在于将需要执行的代码进行一次编码，在执行的时候还原出浏览器可执行的合法的脚本，然后执行之。看上去和可执行文件的加壳有那么点类似。Javascript 提供了将字符串当作代码执行（evaluate）的能力，可以通过 Function 构造器、eval、setTimeout、setInterval 将字符串传递给 js 引擎进行解析执行。</p></li><li><p> 无论代码如何进行变形，其最终都要调用一次 eval 等函数。解密的方法不需要对其算法做任何分析，只需要简单地找到这个最终的调用，改为 console.log 或者其他方式，将程序解码后的结果按照字符串输出即可</p><h1 id="四、OB混淆"><a href="#四、OB混淆" class="headerlink" title="四、OB混淆"></a>四、OB混淆</h1></li><li><p>现在 JavaScript 混淆主流的实现是 javascript-obfuscator 这个库，也就是OB混淆，其能实现代码压缩，变量名混淆，字符串混淆，代码自我保护，控制流平坦化，僵尸代码注入，对象键名替换，调试保护，特殊编码等。其中每一个拿出来都很棘手，组合在一起就更难搞了。</p></li><li><p>OB混淆网站：<a href="https://obfuscator.io/">https://obfuscator.io/</a></p></li><li><p> ob混淆特征：</p></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 开头一个大数组</span></span><br><span class="line"><span class="keyword">var</span> _0xa441 = [<span class="string">&#x27;\x49\x63\x4b\x72\x77\x70\x2f\x44\x6c\x67\x3d\x3d&#x27;</span>, ···]</span><br><span class="line"><span class="comment">// 自执行函数对数组进行位移</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params">_0x56a234, _0xa44115</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> _0x532345 = <span class="function"><span class="keyword">function</span> (<span class="params">_0x549d7c</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (--_0x549d7c) &#123;</span><br><span class="line">            _0x56a234[<span class="string">&#x27;push&#x27;</span>](_0x56a234[<span class="string">&#x27;shift&#x27;</span>]());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    _0x532345(++_0xa44115);</span><br><span class="line">&#125;(_0xa441, <span class="number">0x1d0</span>));</span><br><span class="line"><span class="comment">// 解密函数</span></span><br><span class="line"><span class="keyword">var</span> _0x5323 = <span class="function"><span class="keyword">function</span> (<span class="params">_0x56a234, _0xa44115</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 里面有段自执行函数生成atob函数</span></span><br><span class="line">    ······</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 下方多处调用解密函数，如</span></span><br><span class="line"><span class="keyword">var</span> _0x239123 = _0x5323(<span class="string">&#x27;\x30\x78\x32\x30&#x27;</span>, <span class="string">&#x27;\x70\x59\x48\x73&#x27;</span>);</span><br></pre></td></tr></table></figure><ul><li><p>对于OB混淆还原，没有找到在线网站，因此我结合node环境的还原工具写了一个。当然，因为OB混淆的版本以及配置项很多，并不能做到完美还原，需要搭配其他方式一起使用。</p></li><li><p> 在线地址：<a href="http://celsius.icu/web/deOb/index.html">http://celsius.icu/web/deOb/index.html</a></p><h1 id="五、控制流平坦化"><a href="#五、控制流平坦化" class="headerlink" title="五、控制流平坦化"></a>五、控制流平坦化</h1></li><li><p>这里需要把控制流平坦化单独拿出来说说，因为大部分混淆工具都会用到，加密效果显著。控制流平坦化，简单来讲就是将代码块之间的关系打断，由一个分发器来控制代码块的跳转。</p></li><li><p>如果看到代码里出现for循环while循环switch执行等方式来控制代码的执行顺序，八成就是控制流平坦化：</p></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 混淆前</span></span><br><span class="line"><span class="built_in">window</span> = &#123;&#125;;</span><br><span class="line"><span class="built_in">window</span>.atob = <span class="function"><span class="keyword">function</span>(<span class="params">r</span>) </span>&#123;</span><br><span class="line">    e = <span class="string">&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&quot;</span>;</span><br><span class="line">    <span class="keyword">var</span> o = <span class="built_in">String</span>(r).replace(<span class="regexp">/=+$/</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(o.length % <span class="number">4</span> == <span class="number">1</span>) <span class="keyword">throw</span> <span class="keyword">new</span> t(<span class="string">&quot;&#x27;atob&#x27; failed: The string to be decoded is not correctly encoded.&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">var</span> n, a, i = <span class="number">0</span>, c = <span class="number">0</span>, d = <span class="string">&quot;&quot;</span>; a = o.charAt(c++); ~a &amp;&amp; (n = i % <span class="number">4</span> ? <span class="number">64</span> * n + a : a, i++ % <span class="number">4</span>) ? d += <span class="built_in">String</span>.fromCharCode(<span class="number">255</span> &amp; n &gt;&gt; (-<span class="number">2</span> * i &amp; <span class="number">6</span>)) : <span class="number">0</span>) a = e.indexOf(a);</span><br><span class="line">    <span class="keyword">return</span> d</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 混淆后</span></span><br><span class="line"><span class="built_in">window</span> = &#123;&#125;;</span><br><span class="line"><span class="built_in">window</span>.atob = <span class="function"><span class="keyword">function</span> (<span class="params">_0xc0750g</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> _0xfbb16d = <span class="string">&quot;4|1|2|3|0&quot;</span>[<span class="string">&quot;split&quot;</span>](<span class="string">&quot;|&quot;</span>),</span><br><span class="line">      _0xec2868 = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (!![]) &#123;</span><br><span class="line">    <span class="keyword">switch</span> (_0xfbb16d[_0xec2868++]) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;0&quot;</span>:</span><br><span class="line">        <span class="keyword">return</span> _0xec4345;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;1&quot;</span>:</span><br><span class="line">        <span class="keyword">var</span> _0x1adc16 = <span class="built_in">String</span>(_0xc0750g).replace(<span class="regexp">/=+$/</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;2&quot;</span>:</span><br><span class="line">        <span class="keyword">if</span> (_0x1adc16.length % <span class="number">4</span> == <span class="number">1</span>) <span class="keyword">throw</span> <span class="keyword">new</span> t(<span class="string">&quot;&#x27;atob&#x27; failed: The string to be decoded is not correctly encoded.&quot;</span>);</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;3&quot;</span>:</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">var</span> _0xb342a5, _0x7ecb47, _0xc54a87 = <span class="number">0</span>, _0xa14d1g = <span class="number">0</span>, _0xec4345 = <span class="string">&quot;&quot;</span>; _0x7ecb47 = _0x1adc16.charAt(_0xa14d1g++); ~_0x7ecb47 &amp;&amp; (_0xb342a5 = _0xc54a87 % <span class="number">4</span> ? <span class="number">64</span> * _0xb342a5 + _0x7ecb47 : _0x7ecb47, _0xc54a87++ % <span class="number">4</span>) ? _0xec4345 += <span class="built_in">String</span>.fromCharCode(<span class="number">255</span> &amp; _0xb342a5 &gt;&gt; (-<span class="number">2</span> * _0xc54a87 &amp; <span class="number">6</span>)) : <span class="number">0</span>) _0x7ecb47 = e.indexOf(_0x7ecb47);</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;4&quot;</span>:</span><br><span class="line">        e = <span class="string">&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&quot;</span>;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li><p>要对这种混淆后的代码进行分析，AST语法树是最好的选择，当然也可以灵活使用浏览器调试工具的debugger功能，直接调试运行结果</p></li><li><p>示例博客：<a href="https://security.tencent.com/index.php/blog/msg/112">https://security.tencent.com/index.php/blog/msg/112</a></p><h1 id="六、AST语法树"><a href="#六、AST语法树" class="headerlink" title="六、AST语法树"></a>六、AST语法树</h1></li><li><p>基于AST语法树对代码进行分析是最直接的，当然也是最考验代码能力的</p></li><li><p>以esprima为例，其提供了js代码与语法树之间的转换能力，再结合estools的几个辅助库即可对js进行静态代码分析：</p><blockquote><p>escope Javascript 作用域分析工具</p><pre><code> esutil 辅助函数库，检查语法树节点是否满足某些条件 estraverse 语法树遍历辅助库，接口有一点类似 SAX 方式解析 XML esrecurse 另一个语法树遍历工具，使用递归 esquery 使用 css 选择器的语法从语法树中提取符合条件的节点 escodegen 与 esprima 功能互逆，将语法树还原为代码</code></pre></blockquote></li><li><p>JS代码解析为AST在线地址：<a href="http://esprima.org/demo/parse.html">http://esprima.org/demo/parse.html</a></p></li><li><p> AST语法树的东西比较多，这里说不完，感兴趣可以深入学习</p><h1 id="七、隐写术"><a href="#七、隐写术" class="headerlink" title="七、隐写术"></a>七、隐写术</h1></li><li><p> 一种比较少见的代码加密方法，严格来说的话也不算加密，只是将 js 代码隐藏到了特定的介质当中。如通过最低有效位（LSB）算法嵌入到图片的 RGB 通道、隐藏在图片 EXIF 元数据、隐藏在 HTML 空白字符等。</p></li><li><p> 隐写的方式同样需要解码程序和动态执行，所以破解的方式和base62编码相同，在浏览器上下文中劫持替换关键函数调用的行为，改为文本输出即可得到载体中隐藏的代码。</p></li><li><p> 其特点是执行代码在哪里都要花时间来找，不过在WebAssembly出现之后就被其全面碾压</p><h1 id="八、黑盒加密：Emscripten与WebAssembly"><a href="#八、黑盒加密：Emscripten与WebAssembly" class="headerlink" title="八、黑盒加密：Emscripten与WebAssembly"></a>八、黑盒加密：Emscripten与WebAssembly</h1></li><li><p> Emscripten：某些 JavaScript 的核心功能可以使用 C/C++ 语言实现，然后通过 Emscripten 编译成 asm.js，再由 JavaScript 调用执行，如果要对其进行分析，就需要将JS反编译成C/C++，难度无疑是直线上升</p></li><li><p>WebAssembly：WebAssembly 是经过编译器编译之后的字节码，可以从 C/C++ 编译而来，得到的字节码具有和 JavaScript 相同的功能，但它体积更小，而且在语法上完全脱离 JavaScript，同时具有沙盒化的执行环境。也就是说对其分析需要分析二进制码，这几乎是无法做到的事情，代码示例：</p></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">WebAssembly.compile(<span class="keyword">new</span> <span class="built_in">Uint8Array</span>(<span class="string">`</span></span><br><span class="line"><span class="string">  00 61 73 6d  01 00 00 00  01 0c 02 60  02 7f 7f 01</span></span><br><span class="line"><span class="string">  7f 60 01 7f  01 7f 03 03  02 00 01 07  10 02 03 61</span></span><br><span class="line"><span class="string">  64 64 00 00  06 73 71 75  61 72 65 00  01 0a 13 02</span></span><br><span class="line"><span class="string">  08 00 20 00  20 01 6a 0f  0b 08 00 20  00 20 00 6c</span></span><br><span class="line"><span class="string">  0f 0b`</span>.trim().split(<span class="regexp">/[\s\r\n]+/g</span>).map(<span class="function"><span class="params">str</span> =&gt;</span> <span class="built_in">parseInt</span>(str, <span class="number">16</span>))</span><br><span class="line">)).then(<span class="function"><span class="params">module</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> instance = <span class="keyword">new</span> WebAssembly.Instance(<span class="built_in">module</span>)</span><br><span class="line">  <span class="keyword">const</span> &#123; add, square &#125; = instance.exports</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;2 + 4 =&#x27;</span>, add(<span class="number">2</span>, <span class="number">4</span>))</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;3^2 =&#x27;</span>, square(<span class="number">3</span>))</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;(2 + 5)^2 =&#x27;</span>, square(add(<span class="number">2</span> + <span class="number">5</span>)))</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h1 id="九、反混淆实操学习"><a href="#九、反混淆实操学习" class="headerlink" title="九、反混淆实操学习"></a>九、反混淆实操学习</h1><ul><li> 实践是检验真理的唯一标准，这里我推荐一个反混淆实操学习平台，在实际操作中学习应用。</li><li> 猿人学在线地址：<a href="https://match.yuanrenxue.cn/list">https://match.yuanrenxue.cn/list</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="反混淆" scheme="http://celsius/tags/%E5%8F%8D%E6%B7%B7%E6%B7%86/"/>
    
  </entry>
  
  <entry>
    <title>基于node.js实现分销类微信聊天机器人</title>
    <link href="http://celsius/2023/05/03/%E5%9F%BA%E4%BA%8Enode.js%E5%AE%9E%E7%8E%B0%E5%88%86%E9%94%80%E7%B1%BB%E5%BE%AE%E4%BF%A1%E8%81%8A%E5%A4%A9%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    <id>http://celsius/2023/05/03/%E5%9F%BA%E4%BA%8Enode.js%E5%AE%9E%E7%8E%B0%E5%88%86%E9%94%80%E7%B1%BB%E5%BE%AE%E4%BF%A1%E8%81%8A%E5%A4%A9%E6%9C%BA%E5%99%A8%E4%BA%BA/</id>
    <published>2023-05-03T12:33:51.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="基于node-js实现分销类微信聊天机器人"><a href="#基于node-js实现分销类微信聊天机器人" class="headerlink" title="基于node.js实现分销类微信聊天机器人"></a>基于node.js实现分销类微信聊天机器人</h1><hr><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近遇到了一个微信聊天机器人的需求，主要功能是自动同意好友申请，定时向微信群发送商品优惠信息，收到微信私聊信息后搜索优惠卷信息发送回去。那我作为一个前端开发，第一时间就是去找node.js的相关库了呀，经历了各种坑，总算是给他拿下</p><hr><h1 id="一、涉及技术栈"><a href="#一、涉及技术栈" class="headerlink" title="一、涉及技术栈"></a>一、涉及技术栈</h1><ol><li>首先需要一个node.js的运行环境，可以是服务器也可以在你本地上挂机跑</li><li>优惠卷分销是接入的大淘客，绑定淘宝，京东等账号后可以对应的获取相关商品的优惠卷，并且成功购买后有返利</li><li>微信机器人库的话去网上一搜，很多都会让你使用微信网页端抓包搞出来的一个api库，但是这个微信网页端有使用条件，具体是啥我就不说了，反正我是不符合。最后是找到了基于pad版微信app搞出来的一个库——基于<a href="https://github.com/wechaty/puppet-xp">wechaty-puppet</a>的ding-dong-talk，它的原理是模拟在pad中运行微信app，包括请求头，数据加密解密等都是反编译来的，所以微信一改就会用不了，且用且珍惜吧。</li></ol><h1 id="二、实现代码"><a href="#二、实现代码" class="headerlink" title="二、实现代码"></a>二、实现代码</h1><h2 id="1-登录模块"><a href="#1-登录模块" class="headerlink" title="1.登录模块"></a>1.登录模块</h2><p>引入相关库，再注册对应事件<br>使用qrcode生成二维码，手机微信扫码登录</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> type * <span class="keyword">as</span> PUPPET   <span class="keyword">from</span> <span class="string">&#x27;wechaty-puppet&#x27;</span></span><br><span class="line"><span class="keyword">import</span> qrcodeTerminal <span class="keyword">from</span> <span class="string">&#x27;qrcode-terminal&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  PuppetXp,</span><br><span class="line">&#125;               <span class="keyword">from</span> <span class="string">&#x27;../src/mod.js&#x27;</span></span><br><span class="line"><span class="keyword">const</span> puppet = <span class="keyword">new</span> PuppetXp()</span><br><span class="line">puppet</span><br><span class="line">  .on(<span class="string">&#x27;logout&#x27;</span>, onLogout)</span><br><span class="line">  .on(<span class="string">&#x27;login&#x27;</span>,  onLogin)</span><br><span class="line">  .on(<span class="string">&#x27;scan&#x27;</span>,   onScan)</span><br><span class="line">  .on(<span class="string">&#x27;error&#x27;</span>,  onError)</span><br><span class="line">  .on(<span class="string">&#x27;message&#x27;</span>, onMessage)</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">onScan</span> (<span class="params">payload: PUPPET.payloads.EventScan</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (payload.qrcode) &#123;</span><br><span class="line">    <span class="keyword">const</span> qrcodeImageUrl = [</span><br><span class="line">      <span class="string">&#x27;https://wechaty.js.org/qrcode/&#x27;</span>,</span><br><span class="line">      <span class="built_in">encodeURIComponent</span>(payload.qrcode),</span><br><span class="line">    ].join(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    <span class="built_in">console</span>.info(<span class="string">&#x27;StarterBot&#x27;</span>, <span class="string">&#x27;onScan: %s(%s) - %s&#x27;</span>, payload.status, qrcodeImageUrl)</span><br><span class="line">    qrcodeTerminal.generate(payload.qrcode, &#123; <span class="attr">small</span>: <span class="literal">true</span> &#125;)  <span class="comment">// 将二维码打印到控制台</span></span><br><span class="line">    <span class="built_in">console</span>.info(<span class="string">`[<span class="subst">$&#123;payload.status&#125;</span>] <span class="subst">$&#123;payload.qrcode&#125;</span>\nScan QR Code above to log in: `</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.info(<span class="string">`[<span class="subst">$&#123;payload.status&#125;</span>]`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-聊天消息接收事件"><a href="#2-聊天消息接收事件" class="headerlink" title="2.聊天消息接收事件"></a>2.聊天消息接收事件</h2><p>接收到消息会触发onMessage事件并携带消息内容发送人等参数<br>我这里的逻辑是如果是我本人发送消息，则用来控制每日优惠自动发送事件，如果是其他人发送消息，则使用大淘客接口获取商品优惠卷链接并发送回去</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">onMessage</span> (<span class="params">&#123;</span></span></span><br><span class="line"><span class="params"><span class="function">  messageId,</span></span></span><br><span class="line"><span class="params"><span class="function">&#125;: PUPPET.payloads.EventMessage</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;</span><br><span class="line">    fromId, <span class="comment">// 发送人id</span></span><br><span class="line">    roomId, <span class="comment">// 发送群群id</span></span><br><span class="line">    text, <span class="comment">// 消息内容</span></span><br><span class="line">  &#125; = <span class="keyword">await</span> puppet.messagePayload(messageId)</span><br><span class="line">  <span class="keyword">if</span> (<span class="regexp">/ding/i</span>.test(text || <span class="string">&#x27;&#x27;</span>)) &#123;</span><br><span class="line">    <span class="keyword">await</span> puppet.messageSendText(roomId! || fromId!, <span class="string">&#x27;dong&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">console</span>.log(puppet.messagePayload(messageId))</span><br><span class="line">  <span class="keyword">if</span>(fromId == <span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>)&#123;</span><br><span class="line">    <span class="comment">// 我自己的微信号发来的消息</span></span><br><span class="line">    <span class="keyword">if</span>(text == <span class="string">&#x27;1&#x27;</span>)&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;开始发送&quot;</span>)</span><br><span class="line">      <span class="keyword">await</span> puppet.messageSendText(<span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>, <span class="string">&#x27;开始发送&#x27;</span>)</span><br><span class="line">      handleStart()</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (text == <span class="string">&#x27;2&#x27;</span>) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;停止发送&quot;</span>)</span><br><span class="line">      <span class="keyword">await</span> puppet.messageSendText(<span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>, <span class="string">&#x27;停止发送&#x27;</span>)</span><br><span class="line">      hanldeEnd()</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (text == <span class="string">&#x27;0&#x27;</span>)&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;索引清零&quot;</span>)</span><br><span class="line">      <span class="keyword">await</span> puppet.messageSendText(<span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>, <span class="string">&#x27;索引清零&#x27;</span>)</span><br><span class="line">      idx = <span class="number">0</span></span><br><span class="line">      pageId = <span class="number">0</span></span><br><span class="line">      itemList = []</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (text.includes(<span class="string">&#x27;time&#x27;</span>))&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;修改发送间隔&quot;</span>,text.replace(<span class="regexp">/[^0-9]/ig</span>, <span class="string">&quot;&quot;</span>) * <span class="number">60000</span>)</span><br><span class="line">      <span class="keyword">await</span> puppet.messageSendText(<span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>, <span class="string">&#x27;修改发送间隔成功&#x27;</span>)</span><br><span class="line">      time = text.replace(<span class="regexp">/[^0-9]/ig</span>, <span class="string">&quot;&quot;</span>) * <span class="number">60000</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (text.includes(<span class="string">&#x27;idx&#x27;</span>))&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;修改索引&quot;</span>,text.replace(<span class="regexp">/[^0-9]/ig</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">      <span class="keyword">await</span> puppet.messageSendText(<span class="string">&#x27;wxid_gkc181kbu5bd22&#x27;</span>, <span class="string">&#x27;修改索引成功&#x27;</span>)</span><br><span class="line">      idx = <span class="built_in">Number</span>(text.replace(<span class="regexp">/[^0-9]/ig</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">      pageId = <span class="number">0</span></span><br><span class="line">      itemList = []</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="comment">// 其他人发送的商品链接，使用大淘客接口获取优惠信息并发回去</span></span><br><span class="line">    <span class="keyword">const</span> res = apiGetGoodsInfo(text)</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&quot;商品信息&quot;</span>,res)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这部分功能实现没啥问题，唯一的问题是不知道什么时候微信官方修改就会导致无法使用。<br>除此之外，也可以使用auto.js来对真机进行操作来实现，更稳定。</p><h1 id="git地址"><a href="#git地址" class="headerlink" title="git地址"></a>git地址</h1><p><a href="https://github.com/Ceslsius/wechat-robot-node.js/tree/master">项目代码</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="node" scheme="http://celsius/tags/node/"/>
    
  </entry>
  
  <entry>
    <title>使用浏览器插件修改request请求header头</title>
    <link href="http://celsius/2023/04/29/%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6%E4%BF%AE%E6%94%B9request%E8%AF%B7%E6%B1%82header%E5%A4%B4/"/>
    <id>http://celsius/2023/04/29/%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6%E4%BF%AE%E6%94%B9request%E8%AF%B7%E6%B1%82header%E5%A4%B4/</id>
    <published>2023-04-29T07:10:11.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近遇到了一个疑似header头引发的bug，需要修改header头来验证，于是写了个插件来实现</p><hr><h1 id="一、插件结构"><a href="#一、插件结构" class="headerlink" title="一、插件结构"></a>一、插件结构</h1><p>浏览器插件相关的东西我这里就不说了，简单说下结构<br><img src="https://img-blog.csdnimg.cn/b2e52099c9f846a59786840c24aa0f13.png" alt="在这里插入图片描述"></p><blockquote><p>background.js: 实现修改header头的逻辑，插件加载即运行<br>manifest.json: 插件的一些配置项</p></blockquote><h1 id="二、代码"><a href="#二、代码" class="headerlink" title="二、代码"></a>二、代码</h1><h2 id="1-插件配置项"><a href="#1-插件配置项" class="headerlink" title="1.插件配置项"></a>1.插件配置项</h2><p>manifest.json代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;name&quot;</span>: <span class="string">&quot;editHeader&quot;</span>,</span><br><span class="line">  <span class="string">&quot;description&quot;</span>: <span class="string">&quot;edit header&quot;</span>,</span><br><span class="line">  <span class="string">&quot;version&quot;</span>: <span class="string">&quot;1.0.0&quot;</span>,</span><br><span class="line">  <span class="string">&quot;manifest_version&quot;</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="string">&quot;permissions&quot;</span>: [<span class="string">&quot;webRequest&quot;</span>, <span class="string">&quot;webRequestBlocking&quot;</span>, <span class="string">&quot;http://*/*&quot;</span>, <span class="string">&quot;https://*/*&quot;</span>],</span><br><span class="line">  <span class="string">&quot;background&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;scripts&quot;</span>: [<span class="string">&quot;./background/background.js&quot;</span>],</span><br><span class="line">    <span class="string">&quot;persistent&quot;</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每个参数对应的含义我就不多说了，manifest.json的配置项很多，可以自己百度<br>其中比较关键的是permissions中的webRequest，这个配置了插件才能有拦截请求的权限</p><h2 id="2-功能代码"><a href="#2-功能代码" class="headerlink" title="2.功能代码"></a>2.功能代码</h2><p>background.js代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> targetUrl = <span class="string">&#x27;https://gitee.com/&#x27;</span> <span class="comment">//要修改的目标url</span></span><br><span class="line"><span class="keyword">let</span> headerObj = &#123;</span><br><span class="line">  <span class="attr">test</span>: <span class="string">&#x27;123456&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;User-Agent&#x27;</span>: <span class="string">&#x27;456&#x27;</span></span><br><span class="line">&#125; <span class="comment">//要修改的header头参数，有则修改，无则添加</span></span><br><span class="line">chrome.runtime.onInstalled.addListener(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;插件已被安装&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">replaceHeader</span>(<span class="params">url, requestHeaders</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (url === targetUrl) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> headerObj) &#123;</span><br><span class="line">      <span class="keyword">const</span> idx = requestHeaders.findIndex(<span class="function"><span class="params">x</span> =&gt;</span> x.name === key)</span><br><span class="line">      <span class="keyword">if</span> (idx !== -<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="comment">// 有，则修改</span></span><br><span class="line">        requestHeaders[idx].value = headerObj[key]</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 无，则添加</span></span><br><span class="line">        requestHeaders.push(&#123; <span class="attr">name</span>: key, <span class="attr">value</span>: headerObj[key] &#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="keyword">return</span> requestHeaders</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateListeners</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!listener)</span><br><span class="line">    listener = <span class="function"><span class="keyword">function</span> (<span class="params">details</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">var</span> header_map = &#123; <span class="attr">requestHeaders</span>: details.requestHeaders &#125;</span><br><span class="line">      <span class="keyword">if</span> (details &amp;&amp; details.url &amp;&amp; details.requestHeaders &amp;&amp; details.requestHeaders.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        header_map = &#123; <span class="attr">requestHeaders</span>: replaceHeader(details.url, details.requestHeaders) &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> header_map</span><br><span class="line">    &#125;</span><br><span class="line">  chrome.webRequest.onBeforeSendHeaders.addListener(listener, &#123; <span class="attr">urls</span>: [<span class="string">&#x27;&lt;all_urls&gt;&#x27;</span>] &#125;, [<span class="string">&#x27;requestHeaders&#x27;</span>, <span class="string">&#x27;blocking&#x27;</span>, <span class="string">&#x27;extraHeaders&#x27;</span>])</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> listener = <span class="literal">null</span></span><br><span class="line">updateListeners()</span><br></pre></td></tr></table></figure><p>大致逻辑的话就是通过chrome提供的onBeforeSendHeaders钩子来修改header头</p><hr><h1 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h1><p>github: <a href="https://github.com/Ceslsius/editHeader">https://github.com/Ceslsius/editHeader</a></p><hr><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>目前每修改一次参数就需要重新加载一次插件，后续的话准备做成可配置</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="javascript" scheme="http://celsius/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>h5基于canvas的凹凸拼图碎片生成</title>
    <link href="http://celsius/2023/03/12/h5%E5%9F%BA%E4%BA%8Ecanvas%E7%9A%84%E5%87%B9%E5%87%B8%E6%8B%BC%E5%9B%BE%E7%A2%8E%E7%89%87%E7%94%9F%E6%88%90/"/>
    <id>http://celsius/2023/03/12/h5%E5%9F%BA%E4%BA%8Ecanvas%E7%9A%84%E5%87%B9%E5%87%B8%E6%8B%BC%E5%9B%BE%E7%A2%8E%E7%89%87%E7%94%9F%E6%88%90/</id>
    <published>2023-03-12T03:10:31.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近接到了一个web的拼图游戏需求，要求在前端生成凹凸碎片样式。在github逛了逛，发现基本都是切割成方块，不满足需求；要么是用pixi.js或者cocos这种有学习成本，于是先研究一下怎么简单一点的实现。</p><hr><h1 id="一、如何将图片切割成凹凸形状"><a href="#一、如何将图片切割成凹凸形状" class="headerlink" title="一、如何将图片切割成凹凸形状"></a>一、如何将图片切割成凹凸形状</h1><p>这里用到了canvas的一个属性</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">imgContext.globalCompositeOperation = <span class="string">&#x27;destination-in&#x27;</span></span><br></pre></td></tr></table></figure><p>详细的大家可以去查看文档，简单来说的话就是将canvas两次绘制的图形进行一个保留重合部分的操作<br>，所以我们先绘制出完整的图片，再绘制凹凸碎片，就能得到想要的有图案的碎片，然后修改绘制凹凸碎片的位置重复多次操作就能得到想要的全部碎片。</p><h1 id="二、完整代码"><a href="#二、完整代码" class="headerlink" title="二、完整代码"></a>二、完整代码</h1><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><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></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh-CN&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>凹凸拼图<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">canvas</span> <span class="attr">id</span>=<span class="string">&quot;canvas&quot;</span> <span class="attr">class</span>=<span class="string">&quot;puzzle_game&quot;</span> <span class="attr">id</span>=<span class="string">&quot;puzzleGame&quot;</span> <span class="attr">width</span>=<span class="string">&quot;300&quot;</span> <span class="attr">height</span>=<span class="string">&quot;450&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="javascript"></span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="keyword">var</span> shade = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;canvas&#x27;</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="keyword">var</span> imgContext = shade.getContext(<span class="string">&#x27;2d&#x27;</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    <span class="keyword">var</span> img = <span class="keyword">new</span> Image()</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    img.src = <span class="string">&#x27;./a.jpg&#x27;</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">    img.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      imgContext.drawImage(img, <span class="number">0</span>, <span class="number">0</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      imgContext.globalCompositeOperation = <span class="string">&#x27;destination-in&#x27;</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">      <span class="keyword">var</span> img2 = <span class="keyword">new</span> Image()</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      img2.src = <span class="string">&#x27;./clipart.png&#x27;</span></span></span></span><br><span class="line"><span class="javascript"><span class="xml">      img2.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">        imgContext.drawImage(img2, <span class="number">100</span>, <span class="number">100</span>, <span class="number">50</span>, <span class="number">50</span>)</span></span></span><br><span class="line"><span class="javascript"><span class="xml">      &#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">    &#125;</span></span></span><br><span class="line"><span class="javascript"><span class="xml">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="css"></span></span></span><br><span class="line"><span class="css"><span class="xml">    <span class="selector-class">.puzzle_game</span> &#123;</span></span></span><br><span class="line"><span class="css"><span class="xml">      <span class="attribute">width</span>: <span class="number">300px</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">      <span class="attribute">height</span>: <span class="number">450px</span>;</span></span></span><br><span class="line"><span class="css"><span class="xml">      <span class="attribute">background</span>: blue;</span></span></span><br><span class="line"><span class="css"><span class="xml">    &#125;</span></span></span><br><span class="line"><span class="css"><span class="xml">  </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>绘制图片：<br><img src="https://img-blog.csdnimg.cn/503a99cd55e4492f9f26dbafdf65066a.png" alt="绘制图片"><br>绘制凹凸拼图碎片：<br><img src="https://img-blog.csdnimg.cn/d50db8db7d0e4aa6a62aea2c5267b9aa.png" alt="绘制凹凸拼图碎片"><br>可以看到我们得到了其中一块碎片，之后我们修改第二次绘制的坐标，就能得到全部的碎片。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这种办法生成，优点是代码简单，微信小程序也能用；缺点是生成时间较长，计算拼图碎片放置位置有点麻烦。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="canvas" scheme="http://celsius/tags/canvas/"/>
    
  </entry>
  
  <entry>
    <title>微信小程序开发基础</title>
    <link href="http://celsius/2022/09/02/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80/"/>
    <id>http://celsius/2022/09/02/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80/</id>
    <published>2022-09-02T02:44:38.000Z</published>
    <updated>2023-09-21T06:18:05.483Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>微信⼩程序，简称⼩程序，英⽂名 Mini Program，是⼀种不需要下载安装即可使⽤的应⽤，它实现了应⽤“触⼿可及”的梦想，⽤⼾扫⼀扫或搜⼀下即可打开应⽤</p><p>微信小程序的优势：</p><blockquote><p>1.微信有海量⽤⼾，⽽且粘性很⾼，在微信⾥开发产品更容易触达⽤⼾；<br>2.推⼴app或公众号的成本太⾼。<br>3.开发适配成本低。<br>4.容易⼩规模试错，然后快速迭代。<br>5.跨平台。</p></blockquote><h1 id="一、开发前的准备工作"><a href="#一、开发前的准备工作" class="headerlink" title="一、开发前的准备工作"></a>一、开发前的准备工作</h1><h2 id="1-1-注册小程序"><a href="#1-1-注册小程序" class="headerlink" title="1-1 注册小程序"></a>1-1 注册小程序</h2><p>官方注册文档: <a href="https://developers.weixin.qq.com/miniprogram/introduction/">https://developers.weixin.qq.com/miniprogram/introduction/</a><br>微信小程序注册地址：<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a><br>官方给的注册步骤很详细了，跟着教程来就行，需要注意几点</p><ul><li>记得保存appid，appsecret，如果丢失需要重新生成，可能会影响线上应用</li><li>服务器域名、业务域名等配置项位置：开发-开发管理-开发设置</li><li>服务器域名必须是https，小程序发布类别(如社交，音视频等)需要证书，这些东西可以在开发时同步进行<h2 id="1-2-开发者工具"><a href="#1-2-开发者工具" class="headerlink" title="1-2 开发者工具"></a>1-2 开发者工具</h2>开发者工具下载地址：<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html">https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html</a><br>安装完成后，扫码登录，新建/导入小程序项目时输入appid即可<br><img src="https://img-blog.csdnimg.cn/ac40389a1fd84cf996f6554a8798f0fc.png#pic_center" alt="微信小程序标准的目录结构"></li><li>上面是微信小程序标准的目录结构，无论使用什么框架（比如uni-app）最后打包成微信小程序时也是生成类似的结构</li><li>json文件配置文档（微信小程序）：<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/config.html">https://developers.weixin.qq.com/miniprogram/dev/framework/config.html</a></li><li>json文件配置文档（uni-app）：<a href="https://uniapp.dcloud.net.cn/collocation/pages.html">https://uniapp.dcloud.net.cn/collocation/pages.html</a></li><li>在开发时，可以打开 设置-本地设置-不校验合法域名 来关闭对服务器域名、业务域名等的校验，包括在体验版时也可以通过打开调试模式来关闭校验</li><li>如果后面要使用uni-app项目，需要将开发者工具的服务端口选项打开（设置-安全设置）<h2 id="1-3-微信小程序注意事项"><a href="#1-3-微信小程序注意事项" class="headerlink" title="1-3 微信小程序注意事项"></a>1-3 微信小程序注意事项</h2></li><li>微信官方时不时就会修改API（获取用户信息修改了三四次了），或者API收费（获取手机号），要有心理准备</li><li>微信小程序每个包大小不能超过2M，可以用分包来解决，全部包加起来不能超过16-20M</li><li>因为大小的限制，静态资源最好是放在自己的服务器上</li><li>扫描二维码打开小程序，h5跳转小程序，微信消息通知，这些功能很有用、接入也很简单，但是一定要仔细阅读开发文档</li><li>获取用户位置，音视频通话，红包等功能是需要开通的，一定要先确定能够开通再选择使用<h1 id="二、开发"><a href="#二、开发" class="headerlink" title="二、开发"></a>二、开发</h1><h2 id="2-1-选择开发方式"><a href="#2-1-选择开发方式" class="headerlink" title="2-1 选择开发方式"></a>2-1 选择开发方式</h2>目前主流混合开发方式<table><thead><tr><th></th><th>原生开发</th><th>uni-app</th><th>nvue</th><th>weex</th><th>RN</th><th>flutter</th></tr></thead><tbody><tr><td>所属公司</td><td>腾讯</td><td>Dcloud</td><td>Dcloud</td><td>阿里</td><td>Facebook</td><td>Google</td></tr><tr><td>开发方式</td><td>js</td><td>vue.js</td><td>vue.js</td><td>vue.js</td><td>react.js</td><td>Dart</td></tr><tr><td>渲染方式</td><td>魔改webview</td><td>webview</td><td>weex原生渲染</td><td>weex原生渲染</td><td>webview</td><td>原生渲染</td></tr><tr><td>支持系统</td><td>微信小程序</td><td>web、Android、iOS、多方小程序</td><td>Android、iOS</td><td>web、Android、iOS</td><td>web、Android、iOS、多方小程序</td><td>Android、iOS、Fuchsia</td></tr><tr><td>性能</td><td>一般</td><td>一般</td><td>较快</td><td>较快</td><td>一般</td><td>快</td></tr></tbody></table></li></ul><p>总结：</p><ul><li>如果只考虑小程序和h5端，个人建议是uni-app</li><li>如果应用只需要考虑 Android 和 iOS，个人认为性能体验方面：flutter &gt; nvue/weex &gt; RN &gt; uni-app ；</li><li>如果要跨多个端，除了 Android 和 iOS，还包括微信、支付宝小程序等等，毋庸置疑，首选uni-app；</li><li>如果要考虑桌面端，flutter 本身就支持桌面端，RN 则需要使用react-native-windows这个项目，uni-app 则需要集成 electron</li></ul><h2 id="2-2-开发项目"><a href="#2-2-开发项目" class="headerlink" title="2-2 开发项目"></a>2-2 开发项目</h2><p>这里我以uni-app为例，至于为什么选择uni-app，我的理由是</p><blockquote><p>1.微信原生开发的状态管理及双向绑定并不好用<br>2.uni-app的vue语法让开发更敏捷<br>3.虽然有兼容性问题，uni-app打包多端的能力仍然好使<br>4.多年的迭代使得uni-app目前有较多的插件<br>5.跨平台</p></blockquote><p>没有最好的，只有最合适的，实际选用时也要视情况而定</p><h3 id="2-2-1-项目搭建"><a href="#2-2-1-项目搭建" class="headerlink" title="2-2-1 项目搭建"></a>2-2-1 项目搭建</h3><p>可以从uni-app官网下载模板，也可以从github拉<br>当然也可以导入已有的项目</p><h3 id="2-2-2-开发工具及插件安装"><a href="#2-2-2-开发工具及插件安装" class="headerlink" title="2-2-2 开发工具及插件安装"></a>2-2-2 开发工具及插件安装</h3><p>HBuilderX下载地址：<a href="https://www.dcloud.io/hbuilderx.html">https://www.dcloud.io/hbuilderx.html</a><br>安装后，打开我们刚刚的代码，点击 运行-运行到小程序模拟器 即可拉起小程序预览项目<br>比如安装UI库uView<br>uni-app插件市场地址：<a href="https://ext.dcloud.net.cn/">https://ext.dcloud.net.cn/</a><br>搜索到需要的插件点击导入HBuilderX，然后选择对应项目导入即可</p><h3 id="2-2-3-开写"><a href="#2-2-3-开写" class="headerlink" title="2-2-3 开写"></a>2-2-3 开写</h3><p>uni-app开发规范：</p><blockquote><p>1.页面文件遵循 Vue 单文件组件 (SFC) 规范，即每个页面是一个.vue文件<br>2.组件标签靠近小程序规范，详见uni-app 组件规范<br>3.接口能力（JS API）靠近小程序规范，但需将前缀 wx、my 等替换为 uni，详见uni-app接口规范<br>4.数据绑定及事件处理同 Vue.js 规范，同时补充了应用生命周期及页面的生命周期<br>5.如需兼容app-nvue平台，建议使用flex布局进行开发</p></blockquote><p>简单来说的话就是跟写vue差不多，这里我就不多说vue该怎么写了<br>值得一说的：<br>1.rpx<br>不用去管实际运行时的窗口尺寸，750rpx = 100vw，也就是让UI出图时使用750px为基准，编辑样式时一比一换成rpx即可</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#app&#123;</span><br><span class="line">  <span class="attr">width</span>: 750rpx;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2.条件编译</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// #ifdef H5  </span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&quot;这段代码只会在h5端运行&quot;</span>)</span><br><span class="line"><span class="comment">// #endif </span></span><br><span class="line"><span class="comment">// #ifndef H5  </span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&quot;这段代码只会在非h5端运行&quot;</span>)</span><br><span class="line"><span class="comment">// #endif </span></span><br></pre></td></tr></table></figure><p>3.id含义</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">appid: 小程序id，小程序的唯一识别</span><br><span class="line">openid: 微信用户id，对于当前小程序的微信用户的唯一识别</span><br><span class="line">unionId: 微信用户id，对于当前小程序所属微信商户账号的微信用户的唯一识别</span><br></pre></td></tr></table></figure><h1 id="三、测试及发布"><a href="#三、测试及发布" class="headerlink" title="三、测试及发布"></a>三、测试及发布</h1><h2 id="3-1-打包上传"><a href="#3-1-打包上传" class="headerlink" title="3-1 打包上传"></a>3-1 打包上传</h2><p>如果是原生开发，直接上传代码<br>注意一下开发者工具的几个配置项，按需选择就行<br><img src="https://img-blog.csdnimg.cn/6b5ab3e2b1f342c2ac155da31c1984b3.png#pic_center" alt="配置项"><br>如果是uni-app项目，则在HBuilderX里选择 发行-小程序-微信，然后再点击上传</p><h2 id="3-2-设置体验版"><a href="#3-2-设置体验版" class="headerlink" title="3-2 设置体验版"></a>3-2 设置体验版</h2><p>上传包后，在微信公众平台 管理-版本管理中选择刚刚的开发版本为体验版<br><img src="https://img-blog.csdnimg.cn/9bf096695eb74ceea45e7263f78a46d6.png#pic_center" alt="版本管理"><br>然后，再将测试人员添加到体验人员中（管理-人员管理-体验成员）<br>此时，测试人员扫描体验版二维码，就能使用小程序了</p><h2 id="3-3-提交审核及发布"><a href="#3-3-提交审核及发布" class="headerlink" title="3-3 提交审核及发布"></a>3-3 提交审核及发布</h2><p>测试完成后，点击提交审核，在1-3个工作日内会出结果，如果审核不通过，按照提示信息修改；如果通过，点击发布小程序即可<br>中（管理-人员管理-体验成员）<br>此时，测试人员扫描体验版二维码，就能使用小程序了</p><h2 id="3-3-提交审核及发布-1"><a href="#3-3-提交审核及发布-1" class="headerlink" title="3-3 提交审核及发布"></a>3-3 提交审核及发布</h2><p>测试完成后，点击提交审核，在1-3个工作日内会出结果，如果审核不通过，按照提示信息修改；如果通过，点击发布小程序即可<br>注：第一次审核时，部分类别需要工信部二次审核，时间较长</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="微信小程序" scheme="http://celsius/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>WebAssembly+ffmpeg纯前端实现视频处理</title>
    <link href="http://celsius/2022/08/04/WebAssembly+ffmpeg%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/"/>
    <id>http://celsius/2022/08/04/WebAssembly+ffmpeg%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/</id>
    <published>2022-08-04T03:22:42.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>最近学习了一下 <a href="https://www.wasm.com.cn/">wasm</a></p><p>WebAssembly 是由主流浏览器厂商组成的 <a href="https://www.w3.org/community/webassembly/">W3C 社区团体</a> 制定的一个新的规范。</p><p>这是一个运行于沙箱中的指令集，其二进制的格式能最大效率发挥硬件能力，运行速度是 js 的几倍十几倍</p><p>而因为其是二进制格式，你可以将 C/C++/java 等等等语言编译成 wasm 格式，并在各种语言的虚拟机中运行，自然也包括 JavaScript 虚拟机</p><p>这也就意味着我们能在前端实现一些以前只能在后端实现的功能</p><p>这里我们以 ffmpeg 举例，这是一个 C++ 开发的很有名的开源库，能实现大部分视频的编码格式处理，包括压缩，转码等功能</p><p>我们将 ffmpeg 转成 wasm 格式后就能在前端实现视频处理</p><p>注：如果只是想直接使用，请直接滑到最下面</p><h2 id="Emscripten-amp-asm-js"><a href="#Emscripten-amp-asm-js" class="headerlink" title="Emscripten &amp; asm.js"></a>Emscripten &amp; asm.js</h2><p><a href="https://link.zhihu.com/?target=https://github.com/kripken/emscripten">Emscripten: An LLVM-to-JavaScript Compiler</a></p><p><a href="https://link.zhihu.com/?target=http://asmjs.org/">Asm.js an extraordinarily optimizable, low-level subset of JavaScript</a></p><p>这是 wasm 过程中 2 个比较重要的工具，官网链接在上：</p><ul><li>Emscripten: wasm 的编译器，根据上面的介绍，这是一个 LLVM 到 js 的编译器。咱对于编译原理这些底层的东西了解也不太多，简单理解的话 LLVM 就是一个编译的中间状态，使用 llvm-gcc 或者 clang 这些编译工具可以将 c/c++编译到 LLVM bitcode 这样一个中间代码的状态, 同样类似像 rust 的语言也可以编译到 LLVM。有了 LLVM bitcode，就可以使用 Emscripten 这个工具来将其编译成更底层的汇编 js 代码。</li><li>那么更底层的汇编 js 代码是啥，早期在 wasm 还没有实现之前，汇编 js 的实现就是 Asm.js（字如其名。。）。asm.js 的核心功能就是模拟一个汇编语言的运行环境，通过一个大的 UintArray 数组来模拟机器内存，然后通过 js 实现各种汇编指令来对这个虚拟的内存(UintArray 对象)进行操作。</li></ul><p>通过上面这 2 个步骤，我们先是把 c/c++通过编译器编译到 LLVM，再利用 emscripten 将 LLVM 编译成 asm.js，当我们在运行这段 asm.js 代码的时候，就会省去大量的解释器开销，相当于直接使用汇编代码一样来直接操作内存空间运行。</p><p>但是 asm.js 实现的汇编环境毕竟还是基于 js 的，在性能上还是不能完全满意，这个时候，WebAssebmly 就应运而生了，由浏览器来实现汇编环境，规定好 webassembly 的汇编格式，使得性能进一步提示</p><h2 id="编译过程"><a href="#编译过程" class="headerlink" title="编译过程"></a>编译过程</h2><p>扯了这么多，还是具体说说编译 FFmpeg 的过程吧。编译 ffmpeg 参考了 github 上的一个现有项目，<a href="https://link.zhihu.com/?target=https://github.com/bgrins/videoconverter.js/">videoconverter.js</a>，下面从 0 开始按照顺序说明一下</p><ul><li>编译 Emcc 工具链</li></ul><p>首先我们需要编译 Emcripten 到你的机器上，我这里用的是一台 Ubuntu14.04.</p><p>在执行下面命令之前，请先确保你的机器上有如下依赖</p><p>cmake git python2.7.X</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/juj/emsdk &amp;&amp; <span class="built_in">cd</span> emsdk</span><br><span class="line">./emsdk install sdk-incoming-64bit binaryen-master-64bit</span><br><span class="line">./emsdk activate sdk-incoming-64bit binaryen-master-64bit</span><br><span class="line">. ./emsdk_env.sh</span><br></pre></td></tr></table></figure><p>编译完成之后，按照输出给出的提示，以后只要直接 source ./emsdk_env.sh 就会自动配置环境变量，将各种编译工具的设置成 emsdk 下的 path</p><p>至此，在命令行输入 emcc –help，看到正常的输出说明编译和配置成功。现在你就拥有了一个编译 wasm 的编译器。</p><h2 id="开始编译-FFmpeg-到-LLVM"><a href="#开始编译-FFmpeg-到-LLVM" class="headerlink" title="开始编译 FFmpeg 到 LLVM"></a>开始编译 FFmpeg 到 LLVM</h2><p>跟着上篇的进度，现在我们已经拥有了 Emscripten 的编译环境，现在让我们正式开始编译</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 首先，从github等地方获取ffmpeg的源代码</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/FFmpeg/FFmpeg</span><br><span class="line"><span class="built_in">cd</span> FFmpeg</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开始configure</span></span><br><span class="line"><span class="comment"># 这里的参数参考自videoconverter.js，其中注意需要额外带上下面第一行的CPPFLAGS</span></span><br><span class="line"><span class="comment"># 否则不能在最新的emcripten下编译通过</span></span><br><span class="line"><span class="comment"># 这里通过--cc=&quot;emcc&quot;来指定编译器为emcc，emcc会调用clang来将target设置成LLVM</span></span><br><span class="line">CPPFLAGS=<span class="string">&quot;-D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600&quot;</span> \</span><br><span class="line">emconfigure ./configure --cc=<span class="string">&quot;emcc&quot;</span> \</span><br><span class="line">--prefix=$(<span class="built_in">pwd</span>)/../dist --enable-cross-compile --target-os=none --arch=x86_64 \</span><br><span class="line">--cpu=generic --disable-ffplay --disable-ffprobe --disable-ffserver \</span><br><span class="line">--disable-asm --disable-doc --disable-devices --disable-pthreads \</span><br><span class="line">--disable-w32threads --disable-network --disable-hwaccels \</span><br><span class="line">--disable-parsers --disable-bsfs --disable-debug --disable-protocols \</span><br><span class="line">--disable-indevs --disable-outdevs --enable-protocol=file</span><br><span class="line"></span><br><span class="line"><span class="comment"># ?等待运行完成，执行make开始编译</span></span><br><span class="line">make</span><br></pre></td></tr></table></figure><p>如果一切正常，你将看到下面的结果</p><p><img src="https://pic4.zhimg.com/80/v2-e3d06dfdd1c6cd239be4938c1fef5c6b_720w.png" alt="img"></p><p>你会得到一个叫 ffmpeg 的输出文件（如果最后阶段报 strip error 直接无视掉就好，和我们的栗子没有关系)</p><p><img src="https://pic2.zhimg.com/80/v2-efcef42e832feaee1a35a4e62ad63d25_720w.png" alt="img"></p><p>这个 ffmpeg 文件就是我们第一步要得到的 LLVM bitcode，下一步我们就可以将这个 LLVM bitcode 编译到 js 或者 wasm 里面啦</p><h2 id="行百里者半九十"><a href="#行百里者半九十" class="headerlink" title="行百里者半九十"></a>行百里者半九十</h2><p>在执行最后一步之前，有个疑问，ffmpeg 正常编译出来的二进制应该是个命令行文件，接受命令行参数执行操作。但当我们把它编译到 js 里时，该怎么向它传递参数呢？传递了参数之后又要怎么样才能拿到它的执行结果呢？</p><p>理想的状况下，我们希望最终编译出来的 wasm/asm.js 文件能向外 export 一个函数来作为入口，我们通过这个函数入口传入参数，通过返回值或者回调函数等方法获取结果。</p><p>其实这就是 wasm/asm.js 文件和浏览器 js 的信息通信问题，Emscripten 为我们提供了这样一些解决方案，以下摘自 Emscripten 官网文档 <a href="https://link.zhihu.com/?target=http://kripken.github.io/emscripten-site/docs/api_reference">API Reference - Emscripten 1.37.10 documentation</a></p><ul><li>pre.js 和 post.js</li></ul><blockquote><p>–pre-js <file> Specify a file whose contents are added before the generated code. This is done <em>before</em> optimization, so it will be minified properly if the <em>Closure Compiler</em> is run. –post-js <file> Specify a file whose contents are added after the generated code. This is done <em>before</em> optimization, so it will be minified properly if the <em>Closure Compiler</em> is run.</p></blockquote><p>这 2 个其实是 emcc 编译时的 2 个可选项，通过传入用户自己编写的 pre.js 和 post.js，来将最终生成的代码包裹在 pre.js 和 post.js 之下，就像这样 [pre.js]–[生成的 wasm/asm.js]–[post.js]</p><p>也就是可以让我在 wasm 被执行之前，运行 pre.js 里的代码，在 wasm 代码被执行后(但并不是执行结束后)运行 post.js 里的代码。</p><p>那这样有什么意义呢？其实最方便的就是我们可以在 pre.js 里 export 一个自定义的函数给外部，或者比如说在 pre.js 里做一些必要的初始化，具体做法看下面的介绍</p><ul><li>Module 对象</li></ul><blockquote><p>Module is a global JavaScript object with attributes that Emscripten-generated code calls at various points in its execution.</p></blockquote><p>简单来说，这里的 Module 是一个特殊全局对象，当我们执行最终的 wasm/asm.js 文件时，在不同的阶段，程序会自动调用 Module 下不同的方法。比如执行结束时(<em>Module.postrun</em>)，输出到 stdout(<em>Module.print</em>)时等等。我们就可以自己实现 Module 下那些阶段的函数来挂钩子，比如将返回值返回给回调函数，将 log 打印到浏览器 console 等等。</p><p>结合上面的 pre.js，是不是想到什么了？</p><p>没错，我们可以在 pre.js 里编写定义 Module 函数，来控制 wasm/asm.js 程序的输入和输出。</p><blockquote><p>Module.arguments The commandline arguments. The value of arguments contains the values returned if compiled code checks argc and argv.</p></blockquote><p>通过给 Module 的 arguments 传入值，emscripten 就会把这个值带到 wasm/asm.js 程序的命令行输入里，也就是 ffmpeg 的命令行输入。</p><p>这样，我们就大概解决了命令行参数输入的问题，但是还有几个点需要考虑。</p><p>ffmpeg 输入文件名之后，但是文件要怎么传给他? ffmpeg 输出的文件我们又要怎么得到?</p><ul><li>File System API</li></ul><p><a href="https://link.zhihu.com/?target=http://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html%23filesystem-api">File System API - Emscripten 1.37.10 documentation</a></p><p>没错，解决方案依旧是 EmscriptenAPI</p><p>emscripten 为我们提供了一套文件系统 api，通过这套 API，就可以用 ArrayBuffer，Web File 等方法来创建文件，并将这个文件写入 emscripten 为我们提供的一个虚拟文件系统。</p><p>之后 wasm/asm.js 程序请求文件都会去使用这个虚拟的文件系统，包括读入和写出。</p><p>具体 api 的使用方法这里就不说太多了有兴趣的可以看看文档。</p><p>至此，我们通过这 3 个方法，解决了我们的需求：</p><ul><li>导出一个入口函数到上层 js</li><li>传入函数参数来控制 ffmpeg 命令行参数</li><li>通过虚拟文件系统传入输入文件以及获取输出文件</li></ul><h2 id="Final-Battle-编译-LLVM-到-WebAssmbly"><a href="#Final-Battle-编译-LLVM-到-WebAssmbly" class="headerlink" title="Final Battle 编译 LLVM 到 WebAssmbly"></a>Final Battle 编译 LLVM 到 WebAssmbly</h2><p>这里使用的命令依旧是 emcc，但是注意此时 emcc 的输入为 LLVM bitcode，它将会调用 emscriptem 来将其编译到 js (和第一步 emcc 的行为不同，因为输入格式不同，target 也会不同)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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"># 这里的ffmpeg是上一步编译输出的LLVM bitcode</span></span><br><span class="line">cp ffmpeg ffmpeg.bc</span><br><span class="line"></span><br><span class="line"><span class="comment"># 最终的输出是 -o 指定的，这些 -s 参数的意义可以从emcc的文档中找到</span></span><br><span class="line"><span class="comment"># 这里打开了ALLOW_MEMORY_GROWTH是因为在移动端测试下会遇到内存(wasm/asm.js的虚拟内存)</span></span><br><span class="line"><span class="comment"># 不够的情况，默认内存大小是TOTAL_MEMORY指定的</span></span><br><span class="line"><span class="comment"># 设置WASM=1就会编译到WebAssembly,默认编译到asm.js</span></span><br><span class="line">emcc -s ASSERTIONS=1 -s VERBOSE=1 -s TOTAL_MEMORY=33554432 \</span><br><span class="line">-s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -O2 -v ffmpeg.bc \</span><br><span class="line">-o ../ffmpeg.js --pre-js ../ffmpeg_pre.js --post-js ../ffmpeg_post.js</span><br></pre></td></tr></table></figure><p>如果一切顺利，你就会得到最终的输出，包括这 2 货</p><p><img src="https://pic3.zhimg.com/80/v2-1bd707f42c3fdb56cd11632bb2c1e426_720w.png" alt="img"></p><p>因为我们之前在 pre.js 里写了入口函数并将其 export，所以我们只需要直接 require(“ffmpeg.js”)，或者在 worker 里直接 importScript(“ffmpeg.js”)就可以拿到入口函数。</p><p>注意 ffmpeg.js 和 ffmpeg.wasm 需要放在一个路径下，执行 ffmpeg.js 的时候会去请求 wasm 文件</p><p>下面贴上使用案例，worker 下的</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></pre></td><td class="code"><pre><span class="line">self.importScripts(<span class="string">&quot;ffmpeg.js&quot;</span>);</span><br><span class="line"></span><br><span class="line">onmessage = <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&quot;ffmpeg_run&quot;</span>, ffmpeg_run);</span><br><span class="line">  <span class="keyword">var</span> files = e.data;</span><br><span class="line">  <span class="built_in">console</span>.log(files);</span><br><span class="line">  ffmpeg_run(</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">arguments</span>: [<span class="string">&quot;-i&quot;</span>, <span class="string">&quot;/input/&quot;</span> + files[<span class="number">0</span>].name, <span class="string">&quot;-b:v&quot;</span>, <span class="string">&quot;64k&quot;</span>, <span class="string">&quot;-bufsize&quot;</span>, <span class="string">&quot;64k&quot;</span>, <span class="string">&quot;-vf&quot;</span>, <span class="string">&quot;showinfo&quot;</span>, <span class="string">&quot;-strict&quot;</span>, <span class="string">&quot;-2&quot;</span>, <span class="string">&quot;out.mp4&quot;</span>],</span><br><span class="line">      <span class="attr">files</span>: files,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function"><span class="keyword">function</span> (<span class="params">results</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&quot;result&quot;</span>, results);</span><br><span class="line">      self.postMessage(results[<span class="number">0</span>].data, [results[<span class="number">0</span>].data]);</span><br><span class="line">    &#125;</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>workers 的输入就是一个 WebFile，通过 emscriptenAPI 的 workerfs 可以直接将 html5 的 File 对象传入虚拟文件系统</p><p>输出就是输出文件的 ArrayBuffer，最后再贴个效果图</p><p><img src="https://pic2.zhimg.com/80/v2-4f6151fa45f9f1ab11aa16cdcec8e30d_720w.png" alt="img"></p><h2 id="使用-ffmpeg-wasm-在前端实现视频压缩"><a href="#使用-ffmpeg-wasm-在前端实现视频压缩" class="headerlink" title="使用 ffmpeg.wasm 在前端实现视频压缩"></a>使用 ffmpeg.wasm 在前端实现视频压缩</h2><p>当然如果我们不想自己编译，可以直接用已经编译好的wasm文件</p><p>比如这个：github 仓库地址: <a href="https://github.com/ffmpegwasm/ffmpeg.wasm">https://github.com/ffmpegwasm/ffmpeg.wasm</a></p><p>实例代码:</p><figure class="highlight html"><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="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Title<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/@ffmpeg/ffmpeg@0.9.5/dist/ffmpeg.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="css"></span></span><br><span class="line"><span class="css">      <span class="selector-tag">html</span>,</span></span><br><span class="line"><span class="css">      <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css">      &#125;</span></span><br><span class="line"><span class="css">      <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="css">        <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css">        <span class="attribute">flex-direction</span>: column;</span></span><br><span class="line"><span class="css">        <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css">      &#125;</span></span><br><span class="line"><span class="css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h3</span>&gt;</span>视频前端压缩方案<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">video</span> <span class="attr">id</span>=<span class="string">&quot;output-video&quot;</span> <span class="attr">controls</span>&gt;</span><span class="tag">&lt;/<span class="name">video</span>&gt;</span><span class="tag">&lt;<span class="name">br</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;file&quot;</span> <span class="attr">id</span>=<span class="string">&quot;uploader&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;message&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="javascript"></span></span><br><span class="line"><span class="javascript">      <span class="keyword">const</span> &#123; createFFmpeg, fetchFile &#125; = FFmpeg;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">const</span> message = <span class="built_in">document</span>.getElementById(<span class="string">&quot;message&quot;</span>);</span></span><br><span class="line"><span class="javascript">      <span class="keyword">const</span> ffmpeg = createFFmpeg(&#123;</span></span><br><span class="line"><span class="javascript">        <span class="attr">log</span>: <span class="literal">true</span>,</span></span><br><span class="line"><span class="javascript">        <span class="attr">progress</span>: <span class="function">(<span class="params">&#123; ratio &#125;</span>) =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">          message.innerHTML = <span class="string">`Complete: <span class="subst">$&#123;(ratio * <span class="number">100.0</span>).toFixed(<span class="number">2</span>)&#125;</span>%`</span>;</span></span><br><span class="line"><span class="javascript">        &#125;,</span></span><br><span class="line"><span class="javascript">      &#125;);</span></span><br><span class="line"><span class="javascript">      <span class="keyword">const</span> transcode = <span class="keyword">async</span> (&#123; <span class="attr">target</span>: &#123; files &#125; &#125;) =&gt; &#123;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> &#123; name &#125; = files[<span class="number">0</span>];</span></span><br><span class="line"><span class="javascript">        message.innerHTML = <span class="string">&quot;Loading ffmpeg-core.js&quot;</span>;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">await</span> ffmpeg.load();</span></span><br><span class="line"><span class="javascript">        message.innerHTML = <span class="string">&quot;Start transcoding&quot;</span>;</span></span><br><span class="line"><span class="javascript">        ffmpeg.FS(<span class="string">&quot;writeFile&quot;</span>, name, <span class="keyword">await</span> fetchFile(files[<span class="number">0</span>]));</span></span><br><span class="line"><span class="javascript">        <span class="keyword">await</span> ffmpeg.run(<span class="string">&quot;-i&quot;</span>, name, <span class="string">&quot;output.mp4&quot;</span>);</span></span><br><span class="line"><span class="javascript">        message.innerHTML = <span class="string">&quot;Complete transcoding&quot;</span>;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> data = ffmpeg.FS(<span class="string">&quot;readFile&quot;</span>, <span class="string">&quot;output.mp4&quot;</span>);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> video = <span class="built_in">document</span>.getElementById(<span class="string">&quot;output-video&quot;</span>);</span></span><br><span class="line"><span class="javascript">        video.src = URL.createObjectURL(</span></span><br><span class="line"><span class="javascript">          <span class="keyword">new</span> Blob([data.buffer], &#123;</span></span><br><span class="line"><span class="javascript">            <span class="attr">type</span>: <span class="string">&quot;video/mp4&quot;</span>,</span></span><br><span class="line"><span class="javascript">          &#125;)</span></span><br><span class="line"><span class="javascript">        );</span></span><br><span class="line"><span class="javascript">      &#125;;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">document</span>.getElementById(<span class="string">&quot;uploader&quot;</span>).addEventListener(<span class="string">&quot;change&quot;</span>, transcode);</span></span><br><span class="line"><span class="javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>压缩一个 30M 的视频能达到 2.3M 左右，但是因为 ffmpeg 库本身比较大，我们又是全部编译过来的，有大概 20 多 M，因此不建议在移动端使用</p><p>当然如果你是 C++ 大佬，可以自己精简 ffmpeg 库之后再编译</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="wasm" scheme="http://celsius/tags/wasm/"/>
    
  </entry>
  
  <entry>
    <title>使用阿里云轻量应用服务器搭建自己的 nodejs 接口服务器</title>
    <link href="http://celsius/2022/07/27/%E4%BD%BF%E7%94%A8%E9%98%BF%E9%87%8C%E4%BA%91%E8%BD%BB%E9%87%8F%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84nodejs%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%E5%99%A8-1%20-%20%E5%89%AF%E6%9C%AC/"/>
    <id>http://celsius/2022/07/27/%E4%BD%BF%E7%94%A8%E9%98%BF%E9%87%8C%E4%BA%91%E8%BD%BB%E9%87%8F%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84nodejs%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%E5%99%A8-1%20-%20%E5%89%AF%E6%9C%AC/</id>
    <published>2022-07-27T02:48:09.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>记录一下自己的学习历程<br>首先购买一台阿里云轻量应用服务器，之所以用这个是因为直接就搭建好了 node 的相关环境，到手即用<br><img src="https://img-blog.csdnimg.cn/20210720112232264.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoYXJsaWVfeQ==,size_16,color_FFFFFF,t_70"><br>之后在阿里云控制台就能看到服务器的相关信息，比如公网 ip 地址之类的，记得改密码，然后使用 XShell 就能连接上服务器使用命令行来操作，使用 XFtp 来上传文件到服务器，这里没什么说的哈，都是基本操作。<br>接下来先部署一下自己的静态网站，将你的网页文件上传到/var/www/文件夹下(比如 vue 项目就是 build 后将 dist 文件夹内的东西传上去)，然后配置一下 nginx(阿里云轻量应用服务器自带了 nginx)<br>nginx 的配置文件默认位置是/usr/local/nginx/conf/nginx.conf<br><img src="https://img-blog.csdnimg.cn/20210720113025747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoYXJsaWVfeQ==,size_16,color_FFFFFF,t_70"><br>使用 vim 或者记事本打开编辑 http 下的 server 里的参数，接着重启一下 nginx 就生效了，然后用浏览器打开 ip 地址就能看到<br>接下来就来到了接口的开发，首先我们在本地新建一个文件夹，起名叫 api，<br>进去之后我们打开命令行窗口</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm init</span><br></pre></td></tr></table></figure><p>配置一下相关信息<br>然后装上 express 框架并初始化一个项目</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ npm install express --save</span><br><span class="line">$ npm install express-generator -g</span><br><span class="line">$ express</span><br><span class="line">$ npm install</span><br></pre></td></tr></table></figure><p>然后就会生成这样的一个结构</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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><br><span class="line">.</span><br><span class="line">├── app.js</span><br><span class="line">├── bin</span><br><span class="line">│   └── www</span><br><span class="line">├── package.json</span><br><span class="line">├── public</span><br><span class="line">│   ├── images</span><br><span class="line">│   ├── javascripts</span><br><span class="line">│   └── stylesheets</span><br><span class="line">│       └── style.css</span><br><span class="line">├── routes</span><br><span class="line">│   ├── index.js</span><br><span class="line">│   └── users.js</span><br><span class="line">└── views</span><br><span class="line">    ├── error.jade</span><br><span class="line">    ├── index.jade</span><br><span class="line">    └── layout.jade</span><br></pre></td></tr></table></figure><p>这里具体的代码实现就不说了，先说一下怎么在服务器上跑起来<br>把 api 文件夹上传到服务器随便哪个目录(node_modules 文件夹别一起上传了)<br>然后使用 XShell cd 到这个文件夹使用 pm2 启动(pm2 可以在你关闭命令行窗口后保护进程运行，也是阿里云轻量应用服务器自带的)</p><figure class="highlight bash"><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">npm install</span><br><span class="line">pm2 start ./bin/www</span><br></pre></td></tr></table></figure><p>接着去阿里云控制台防火墙放开一下 3000 端口<br><img src="https://img-blog.csdnimg.cn/20210720115536425.png" alt="在这里插入图片描述"><br>然后我们用 ip 地址+3000，比如 47.96.109.208:3000 打开就能看到<br><img src="https://img-blog.csdnimg.cn/20210720115617558.png" alt="在这里插入图片描述"><br>成功！<br>后面就是在这个模板里修改代码来做接口啦</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="centOS" scheme="http://celsius/tags/centOS/"/>
    
  </entry>
  
  <entry>
    <title>vue纯前端实现json导出为excel文件(xlsx.full.min.js)</title>
    <link href="http://celsius/2022/03/20/vue%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0json%E5%AF%BC%E5%87%BA%E4%B8%BAexcel%E6%96%87%E4%BB%B6(xlsx.full.min.js)/"/>
    <id>http://celsius/2022/03/20/vue%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0json%E5%AF%BC%E5%87%BA%E4%B8%BAexcel%E6%96%87%E4%BB%B6(xlsx.full.min.js)/</id>
    <published>2022-03-20T07:51:09.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>废话不多说，能实现导出为 excel 的库也不少，这次我用的是 xlsx.full.min.js(git 地址：<a href="https://github.com/SheetJS/sheetjs">https://github.com/SheetJS/sheetjs</a>)<br>在 utils 里简单封装一下</p><figure class="highlight typescript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> XLSX <span class="keyword">from</span> <span class="string">&quot;xlsx&quot;</span>;</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></span><br><span class="line"><span class="keyword">const</span> s2ab = <span class="function">(<span class="params">s: <span class="built_in">string</span> = <span class="string">&quot;&quot;</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">//字符串转字符流</span></span><br><span class="line">  <span class="keyword">let</span> buf = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(s.length);</span><br><span class="line">  <span class="keyword">let</span> view = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(buf);</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i != s.length; ++i) view[i] = s.charCodeAt(i) &amp; <span class="number">0xff</span>;</span><br><span class="line">  <span class="keyword">return</span> buf;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将指定的自然数转换为26进制表示。映射关系：[0-25] -&gt; [A-Z]</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> getCharCol = <span class="function">(<span class="params">n: <span class="built_in">number</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> s = <span class="string">&quot;&quot;</span>,</span><br><span class="line">    m = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (n &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    m = (n % <span class="number">26</span>) + <span class="number">1</span>;</span><br><span class="line">    s = <span class="built_in">String</span>.fromCharCode(m + <span class="number">64</span>) + s;</span><br><span class="line">    n = (n - m) / <span class="number">26</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> s;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将json转换为excel格式流并使用a标签点击下载</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> downloadExl = <span class="function">(<span class="params">json: <span class="built_in">any</span>, <span class="keyword">type</span>: <span class="built_in">any</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> tmpdata: <span class="built_in">any</span> = json[<span class="number">0</span>];</span><br><span class="line">  json.unshift(&#123;&#125;);</span><br><span class="line">  <span class="keyword">let</span> keyMap: <span class="built_in">string</span>[] = []; <span class="comment">//获取keys</span></span><br><span class="line">  <span class="comment">//keyMap =Object.keys(json[0]);</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> k <span class="keyword">in</span> tmpdata) &#123;</span><br><span class="line">    keyMap.push(k);</span><br><span class="line">    json[<span class="number">0</span>][k] = k;</span><br><span class="line">  &#125;</span><br><span class="line">  tmpdata = [];</span><br><span class="line">  json</span><br><span class="line">    .map(<span class="function">(<span class="params">v: &#123; [x: <span class="built_in">string</span>]: <span class="built_in">any</span> &#125;, i: <span class="built_in">number</span></span>) =&gt;</span></span><br><span class="line">      keyMap.map(<span class="function">(<span class="params">k, j</span>) =&gt;</span></span><br><span class="line">        <span class="built_in">Object</span>.assign(</span><br><span class="line">          &#123;&#125;,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">v</span>: v[k],</span><br><span class="line">            <span class="attr">position</span>: (j &gt; <span class="number">25</span> ? getCharCol(j) : <span class="built_in">String</span>.fromCharCode(<span class="number">65</span> + j)) + (i + <span class="number">1</span>),</span><br><span class="line">          &#125;</span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">    )</span><br><span class="line">    .reduce(<span class="function">(<span class="params">prev: <span class="built_in">string</span> | <span class="built_in">any</span>[], next: <span class="built_in">any</span></span>) =&gt;</span> prev.concat(next))</span><br><span class="line">    .forEach(</span><br><span class="line">      <span class="function">(<span class="params">v: &#123; position: <span class="built_in">string</span> | <span class="built_in">number</span>; v: <span class="built_in">any</span> &#125;, i: <span class="built_in">any</span></span>) =&gt;</span></span><br><span class="line">        (tmpdata[v.position] = &#123;</span><br><span class="line">          <span class="attr">v</span>: v.v,</span><br><span class="line">        &#125;)</span><br><span class="line">    );</span><br><span class="line">  <span class="keyword">let</span> outputPos = <span class="built_in">Object</span>.keys(tmpdata); <span class="comment">//设置区域,比如表格从A1到D10</span></span><br><span class="line">  <span class="keyword">let</span> tmpWB = &#123;</span><br><span class="line">    <span class="attr">SheetNames</span>: [<span class="string">&quot;mySheet&quot;</span>], <span class="comment">//保存的表标题</span></span><br><span class="line">    <span class="attr">Sheets</span>: &#123;</span><br><span class="line">      <span class="attr">mySheet</span>: <span class="built_in">Object</span>.assign(</span><br><span class="line">        &#123;&#125;,</span><br><span class="line">        tmpdata, <span class="comment">//内容</span></span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">&quot;!ref&quot;</span>: outputPos[<span class="number">0</span>] + <span class="string">&quot;:&quot;</span> + outputPos[outputPos.length - <span class="number">1</span>], <span class="comment">//设置填充区域</span></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 class="keyword">let</span> tmpDown: <span class="built_in">any</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line">  tmpDown = <span class="keyword">new</span> Blob(</span><br><span class="line">    [</span><br><span class="line">      s2ab(</span><br><span class="line">        XLSX.write(</span><br><span class="line">          tmpWB,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">bookType</span>: <span class="string">&quot;xlsx&quot;</span>,</span><br><span class="line">            <span class="attr">bookSST</span>: <span class="literal">false</span>,</span><br><span class="line">            <span class="attr">type</span>: <span class="string">&quot;binary&quot;</span>,</span><br><span class="line">          &#125; <span class="comment">//这里的数据是用来定义导出的格式类型</span></span><br><span class="line">        )</span><br><span class="line">      ),</span><br><span class="line">    ],</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  ); <span class="comment">//创建二进制对象写入转换好的字节流</span></span><br><span class="line">  <span class="keyword">let</span> href = URL.createObjectURL(tmpDown); <span class="comment">//创建对象超链接</span></span><br><span class="line">  <span class="keyword">let</span> _a = <span class="built_in">document</span>.createElement(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">  _a.setAttribute(<span class="string">&quot;href&quot;</span>, href);</span><br><span class="line">  _a.setAttribute(<span class="string">&quot;download&quot;</span>, <span class="string">&quot;这里是excel文件名.xlsx&quot;</span>);</span><br><span class="line">  <span class="built_in">document</span>.body.appendChild(_a);</span><br><span class="line">  _a.click();</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">//延时释放</span></span><br><span class="line">    URL.revokeObjectURL(tmpDown); <span class="comment">//用URL.revokeObjectURL()来释放这个object URL</span></span><br><span class="line">  &#125;, <span class="number">100</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> &#123; downloadExl &#125;;</span><br></pre></td></tr></table></figure><p>在页面使用</p><figure class="highlight typescript"><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">import</span> &#123; downloadExl &#125; <span class="keyword">from</span> <span class="string">&quot;@/utils/util&quot;</span>;</span><br><span class="line"><span class="keyword">let</span> json = [</span><br><span class="line">  &#123;</span><br><span class="line">    提交时间: <span class="string">&quot;2021-05-19 14:57:59&quot;</span>,</span><br><span class="line">    用户名称: <span class="string">&quot;张大毛&quot;</span>,</span><br><span class="line">    编号: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">    更新时间: <span class="string">&quot;2021-05-19 14:57:59&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    提交时间: <span class="string">&quot;2021-05-17 11:32:48&quot;</span>,</span><br><span class="line">    用户名称: <span class="string">&quot;王德发&quot;</span>,</span><br><span class="line">    编号: <span class="string">&quot;2&quot;</span>,</span><br><span class="line">    更新时间: <span class="string">&quot;2021-05-17 11:32:48&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line">downloadExl(json);</span><br></pre></td></tr></table></figure><p>然后就会直接调起下载<br><img src="https://img-blog.csdnimg.cn/20210604101857571.png" alt="冷吃兔"><br>打开看看<br><img src="https://img-blog.csdnimg.cn/20210604102145291.png" alt="可以，完美"><br>可以，非常完美<br>各位看官点个赞啦</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue" scheme="http://celsius/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>Git的基本使用</title>
    <link href="http://celsius/2021/12/10/Git%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/"/>
    <id>http://celsius/2021/12/10/Git%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/</id>
    <published>2021-12-10T08:50:32.000Z</published>
    <updated>2023-09-20T05:33:50.495Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h3 id="Git的用途"><a href="#Git的用途" class="headerlink" title="Git的用途"></a>Git的用途</h3><ol><li>托管代码到远程，分布式托管，避免本机磁盘损坏造成不可挽回的局面。</li><li>版本控制，可以发布多个版本并且实现在各个版本之间来回穿梭（实现原理：文件快照，每个版本都会有一个文件快照，比直接备份文件快速便捷。因此，Git仓库又被称为版本库）。</li><li>团队协作，强大的分支功能，可以快速实现团队协作</li></ol><h3 id="Git代码托管平台"><a href="#Git代码托管平台" class="headerlink" title="Git代码托管平台"></a>Git代码托管平台</h3><ol><li><p>github： <a href="https://github.com/">https://github.com</a></p><p>github是全球最大的开源社交编程及代码托管网站，也被称为全球最大同性交友网（gayhub）👨‍❤️‍💋‍👨👨‍❤️‍💋‍👨👨‍❤️‍💋‍👨👨‍❤️‍💋‍👨👨‍❤️‍💋‍👨👨‍❤️‍👨👨‍❤️‍👨👨‍❤️‍👨👨‍❤️‍👨👨‍❤️‍👨</p></li><li><p>coding： <a href="https://dev.tencent.com/">https://dev.tencent.com</a></p><p>与腾讯云达成战略合作，基于云计算技术的软件开发平台，集项目管理、代码托管、运行空间、质量控制为一体。</p></li><li><p>gitee： <a href="https://gitee.com/">https://gitee.com</a></p><p>码云(gitee.com)是 OSCHINA.NET 推出的代码托管平台,支持 Git 和 SVN,提供免费的私有仓库托管。</p></li></ol><h3 id="Git使用步骤"><a href="#Git使用步骤" class="headerlink" title="Git使用步骤"></a>Git使用步骤</h3><p><em>本次以gitee为例做讲解，读者也可以选择github或者coding，使用步骤一致，步骤中使用 [] 中括号括起来的部分需要替换成你自己相应的内容，不需要保留中括号。</em></p><ol><li> <strong>第一次安装git的时候要执行1-4步，否则从第5步开始</strong></li></ol><p>   安装git工具，安装完成之后鼠标右键只要出现 <code>git bash here</code> 菜单即说明安装成功。</p><p>   <a href="https://git-scm.com/download/win">windows系统下载链接</a></p><p>   <a href="https://git-scm.com/download/mac">MAC下载链接</a></p><ol start="2"><li><p>注册gitee的账号（或其他平台账号），修改个人空间地址，绑定邮箱。</p></li><li><p>全局配置用户名和邮箱</p><p><code>git config --global user.name[你的码云账号]</code></p><p><code>git config --global user.email [你的码云验证邮箱]</code></p></li><li><p>配置密钥对：生成公钥和私钥，用于上传代码时的安全验证</p><p>在<code>git bash</code>里执行命令<code>ssh-keygen</code> 一路回车，就可以生成密钥对，默认密钥对是存放在<code>(/c/Users/[主机用户名]/.ssh/)</code> 。这个目录下有两个文件， .pub就是公钥，另外一个是私钥，这两个文件千万不要动！！！</p><p>到线上（gitee或其他平台）打开设置-&gt;安全设置-&gt;ssh公钥，把本地的公钥文件全选复制进来，输入登录密码，就配置成功了。</p></li><li><p><strong>第一次创建项目的时候执行5-7步，否则从第8步开始</strong></p><p>创建本地仓库</p><p>在本地创建一个项目文件夹，项目代码都在这个文件夹里，执行 <code>git init</code> 初始化一个本地git仓库，这个时候项目里会多出一个.git目录（这个目录默认是隐藏的，这里就是用来存放文件快照的地方），这个目录千万不要动！！！</p></li><li><p>创建一个线上仓库</p><p>登录gitee，新建仓库，输入项目名称，选择私有或者公开源代码（私有在加入合作者之前就只能你自己能查看，公开就意味着开源），下面的选框一个都不要勾（初始化的不是文件都来自于本地仓库，线上仓库不需要任何文件），最后点击创建就ok了。</p></li><li><p>将本地仓库和线上仓库建立关联：<code>git remote add origin [线上仓库的SSH地址]</code></p><p> 如果在执行这句话的时候报错：<code>fatal: remote origin already exists.</code></p><p> 那么就先执行 <code>git remote rm origin</code></p><p> 再重新执行 <code>git remote add origin [线上仓库的SSH地址]</code></p></li><li><p>代码添加到暂存区 <code>git add -A</code> (也可以 git add [文件名] 来单独添加某一个文件)</p></li><li><p>代码提交到本地仓库 <code>git commit -m &#39;[说明本次提交所做的操作，越详细越好]&#39;</code></p></li><li><p>代码推送到远程 <code>git push origin master</code></p></li></ol><h3 id="版本管理"><a href="#版本管理" class="headerlink" title="版本管理"></a>版本管理</h3><ol><li><p>把已经放在暂存区的内容在拉回到工作区</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 拉回暂存区的 index.txt 文件</span><br><span class="line">git reset HEAD -- index.txt</span><br><span class="line"></span><br><span class="line"># 拉回暂存区的 ceshi 文件夹</span><br><span class="line">git reset HEAD -- ceshi/</span><br><span class="line"></span><br><span class="line"># 拉回暂存区的 所有文件</span><br><span class="line">git reset HEAD -- .</span><br></pre></td></tr></table></figure></li><li><p>历史版本回退</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 查看历史版本</span><br><span class="line">git log</span><br></pre></td></tr></table></figure></li></ol><p><a href="http://www.xiongdalin.com/img/article/git%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF.png"><img src="http://www.xiongdalin.com/img/article/git%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF.png" alt="git版本信息">git版本信息</a></p><p>这里commit后面的字符串即为版本号，我们可以使用 <code>git reset --hard 版本编号</code> 进行历史回退</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 回退到第一次提交的版本</span><br><span class="line">git reset --hard ce0c17f7a703c6847552c7aaab6becea6f0197f2</span><br><span class="line"></span><br><span class="line"># 回退到第二次提交的版本</span><br><span class="line">git reset --hard abb2c4f12566440e04bc166c3285f855a37a3bb2</span><br></pre></td></tr></table></figure><h3 id="Git的用途-1"><a href="#Git的用途-1" class="headerlink" title="Git的用途"></a>Git的用途</h3><p>git分支，就是我们自己把我们的整个文件夹分成一个一个独立的区域，比如我在开发 <strong>登录</strong> 功能的时候，可以放在 <code>login</code> 分支下进行开发；开发 <strong>列表</strong> 功能的时候，可以放在 <code>list</code> 分支下进行开发，大家互不干扰，每一个功能都是一个独立的功能分支，这样开发就会好很多。</p><p>git在初始化的时候，会自动生成一个分支，叫做 <code>master</code>，是表示主要分支的意思，我们就可以自己基于<code>master</code>开辟出很多独立分支</p><ol><li><p>开辟一个分支使用 <code>git branch 分支名称</code> 指令</p><figure class="highlight bash"><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="comment"># 开辟一个 login 分支</span></span><br><span class="line">$ git branch login</span><br></pre></td></tr></table></figure></li><li><p>查看一下当前分支情况</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 查看当前分支情况</span><br><span class="line">$ git branch</span><br></pre></td></tr></table></figure><p>[<img src="http://www.xiongdalin.com/img/article/%E6%9F%A5%E7%9C%8B%E5%88%86%E6%94%AF%E6%83%85%E5%86%B5.png" alt="查看分支情况">查看分</p><p><a href="http://www.xiongdalin.com/img/article/%E6%9F%A5%E7%9C%8B%E5%88%86%E6%94%AF%E6%83%85%E5%86%B5.png">支情况</a></p><p>已经可以看到，当前有两个分支了，一个是 <code>master</code>，一个是 <code>login</code>。前面有个 <code>*</code> 号，并且有高亮显示的，表示你当前所处的分支。</p><p>我们对 <strong>登录</strong> 功能的开发要移动到 <code>login</code> 分支去完成，此时可以切换到其他分支，使用 <code>git checkout 分支名称</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 切换到 login 分支</span><br><span class="line">$ git checkout login</span><br></pre></td></tr></table></figure><p>然后我们在整个<code>login</code>分支上进行 <strong>登录</strong> 功能的开发。开发完毕以后，我们就在当前分支上进行提交，提交以后我们再把分支切换回<code>master</code>发现 <code>master</code> 上面还是最初始的状态，而 <code>login</code> 分支上有我们新写的 <strong>登录</strong> 功能的代码。我们按照分支把所有功能都开发完毕了以后，需要把所有代码都合并到 <code>master</code> 主分支上。</p><p><code>git</code> 的合并分支，只能是把别的分支的内容合并到自己的分支上</p><p>使用的指令是 <code>git merge</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 切换到 master 分支</span><br><span class="line">$ git checkout master</span><br><span class="line"></span><br><span class="line"># 把 login 的内容合并到自己的分支</span><br><span class="line">$ git merge login</span><br></pre></td></tr></table></figure><p>这个时候，我们刚才在 <code>login</code> 上开发的东西就都来到了 <code>master</code> 主分支上，<strong>如果是有多个分支的话，那么所有的最后都合并到 <code>master</code> 分支上的时候，我们的主分支上就有完整网站的所有页面，各个分支上都是单独的页面和功能</strong>。这个时候我们开辟的分支就没有什么用了，就可以删除分支了</p><ol><li><p>先切换到别的分支</p></li><li><p>使用指令 <code>git branch -d 分支名称</code> 来删除</p></li><li><p>```base</p><h1 id="先切换到别的分支"><a href="#先切换到别的分支" class="headerlink" title="先切换到别的分支"></a>先切换到别的分支</h1><p>$ git checkout master</p><h1 id="删除-login-分支"><a href="#删除-login-分支" class="headerlink" title="删除 login 分支"></a>删除 login 分支</h1><p>$ git branch -d login</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">### 常用的分支命名</span><br><span class="line"></span><br><span class="line">在使用分支的时候我们的分支命名也要尽量规范一些，我们有一些分支名称大家都默认是有特殊意义的，比如我们之前的写的`login`分支就是不规范的分支名称。</span><br><span class="line"></span><br><span class="line">常见的分支名称</span><br><span class="line"></span><br><span class="line">1. master：主分支，永远只存储一个可以稳定运行的版本，不能在这个分支上直接开发</span><br><span class="line">2. develop（dev）： 主要开发分支，主要用于所用功能开发的代码合并，记录一个个的完整版本</span><br><span class="line">   - 包含测试版本和稳定版本</span><br><span class="line">   - 不要在这个分支上进行开发</span><br><span class="line">3. feature-xxx（feat-xxx）：功能开发分支，从`dev`创建的分支主要用作某一个功能的开发，以自己功能来命名就行，例如 `feat-login` / `feat-list`，开发完毕后合并到 `dev` 分支上</span><br><span class="line">4. feat-xxx-fix: 某一分支出现`bug`以后，在当前分支下开启一个`fix`分支，比如登录功能有bug，可以基于`feat-login`开辟一个新的分支`feat-login-fix`，解决完 `bug` 以后，合并到当前功能分支上，如果是功能分支已经合并之后发现 `bug` 可以直接在 `develop` 上开启分支，修复完成之后合并到 `dev` 分支上</span><br><span class="line">5. hotfix-xxx： 用于紧急`bug`修复，直接在 `master` 分支上开启，修复完成之后合并回 `master`</span><br><span class="line"></span><br><span class="line">### 冲突解决</span><br><span class="line"></span><br><span class="line">git冲突是指在我们的上传过程中，本地的版本和远程的版本不一致导致的，这个时候只要先使用 `git pull` 拉取回来，让本地和远程保持一致，然后再从新上传就好了，但是 `git pull` 相对不安全，因为会自动和本地内容合并，我们也可以选择使用 `git fetch`</span><br><span class="line"></span><br><span class="line">```base</span><br><span class="line"># 使用 fetch 获取远程最新信息并开辟一个临时分支</span><br><span class="line">$ git fetch origin master:tmp</span><br><span class="line"></span><br><span class="line"># 将当前分支和临时分支的内容进行对比</span><br><span class="line">$ git diff tmp</span><br><span class="line"></span><br><span class="line"># 再选择合并分支内容</span><br><span class="line">$ git merge tmp</span><br></pre></td></tr></table></figure></li></ol></li></ol><h3 id="附录1：Git常见命令"><a href="#附录1：Git常见命令" class="headerlink" title="附录1：Git常见命令"></a>附录1：Git常见命令</h3><ul><li><code>git init</code>  初始化仓库</li><li><code>git config</code>  配置用户信息</li><li><code>git remote add origin [线上仓库地址]</code>  新增远程仓库的关联</li><li><code>git remote rm origin</code>  删除远程仓库的关联</li><li><code>git add</code>  添加到暂存区</li><li><code>git commit -m &#39;[本次提交的备注信息]&#39;</code>  代码提交（每一次commit都会有一个新的版本号）</li><li><code>git push origin [分支名]</code>  推送到远程仓库</li><li><code>git status</code>  查看当前仓库的状态</li><li><code>git log</code>  查看日志（每一个commit在这里都能查看到，而且commit后面的随机字符串就是版本号），按字母q 退出log</li><li><code>git reset --hard [要回退的版本号]</code>  回退到之前的某一个版本</li><li><code>git clone [线上仓库地址]</code>  把线上仓库代码克隆到本地</li><li><code>git pull origin [分支名]</code>  在已有的仓库基础上拉取某分支最新的线上代码，拉取之后直接合并</li><li><code>git fetch origin [分支名]</code>  在已有的仓库基础上拉取某分支最新的线上代码，拉取之后由用户决定是否合并</li><li><code>git branch</code>  查看分支</li><li><code>git branch newBranch</code>  基于当前分支创建<code>newBranch</code>分支</li><li><code>git branch -d myBranch</code>  删除<code>myBranch</code>分支</li><li><code>git diff tmp</code>  查看当前分支和<code>tmp</code>分支的区别</li><li><code>git merge tmp</code>  将<code>tmp</code>分支合并到当前分支</li></ul><h2 id="附录2：使用Git时候的一些注意事项"><a href="#附录2：使用Git时候的一些注意事项" class="headerlink" title="附录2：使用Git时候的一些注意事项"></a>附录2：使用Git时候的一些注意事项</h2><ul><li>一个本地仓库对应一个远程仓库</li><li>远程代码和本地代码要保持统一</li><li>.git 文件不能嵌套（仓库不能嵌套）</li></ul><h2 id="附录3：使用Git提交时的备注信息"><a href="#附录3：使用Git提交时的备注信息" class="headerlink" title="附录3：使用Git提交时的备注信息"></a>附录3：使用Git提交时的备注信息</h2><p><strong>用于说明 commit 的类别，只允许使用下面7个标识</strong></p><ol><li>feat：新功能（feature）</li><li>fix：修补bug</li><li>docs：文档（documentation）</li><li>style： 格式（不影响代码运行的变动）</li><li>refactor：重构（即不是新增功能，也不是修改bug的代码变动）</li><li>test：增加测试</li><li>chore：构建过程或辅助工具的变动</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="git" scheme="http://celsius/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>在uniapp中使用SVGAPlayer播放svga动画</title>
    <link href="http://celsius/2021/08/20/%E5%9C%A8uniapp%E4%B8%AD%E4%BD%BF%E7%94%A8SVGAPlayer%E6%92%AD%E6%94%BEsvga%E5%8A%A8%E7%94%BB/"/>
    <id>http://celsius/2021/08/20/%E5%9C%A8uniapp%E4%B8%AD%E4%BD%BF%E7%94%A8SVGAPlayer%E6%92%AD%E6%94%BEsvga%E5%8A%A8%E7%94%BB/</id>
    <published>2021-08-20T07:51:09.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>在uniapp中使用svga，我是只需要编译为微信小程序和h5，其他端的暂时没研究。完整代码在最后<br>首先是微信小程序，从<a href="https://github.com/svga/svgaplayer-weapp">https://github.com/svga/svgaplayer-weapp</a>下载svgaplayer.weapp.js，按照教程引入即可</p><figure class="highlight html"><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="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">canvas</span></span></span><br><span class="line"><span class="tag">      <span class="attr">type</span>=<span class="string">&quot;2d&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">style</span>=<span class="string">&quot;width: 300px; height:300px;background-color: #000;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">id</span>=<span class="string">&quot;myCanvas&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> SVGA = <span class="built_in">require</span>(<span class="string">&#x27;../../utils/svga.weapp.js&#x27;</span>)</span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="title">onLoad</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> player = <span class="keyword">new</span> SVGA.Player(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> parser = <span class="keyword">new</span> SVGA.Parser(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">    parser.load(</span><br><span class="line">      <span class="string">&#x27;https://qiniu.taktak.tv/spring-headlines2022/inlive.svga&#x27;</span>,</span><br><span class="line">      <span class="function"><span class="keyword">function</span>(<span class="params">value: any</span>) </span>&#123;</span><br><span class="line">        player.setVideoItem(value)</span><br><span class="line">        player.startAnimation()</span><br><span class="line">      &#125;</span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着是h5，从<a href="https://github.com/svga/SVGAPlayer-Web">https://github.com/svga/SVGAPlayer-Web</a>下载svga.min.js，按照教程引入，运行，发现报错<br><img src="https://img-blog.csdnimg.cn/da592e063a3e4953bad7340edac8a5a4.png" alt="在这里插入图片描述"><br>看报错原因我们能发现，getContext这是canvas的一个api，而这个报错应该是因为没有通过#myCanvas获取到对应的canvas dom节点，我们点开Elements就会发现这是因为uniapp在编译h5的时候，会把canvas节点上的id属性给弄到uni-canvas这个节点上，这也许是为了重写多端的canvas方法<br><img src="https://img-blog.csdnimg.cn/bedb7cf12b7448b88b95fa924139745a.png" alt="在这里插入图片描述"><br>但是这样的话我们就没办法拿到真正的canvas节点了啊，这咋办。只能直接上暴力美学，咱们直接用js插入一个canvas dom 进去，下面附全部代码</p><figure class="highlight html"><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="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span> <span class="attr">id</span>=<span class="string">&quot;main&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- #ifndef H5 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">canvas</span></span></span><br><span class="line"><span class="tag">      <span class="attr">type</span>=<span class="string">&quot;2d&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">style</span>=<span class="string">&quot;width: 300px; height:300px;background-color: #000;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">id</span>=<span class="string">&quot;myCanvas&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">ref</span>=<span class="string">&quot;myCanvas&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- #endif --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><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="keyword">let</span> SVGA: any = <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="comment">// #ifndef H5</span></span><br><span class="line">SVGA = <span class="built_in">require</span>(<span class="string">&#x27;../../utils/svga.weapp.js&#x27;</span>)</span><br><span class="line"><span class="comment">// #endif</span></span><br><span class="line"><span class="comment">// #ifdef H5</span></span><br><span class="line">SVGA = <span class="built_in">require</span>(<span class="string">&#x27;../../utils/svga.min.js&#x27;</span>)</span><br><span class="line"><span class="comment">// #endif</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="title">onLoad</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="comment">//#ifndef H5</span></span><br><span class="line">    <span class="keyword">const</span> player = <span class="keyword">new</span> SVGA.Player(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> parser = <span class="keyword">new</span> SVGA.Parser(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      parser.load(</span><br><span class="line">        <span class="string">&#x27;https://qiniu.taktak.tv/spring-headlines2022/inlive.svga&#x27;</span>,</span><br><span class="line">        <span class="function"><span class="keyword">function</span>(<span class="params">value: any</span>) </span>&#123;</span><br><span class="line">          player.setVideoItem(value)</span><br><span class="line">          player.startAnimation()</span><br><span class="line">        &#125;</span><br><span class="line">      )</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line">    <span class="comment">//#endif</span></span><br><span class="line">    <span class="comment">//#ifdef H5</span></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> canvasList: any = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;main&#x27;</span>)</span><br><span class="line">      <span class="keyword">const</span> canvas = <span class="built_in">document</span>.createElement(<span class="string">&#x27;canvas&#x27;</span>)</span><br><span class="line">      canvas.width = <span class="number">300</span></span><br><span class="line">      canvas.height = <span class="number">300</span></span><br><span class="line">      canvas.style.backgroundColor = <span class="string">&#x27;#000&#x27;</span></span><br><span class="line">      canvas.id = <span class="string">&#x27;myCanvas&#x27;</span></span><br><span class="line">      canvasList.appendChild(canvas)</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> player = <span class="keyword">new</span> SVGA.Player(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">        <span class="keyword">const</span> parser = <span class="keyword">new</span> SVGA.Parser(<span class="string">&#x27;#myCanvas&#x27;</span>)</span><br><span class="line">        parser.load(</span><br><span class="line">          <span class="string">&#x27;https://qiniu.taktak.tv/spring-headlines2022/inlive.svga&#x27;</span>,</span><br><span class="line">          <span class="function"><span class="keyword">function</span>(<span class="params">value: any</span>) </span>&#123;</span><br><span class="line">            player.setVideoItem(value)</span><br><span class="line">            player.startAnimation()</span><br><span class="line">          &#125;</span><br><span class="line">        )</span><br><span class="line">      &#125;, <span class="number">0</span>)</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line">    <span class="comment">//#endif</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue" scheme="http://celsius/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>记录 vue-ts 引入 js 文件遇到无法编译的问题</title>
    <link href="http://celsius/2021/05/24/%E8%AE%B0%E5%BD%95vue-ts%E5%BC%95%E5%85%A5js%E6%96%87%E4%BB%B6%E9%81%87%E5%88%B0%E6%97%A0%E6%B3%95%E7%BC%96%E8%AF%91%E7%9A%84%E9%97%AE%E9%A2%98/"/>
    <id>http://celsius/2021/05/24/%E8%AE%B0%E5%BD%95vue-ts%E5%BC%95%E5%85%A5js%E6%96%87%E4%BB%B6%E9%81%87%E5%88%B0%E6%97%A0%E6%B3%95%E7%BC%96%E8%AF%91%E7%9A%84%E9%97%AE%E9%A2%98/</id>
    <published>2021-05-24T06:48:09.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>在 ts 环境中引入 js 文件有 import 方式和 require 方式，import 方式需要写个声明，require 则不需要，不过 eslint 会报错，可以在 eslinttrc.js 中加入</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&#x27;@typescript-eslint/no-var-requires&#x27;</span>: <span class="string">&#x27;off&#x27;</span></span><br></pre></td></tr></table></figure><p>来关闭验证<br>这都没啥问题，然后重新编译一下，诶，怎么卡在一个 node_modules 里的一个依赖这卡了五六分钟。我这引入第三方 js 跟依赖也没关系啊。<br>然后百度+谷歌，并没有找到解决方案<br>最后我就打开了这个 js 文件，是一个压缩后的 js 文件，使用 prettier 反压缩之后准备看看是不是哪里写的有问题，接着神奇的事发生了，编译直接通过，十分之丝滑……<br>目前没找到仅仅一个压缩会阻塞编译的原因，希望有大佬知道的可以留言告知，谢谢~</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="vue" scheme="http://celsius/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>h5监听页面展示隐藏返回</title>
    <link href="http://celsius/2021/05/06/h5%E7%9B%91%E5%90%AC%E9%A1%B5%E9%9D%A2%E5%B1%95%E7%A4%BA%E9%9A%90%E8%97%8F%E8%BF%94%E5%9B%9E/"/>
    <id>http://celsius/2021/05/06/h5%E7%9B%91%E5%90%AC%E9%A1%B5%E9%9D%A2%E5%B1%95%E7%A4%BA%E9%9A%90%E8%97%8F%E8%BF%94%E5%9B%9E/</id>
    <published>2021-05-06T08:30:11.000Z</published>
    <updated>2023-09-20T05:33:50.511Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><ol><li><p>在开发中遇到了需要监听页面隐藏展示（比如切到后台又切回来），这个时候我立马想到onShow生命周期，不过反应过来这个是微信小程序的。如果想在h5里面监听的话，只需要加一个监听器</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">&#x27;visibilitychange&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">document</span>.visibilityState == <span class="string">&#x27;visible&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">//页面展示 </span></span><br><span class="line">  &#125; eles &#123;</span><br><span class="line">    <span class="comment">//页面隐藏</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>解决了这个问题，又来了一个新的问题，比如我开发的A页面，从A页面跳转到B页面，再从B页面返回到A页面，这个使用上面所说的visibilitychange是监听不到的，因为本质上讲A页面并没有被隐藏，这里不展开细说，只说怎么解决，那就是用另一个事件监听 popstate</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">&#x27;popstate&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">//监听到页面返回</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>好的，又把这个问题解决了，但是出现了一个新的问题，那就是如果我使用了vue-router，在单页面应用里进行路由跳转，因为链接的变化，路由跳转也会被认为是页面之间的切换，因此也会触发popstate这个监听。<br>但这显然不是我想要的效果，因此我只好又拿出了另外一个事件监听: pageshow<br>eg: 这里使用popstate也可以完成需求，用全局参数记录一下是否路由跳转就行</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">&#x27;pageshow&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line"> <span class="comment">//监听到页面展示</span></span><br><span class="line"> <span class="keyword">if</span> (StoreModule.loadEnd) &#123;<span class="comment">//loadEnd为true即为页面加载完成</span></span><br><span class="line">    <span class="comment">//to do something</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>pageshow，字面上就能看出是页面展示的意思，因此页面的最开始加载和从其他页面返回才会触发，而hash路由跳转的时候并不会触发，再用一个vuex保存一个页面是否加载完成的参数来判断，就能完美处理页面返回的监听处理逻辑了。</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    
    <category term="git" scheme="http://celsius/tags/git/"/>
    
  </entry>
  
</feed>
