<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>JCAlways</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.zhangsifan.com/</id>
  <link href="https://blog.zhangsifan.com/" rel="alternate"/>
  <link href="https://blog.zhangsifan.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, JCAlways</rights>
  <subtitle>前端开发与编程技术笔记 | 张思帆的博客</subtitle>
  <title>JCAlways</title>
  <updated>2025-10-10T16:00:00.000Z</updated>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="OnlyOffice" scheme="https://blog.zhangsifan.com/categories/OnlyOffice/"/>
    <category term="OnlyOffice" scheme="https://blog.zhangsifan.com/tags/OnlyOffice/"/>
    <content>
      <![CDATA[<h2 id="前端使用"><a href="#前端使用" class="headerlink" title="前端使用"></a>前端使用</h2><p>OnlyOffice 文档服务器（Document Server）提供了前端 SDK，通过简单的 <code>DocsAPI.DocEditor</code> 即可在网页中嵌入在线文档的查看与编辑功能。整体流程分三步：</p><ol><li><strong>引入 SDK</strong>：在页面中加载文档服务器提供的 <code>api.js</code>（注意域名需替换为你的 OnlyOffice 服务地址）</li><li><strong>配置 config 对象</strong>：声明文档信息（<code>document</code>）、编辑配置（<code>editorConfig</code>）与事件回调（<code>events</code>）</li><li><strong>实例化编辑器</strong>：<code>new DocsAPI.DocEditor(容器id, config)</code> 将编辑器挂载到指定 DOM 中</li></ol><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><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br></pre></td><td class="code"><pre><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">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&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OnlyOffice<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>,</span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-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">script</span></span></span><br><span class="line"><span class="tag">      <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">src</span>=<span class="string">&quot;https://example.com/web-apps/apps/api/documents/api.js&quot;</span></span></span><br><span class="line"><span class="tag">    &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">div</span> <span class="attr">id</span>=<span class="string">&quot;preview&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">let</span> config = &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">document</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">documentType</span>: <span class="string">&#x27;text&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">width</span>: <span class="string">&#x27;100%&#x27;</span>, <span class="comment">//打开窗口宽度</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">height</span>: <span class="string">&#x27;100%&#x27;</span>, <span class="comment">//打开窗口高度</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">fileType</span>: fileType, <span class="comment">//文档类型</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">key</span>: key, <span class="comment">//定义用于服务识别文档的唯一文档标识符。每次编辑和保存文档时，都必须重新生成密钥。长度限制为128个符号。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">title</span>: title, <span class="comment">//为查看或编辑的文档定义所需的文件名，该文件名也将在下载文档时用作文件名。长度限制为128个符号。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">url</span>: url, <span class="comment">//定义存储原始查看或编辑的文档的绝对URL</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">info</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">owner</span>: <span class="string">&#x27;&#x27;</span>, <span class="comment">//文件创建者名称</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">sharingSettings</span>: [</span></span><br><span class="line"><span class="language-javascript">              <span class="comment">//文件对应用户的操作权限配置</span></span></span><br><span class="line"><span class="language-javascript">              &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">permissions</span>: <span class="string">&#x27;Full Access&#x27;</span>, <span class="comment">// 完全操作权限-Full Access,只读权限-Read Only 拒绝访问-Deny Access</span></span></span><br><span class="line"><span class="language-javascript">                <span class="attr">user</span>: <span class="string">&#x27;&#x27;</span>, <span class="comment">//有次权限的用户</span></span></span><br><span class="line"><span class="language-javascript">              &#125;,</span></span><br><span class="line"><span class="language-javascript">              &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">permissions</span>: <span class="string">&#x27;Read Only&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">user</span>: <span class="string">&#x27;&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">              &#125;,</span></span><br><span class="line"><span class="language-javascript">            ],</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">uploaded</span>: <span class="string">&#x27;&#x27;</span>, <span class="comment">//文件创建时间</span></span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">          <span class="comment">//文档权限参数</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">permissions</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">edit</span>: <span class="literal">true</span>, <span class="comment">//（文件是否可以编辑，false时文件不可编辑）</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">fillForms</span>: <span class="literal">true</span>, <span class="comment">//定义是否能在文档中填充表单</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">print</span>: <span class="literal">true</span>, <span class="comment">//定义文档是否能打印</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">review</span>: <span class="literal">false</span>, <span class="comment">//第一是否显示审阅文档菜单</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">comment</span>: <span class="literal">true</span>, <span class="comment">//定义是否可以注释文档。如果注释权限设置为“ true”，则文档侧栏将包含“注释”菜单选项；只有将mode参数设置为edit时才生效，默认值与edit参数的值一致。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">copy</span>: <span class="literal">true</span>, <span class="comment">//是否允许您将内容复制到剪贴板。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">download</span>: <span class="literal">true</span>, <span class="comment">//定义是否可以下载文档或仅在线查看或编辑文档。如果下载权限设置为“false”下载为菜单选项将没有。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">modifyContentControl</span>: <span class="literal">true</span>, <span class="comment">//定义是否可以更改内容控件设置。仅当mode参数设置为edit时，内容控件修改才可用于文档编辑器。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">modifyFilter</span>: <span class="literal">true</span>, <span class="comment">//定义过滤器是否可以全局应用（true）影响所有其他用户，或局部应用（false），即仅适用于当前用户。如果将mode参数设置为edit，则过滤器修改仅对电子表格编辑器可用。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">        &#125;,</span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// type: &quot;embedded&quot;,</span></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">//打开文档类型</span></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// text对应各种文档类型(.doc, .docm, .docx, .dot, .dotm, .dotx, .epub, .fodt, .htm, .html, .mht, .odt, .ott, .pdf, .rtf, .txt, .djvu, .xps)</span></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">//spreadsheet对应表格类型(.csv, .fods, .ods, .ots, .xls, .xlsm, .xlsx, .xlt, .xltm, .xltx)</span></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">//presentation对应PPT类型(.fodp, .odp, .otp, .pot, .potm, .potx, .pps, .ppsm, .ppsx, .ppt, .pptm, .pptx)</span></span></span><br><span class="line"><span class="language-javascript">        <span class="attr">editorConfig</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="comment">//编辑配置</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">createUrl</span>: <span class="string">&#x27;http://docServer:port/url-to-create-document/&#x27;</span>, <span class="comment">//指定创建文件的页面,添加该配置后文档服务器插件才会显示新建文件按钮</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">mode</span>: model, <span class="comment">//文档操作模式 view 视图模式不可编辑  edit 编辑模式可编辑文档</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">callbackUrl</span>: callbackUrl, <span class="comment">//保存文件时的回调地址</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">lang</span>: <span class="string">&#x27;zh-CN&#x27;</span>, <span class="comment">//语言环境</span></span></span><br><span class="line"><span class="language-javascript">          <span class="attr">customization</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">//定制部分允许自定义编辑器界面，使其看起来像您的其他产品，并更改是否存在其他按钮，链接，更改徽标和编辑者所有者详细信息。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">help</span>: <span class="literal">false</span>, <span class="comment">//定义是显示还是隐藏“帮助”菜单按钮。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">hideRightMenu</span>: <span class="literal">false</span>, <span class="comment">//定义在第一次加载时是显示还是隐藏右侧菜单。默认值为false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">autosave</span>: <span class="literal">false</span>, <span class="comment">//定义是启用还是禁用“自动保存”菜单选项。请注意，如果您在菜单中更改此选项，它将被保存到浏览器的localStorage中。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">forcesave</span>: <span class="literal">true</span>, <span class="comment">//定义保存按钮是否显示默认false</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">chat</span>: <span class="literal">false</span>, <span class="comment">//定义“聊天”菜单按钮是显示还是隐藏；请注意，如果您隐藏“聊天”按钮，则相应的聊天功能也将被禁用。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">commentAuthorOnly</span>: <span class="literal">false</span>, <span class="comment">//定义用户是否只能编辑和删除他的评论。默认值为false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">comments</span>: <span class="literal">false</span>, <span class="comment">//定义是显示还是隐藏“注释”菜单按钮；请注意，如果您隐藏“评论”按钮，则相应的评论功能将仅可用于查看，评论的添加和编辑将不可用。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">compactHeader</span>: <span class="literal">false</span>, <span class="comment">//定义是否将菜单栏放在在徽标旁边使界面更加紧凑默认false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">compactToolbar</span>: <span class="literal">false</span>, <span class="comment">//定义显示的顶部工具栏类型是完整（false）还是紧凑true。默认值为false 多余菜单将在右侧折叠点击显示。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">compatibleFeatures</span>: <span class="literal">false</span>, <span class="comment">//定义仅与OOXML格式兼容的功能的使用。例如，不要在整个文档上使用注释。默认值为false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">macros</span>: <span class="literal">false</span>, <span class="comment">//定义是否将运行文档宏以及可用的宏设置。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">macrosMode</span>: <span class="string">&#x27;warn&#x27;</span>, <span class="comment">//定义是否将运行文档宏。可以采用以下值： disable -根本不运行；enable -自动运行所有宏；warn -警告宏并请求允许运行。默认值为original。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">plugins</span>: <span class="literal">false</span>, <span class="comment">//定义是否将启动插件并可用。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">showReviewChanges</span>: <span class="literal">false</span>, <span class="comment">//定义在加载编辑器时是否自动显示或隐藏审阅更改面板。默认值为false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">spellcheck</span>: <span class="literal">false</span>, <span class="comment">//定义在加载编辑器时是否自动打开或关闭拼写检查器。拼写检查器仅适用于文档编辑器和演示文稿编辑器。默认值为true。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">toolbarNoTabs</span>: <span class="literal">false</span>, <span class="comment">//定义是突出显示顶部工具栏选项卡样式。默认值为false。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">unit</span>: <span class="string">&#x27;cm&#x27;</span>, <span class="comment">//定义在标尺和对话框中使用的度量单位。可以采用以下值：cm -厘米，pt-点，inch -英寸。默认值为厘米（cm）。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">zoom</span>: <span class="number">100</span>, <span class="comment">//定义以百分比为单位的文档显示缩放值。可以取大于0的值。对于文本文档和演示文稿，可以将此参数设置为-1（使文档适合页面选项）或-2（使文档页面宽度适合编辑器页面）。默认值为100。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">customer</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="comment">//关于 文档编辑器的显示信息</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">address</span>: <span class="string">&#x27;My City, 123a-45&#x27;</span>, <span class="comment">//有权访问编辑或编辑作者的公司或个人的邮政地址，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">info</span>: <span class="string">&#x27;Some additional information&#x27;</span>, <span class="comment">//有关您希望其他人认识的公司或个人的一些其他信息，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">logo</span>: <span class="string">&#x27;https://example.com/logo-big.png&#x27;</span>, <span class="comment">//图片徽标的路径（此文件没有特别建议，但是如果使用透明背景的.png格式会更好）。图片必须具有以下尺寸：432x70，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">mail</span>: <span class="string">&#x27;john@example.com&#x27;</span>, <span class="comment">//有权访问编辑者或编辑者的公司或个人的电子邮件地址</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">name</span>: <span class="string">&#x27;欧阳锋&#x27;</span>, <span class="comment">//该公司或个人的谁可以访问编辑或编辑作者，名称</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">www</span>: <span class="string">&#x27;example.com&#x27;</span>, <span class="comment">//以上公司或个人的家庭网站地址，</span></span></span><br><span class="line"><span class="language-javascript">            &#125;,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">feedback</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="comment">//反馈配置信息</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">url</span>: <span class="string">&#x27;https://example.com&#x27;</span>, <span class="comment">//单击“反馈和支持”菜单按钮时将打开的网站地址的绝对URL ，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">visible</span>: <span class="literal">false</span>, <span class="comment">//显示或隐藏“反馈和支持”菜单按钮，</span></span></span><br><span class="line"><span class="language-javascript">            &#125;,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">goback</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="comment">//定义“打开文件位置”菜单按钮和右上角按钮的设置。该对象具有以下参数：</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">blank</span>: <span class="literal">true</span>, <span class="comment">//在新的浏览器选项卡/窗口（如果值设置为true）或当前选项卡（如果值设置为false）中打开网站。默认值为true，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">requestClose</span>: <span class="literal">false</span>, <span class="comment">//定义如果单击“打开文件位置”按钮，则调用events.onRequestClose事件，而不是打开浏览器选项卡或窗口。默认值为false，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">text</span>: <span class="string">&#x27;Open file location&#x27;</span>, <span class="comment">//将在“打开文件位置”菜单按钮和右上角按钮（即，而不是“转到文档”）上显示的文本，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">url</span>: <span class="string">&#x27;https://example.com&#x27;</span>, <span class="comment">//单击“打开文件位置”菜单按钮时将打开的网站地址的绝对URL ，</span></span></span><br><span class="line"><span class="language-javascript">            &#125;,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">logo</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="attr">image</span>: <span class="string">&#x27;https://example.com/logo.png&#x27;</span>, <span class="comment">//图像文件的路径，用于在普通工作模式下显示（即，在所有编辑器的查看和编辑模式下）。图片必须具有以下尺寸：172x40，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">imageEmbedded</span>: <span class="string">&#x27;https://example.com/logo_em.png&#x27;</span>, <span class="comment">//用于以嵌入式模式显示的图像文件的路径（请参阅config部分以了解如何定义嵌入式文档类型）。图片必须具有以下尺寸：248x40，</span></span></span><br><span class="line"><span class="language-javascript">              <span class="attr">url</span>: <span class="string">&#x27;https://www.baidu.com&#x27;</span>, <span class="comment">//某人单击徽标图像时将使用的绝对URL（可用于转到您的网站等）。保留为空字符串或null以使徽标不可单击，</span></span></span><br><span class="line"><span class="language-javascript">            &#125;,</span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">user</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">//用户信息</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">id</span>: <span class="string">&#x27;admin&#x27;</span>, <span class="comment">//用户ID</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">name</span>: <span class="string">&#x27;操作员&#x27;</span>, <span class="comment">//用户全名称</span></span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">embedded</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">//Embedded部分仅适用于嵌入式文档类型（请参阅config部分以了解如何定义嵌入式文档类型）。它允许更改设置，这些设置定义嵌入式模式下按钮的行为。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">embedUrl</span>: <span class="string">&#x27;https://example.com/embedded?doc=exampledocument1.docx&#x27;</span>, <span class="comment">//定义文档的绝对URL，以作为嵌入到网页中的文档的源文件</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">fullscreenUrl</span>: <span class="string">&#x27;https://example.com/embedded?doc=exampledocument1.docx#fullscreen&#x27;</span>, <span class="comment">//定义将以全屏模式打开的文档的绝对URL。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">saveUrl</span>: <span class="string">&#x27;https://example.com/download?doc=exampledocument1.docx&#x27;</span>, <span class="comment">//定义允许将文档保存到用户个人计算机上的绝对URL。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">shareUrl</span>: <span class="string">&#x27;https://example.com/view?doc=exampledocument1.docx&#x27;</span>, <span class="comment">//定义允许其他用户共享此文档的绝对URL。</span></span></span><br><span class="line"><span class="language-javascript">            <span class="attr">toolbarDocked</span>: <span class="string">&#x27;top&#x27;</span>, <span class="comment">//定义嵌入式查看器工具栏的位置，可以为top或bottom。</span></span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">        &#125;,</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="attr">events</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="comment">//事件配置</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onAppReady,//-将应用程序加载到浏览器时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onCollaborativeChanges //-当文档由其他用户在严格共同编辑模式下共同编辑时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onDocumentReady,//-将应用程序加载到浏览器时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onDocumentStateChange,//-修改文档时调用的函数。这就是所谓的使用参数：&#123;真正的“数据”&#125;在当前用户编辑文档以及与参数：&#123;“数据”：假&#125;在当前用户的更改发送到文档编辑服务。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onDownloadAs,//-调用downloadAs方法时，使用指向已编辑文件的绝对URL调用的函数。在data参数中发送要下载的文档的绝对URL 。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onError,//-发生错误或其他特定事件时调用的函数。错误消息在data参数中发送。==</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onInfo,//-应用程序打开文件时调用的函数。该模式在data.mode参数中发送。可以查看或编辑。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onMetaChange,//-通过meta命令更改文档的元信息时调用的函数。文档名称通过data.title参数发送。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onOutdatedVersion,//-使用旧的document.key值打开文档进行编辑时，显示错误后调用的函数，该值用于编辑先前的文档版本并已成功保存。调用此事件时，必须使用新的document.key重新初始化编辑器。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onReady,//-将应用程序加载到浏览器时调用的函数。自从5.0版本不推荐使用，请使用onAppReady代替</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestClose,//-结束编辑器的工作并且必须关闭编辑器时调用的函数。==</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestCompareFile,//-用户尝试通过单击“存储中的文档”按钮来选择要比较的文档时调用的函数。要选择要比较的文档，必须调用setRevisedFile方法。如果未声明该方法，则不会显示“来自存储的文档”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestCreateNew,//-用户尝试通过单击“新建”按钮来创建文档时调用的函数。使用此方法代替createUrl字段。如果未声明该方法且未指定createUrl，则将不会显示“创建新”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestEditRights,//-用户尝试通过单击“编辑文档”按钮尝试将文档从视图切换到编辑模式时调用的函数。调用该函数时，必须在编辑模式下再次初始化编辑器。如果未声明该方法，则不会显示“编辑”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestHistory,//-用户尝试通过单击“版本历史记录”按钮显示文档版本历史记录时调用的函数。要显示文档版本历史，您必须调用refreshHistory方法。如果未声明该方法和onRequestHistoryData方法，则不会显示“版本历史记录”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestHistoryClose,//-当用户尝试通过单击“关闭历史记录”按钮来查看文档版本历史记录时，试图调用该文档时调用的函数。调用该函数时，必须在编辑模式下再次初始化编辑器。如果未声明该方法，则不会显示“关闭历史记录”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestHistoryData,//-用户尝试单击文档版本历史记录中的特定文档版本时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestInsertImage,//-用户尝试通过单击“保存图像”按钮插入图像时调用的函数。图像插入的类型在参数data.c中指定。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestRename,//-用户尝试通过单击“重命名...”按钮重命名文件时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestRestore,//-用户单击版本历史记录中的“还原”按钮来还原文件版本时调用的函数。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestSaveAs,//-用户尝试通过单击“另存为...”按钮保存文件时调用的函数。文档的标题和要下载的文档的绝对URL在data参数中发送。如果未声明该方法，则不会显示“另存为...”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestSharingSettings,//-用户单击“更改访问权限”按钮来管理文档访问权限时调用的函数。必须调用setSharingSettings方法来更新有关允许与其他用户共享文档的设置的信息。如果未声明该方法，则不会显示“更改访问权限”按钮。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onRequestUsers,//-评论者可以选择要在评论中提及的其他用户时调用的函数。要设置用户列表，必须调用setUsers方法。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// onWarning,//-发生警告时调用的函数。警告消息在data参数中发送。</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// &quot;onDocumentStateChange&quot;: function() &#123;</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// &#125;, //文档改变后的回调</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">//&quot;onDocumentReady&quot; : onDocumentReady, //文档初始化准备好后的回调</span></span></span><br><span class="line"><span class="language-javascript">        &#125;,</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> docEditor = <span class="keyword">new</span> <span class="title class_">DocsAPI</span>.<span class="title class_">DocEditor</span>(<span class="string">&#x27;preview&#x27;</span>, config)</span></span><br><span class="line"><span class="language-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><h2 id="核心配置项说明"><a href="#核心配置项说明" class="headerlink" title="核心配置项说明"></a>核心配置项说明</h2><h3 id="document（文档信息）"><a href="#document（文档信息）" class="headerlink" title="document（文档信息）"></a>document（文档信息）</h3><table><thead><tr><th>字段</th><th>说明</th></tr></thead><tbody><tr><td><code>documentType</code></td><td>文档类型：<code>text</code>（文档）&#x2F; <code>spreadsheet</code>（表格）&#x2F; <code>presentation</code>（演示文稿）</td></tr><tr><td><code>key</code></td><td>文档唯一标识，每次编辑保存后<strong>必须重新生成</strong>，否则会提示版本过期</td></tr><tr><td><code>title</code></td><td>文件名，下载文档时也会使用该名称</td></tr><tr><td><code>url</code></td><td>存储原始文档的绝对 URL，OnlyOffice 服务会从这里拉取文件</td></tr><tr><td><code>permissions</code></td><td>权限配置：<code>edit</code>（可否编辑）、<code>print</code>（可否打印）、<code>download</code>（可否下载）等</td></tr></tbody></table><h3 id="editorConfig（编辑配置）"><a href="#editorConfig（编辑配置）" class="headerlink" title="editorConfig（编辑配置）"></a>editorConfig（编辑配置）</h3><ul><li><code>mode</code>：<code>view</code>（只读视图）或 <code>edit</code>（编辑模式）</li><li><code>callbackUrl</code>：<strong>保存文件时的回调地址</strong>，用户点保存后 OnlyOffice 服务会把编辑后的文件回传给该地址，由后端接收并落库</li><li><code>lang</code>：界面语言，如 <code>zh-CN</code></li><li><code>customization</code>：界面定制（是否显示帮助、聊天、自动保存、徽标 logo 等）</li><li><code>user</code>：当前操作者信息（<code>id</code>、<code>name</code>），用于协同编辑时标识用户</li></ul><h3 id="events（事件回调）"><a href="#events（事件回调）" class="headerlink" title="events（事件回调）"></a>events（事件回调）</h3><p>常用事件：</p><ul><li><code>onAppReady</code>：编辑器加载完成</li><li><code>onDocumentReady</code>：文档就绪</li><li><code>onDocumentStateChange</code>：文档内容被修改（<code>data</code> 为 <code>true</code> 表示有未保存修改）</li><li><code>onError</code>：发生错误</li><li><code>onRequestSaveAs</code>：用户点击”另存为”</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听文档修改状态，用于提示用户是否保存</span></span><br><span class="line">docEditor.<span class="property">events</span>.<span class="title function_">on</span>(<span class="string">&#x27;documentStateChange&#x27;</span>, <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(e.<span class="property">data</span>); <span class="comment">// true = 有未保存的修改</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>key</code> 在每次保存后必须重新生成，否则再次打开会触发 <code>onOutdatedVersion</code> 错误提示，需用新的 <code>key</code> 重新初始化编辑器。</p></blockquote>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20251011oo/</id>
    <link href="https://blog.zhangsifan.com/posts/20251011oo/"/>
    <published>2025-10-10T16:00:00.000Z</published>
    <summary>OnlyOffice 前端使用教程：通过 JavaScript API 在网页中集成在线文档编辑器</summary>
    <title>OnlyOffice 前端使用教程</title>
    <updated>2025-10-10T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="我的世界" scheme="https://blog.zhangsifan.com/categories/%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C/"/>
    <category term="我的世界" scheme="https://blog.zhangsifan.com/tags/%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C/"/>
    <content>
      <![CDATA[<h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>在 Switch 版本中，任天堂出于安全因素考虑，屏蔽了使用 IP 地址连接自建服务器的功能，对 Switch 端的游玩造成了较大的不便。</p><h2 id="解决原理"><a href="#解决原理" class="headerlink" title="解决原理"></a>解决原理</h2><p>本文使用了基于<code>小米路由器自定义Hosts</code>的原理，通过将 Switch 版本中的默认联机服务器的 IP 地址劫持为目标服务器，实现了 Switch 版连接自建云服务器的功能。</p><p>具体流程：</p><ol><li>Switch 启动我的世界联机功能时，会向默认的几个服务器域名（如 <code>hivebedrock.network</code>）发起 DNS 解析请求</li><li>路由器上的自定义 Hosts 拦截这些请求，把域名解析到<strong>你自建服务器的 IP</strong></li><li>Switch 因此连接到了你的服务器，实现”以假乱真”的联机</li></ol><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ul><li>一台支持<strong>自定义 Hosts</strong> 的路由器（本文以小米路由器为例，其他路由器如 OpenWrt、梅林固件原理相同）</li><li>一台已搭建好的我的世界基岩版服务器（需对外开放端口，本文以局域网 <code>192.168.1.88</code> 为例）</li></ul><h2 id="修改路由器-Hosts"><a href="#修改路由器-Hosts" class="headerlink" title="修改路由器 Hosts"></a>修改路由器 Hosts</h2><p>进入小米路由器后台 →「高级设置」→「自定义 Hosts」，添加以下内容（将 <code>192.168.1.88</code> 替换为你的服务器 IP）：</p><figure class="highlight text"><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">192.168.1.88 hivebedrock.network</span><br><span class="line">192.168.1.88 mco.mineplex.com</span><br><span class="line">192.168.1.88 play.inpvp.net</span><br><span class="line">192.168.1.88 mco.lbsg.net</span><br><span class="line">192.168.1.88 mco.cubecraft.net</span><br></pre></td></tr></table></figure><h2 id="各域名对应的服务器"><a href="#各域名对应的服务器" class="headerlink" title="各域名对应的服务器"></a>各域名对应的服务器</h2><table><thead><tr><th>域名</th><th>说明</th></tr></thead><tbody><tr><td><code>hivebedrock.network</code></td><td>Hive 基岩服务器</td></tr><tr><td><code>mco.mineplex.com</code></td><td>Mineplex 基岩服务器</td></tr><tr><td><code>play.inpvp.net</code></td><td>InPvP 基岩服务器</td></tr><tr><td><code>mco.lbsg.net</code></td><td>Lifeboat 基岩服务器</td></tr><tr><td><code>mco.cubecraft.net</code></td><td>CubeCraft 基岩服务器</td></tr></tbody></table><h2 id="连接步骤"><a href="#连接步骤" class="headerlink" title="连接步骤"></a>连接步骤</h2><ol><li>保存并应用路由器 Hosts 配置</li><li>让 Switch 与路由器处于<strong>同一局域网</strong>（或路由器为 Switch 提供 DHCP）</li><li>打开 Switch 上的我的世界，进入「游戏」→「服务器」标签</li><li>任选一个默认服务器列表中的服务器点击连接，此时实际连接的将是你的自建服务器</li><li>若连接失败，检查服务器是否已开放端口、IP 是否正确</li></ol><blockquote><p>提示：该方法原理是 DNS 劫持，仅适用于当前局域网；如需远程连接，可考虑自建 DNS 服务或使用 VPN 组网。</p></blockquote>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20250925mc/</id>
    <link href="https://blog.zhangsifan.com/posts/20250925mc/"/>
    <published>2025-09-24T16:00:00.000Z</published>
    <summary>通过小米路由器自定义 Hosts 劫持，让 Nintendo Switch 版我的世界连接自建第三方服务器</summary>
    <title>Nintendo Switch 我的世界 连接第三方服务器</title>
    <updated>2025-09-24T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="Gitlab" scheme="https://blog.zhangsifan.com/categories/Gitlab/"/>
    <category term="Gitlab" scheme="https://blog.zhangsifan.com/tags/Gitlab/"/>
    <content>
      <![CDATA[<p><a href="https://docs.gitlab.com/">官方文档</a></p><p>GitLab CE（Community Edition）是开源的代码托管平台，支持 Git 仓库管理、CI&#x2F;CD、Issue 追踪等功能。本文介绍如何通过宝塔面板的 Docker 功能一键安装 GitLab CE，并配置域名、HTTPS 与邮件服务。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><h2 id="使用-宝塔面板-Docker-安装"><a href="#使用-宝塔面板-Docker-安装" class="headerlink" title="使用 宝塔面板 Docker 安装"></a>使用 宝塔面板 Docker 安装</h2><p>在宝塔面板 →「Docker」→「项目编排（Compose）」中创建项目，填写下面的 <code>docker-compose.yml</code> 与 <code>.env</code> 文件，一键启动即可。</p><p><img src="https://s2.loli.net/2025/09/05/eBzhS35rQpbatUl.png" alt="gitlab安装配置"></p><h2 id="完整docker-compose-yml"><a href="#完整docker-compose-yml" class="headerlink" title="完整docker-compose.yml"></a>完整docker-compose.yml</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">gitlab:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">gitlab/gitlab-ce:$&#123;VERSION&#125;</span></span><br><span class="line">    <span class="comment">#    container_name: $&#123;CONTAINER_NAME&#125;</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">        <span class="attr">limits:</span></span><br><span class="line">          <span class="attr">cpus:</span> <span class="string">$&#123;CPUS&#125;</span></span><br><span class="line">          <span class="attr">memory:</span> <span class="string">$&#123;MEMORY_LIMIT&#125;</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">$&#123;DOMAIN_HOST&#125;</span></span><br><span class="line">    <span class="attr">shm_size:</span> <span class="string">&#x27;256m&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;HOST_IP&#125;:$&#123;WEB_HTTP_PORT&#125;:80</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;HOST_IP&#125;:$&#123;WEB_HTTPS_PORT&#125;:443</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;HOST_IP&#125;:$&#123;WEB_SSH_PORT&#125;:22</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">GITLAB_OMNIBUS_CONFIG:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        # Add any other gitlab.rb configuration here, each on its own line</span></span><br><span class="line"><span class="string">        external_url &#x27;https://$&#123;DOMAIN_HOST&#125;&#x27;</span></span><br><span class="line"><span class="string">        nginx[&#x27;listen_port&#x27;] = 80</span></span><br><span class="line"><span class="string">        nginx[&#x27;listen_https&#x27;] = false</span></span><br><span class="line"><span class="string">        nginx[&#x27;proxy_set_headers&#x27;] = &#123; &quot;X-Forwarded-Proto&quot; =&gt; &quot;https&quot;, &quot;X-Forwarded-Ssl&quot; =&gt; &quot;on&quot; &#125;</span></span><br><span class="line"><span class="string">        nginx[&#x27;client_max_body_size&#x27;] = &#x27;0&#x27;</span></span><br><span class="line"><span class="string">        nginx[&#x27;proxy_read_timeout&#x27;] = 600</span></span><br><span class="line"><span class="string">        nginx[&#x27;proxy_connect_timeout&#x27;] = 600</span></span><br><span class="line"><span class="string">        nginx[&#x27;proxy_send_timeout&#x27;] = 600</span></span><br><span class="line"><span class="string">        nginx[&#x27;gzip_enabled&#x27;] = false</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_enable&#x27;] = true</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_address&#x27;] = &quot;smtp.ym.163.com&quot;</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_port&#x27;] = 465</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_user_name&#x27;] = &quot;robot@domain.com&quot;</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_password&#x27;] = &quot;PASSWORD&quot;</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_domain&#x27;] = &quot;smtp.ym.163.com&quot;</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_authentication&#x27;] = &quot;login&quot;</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_enable_starttls_auto&#x27;] = false</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;smtp_tls&#x27;] = true</span></span><br><span class="line"><span class="string">        gitlab_rails[&#x27;gitlab_email_from&#x27;] = &#x27;robot@domain.com&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;APP_PATH&#125;/config:/etc/gitlab</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;APP_PATH&#125;/logs:/var/log/gitlab</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;APP_PATH&#125;/data:/var/opt/gitlab</span></span><br><span class="line">    <span class="attr">labels:</span></span><br><span class="line">      <span class="attr">createdBy:</span> <span class="string">&quot;bt_apps&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">baota_net</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">baota_net:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="完整-env"><a href="#完整-env" class="headerlink" title="完整.env"></a>完整.env</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">VERSION=latest</span><br><span class="line">CONTAINER_NAME=CONTAINER_NAME</span><br><span class="line">HOST_IP=127.0.0.1</span><br><span class="line">WEB_HTTP_PORT=10080</span><br><span class="line">WEB_HTTPS_PORT=10443</span><br><span class="line">WEB_SSH_PORT=10022</span><br><span class="line">DOMAIN_HOST=git.domain.com</span><br><span class="line">CPUS=0</span><br><span class="line">MEMORY_LIMIT=0MB</span><br><span class="line">APP_PATH=/www/dk_project/dk_app/gitlab/gitlab</span><br></pre></td></tr></table></figure><h2 id="配置说明"><a href="#配置说明" class="headerlink" title="配置说明"></a>配置说明</h2><ul><li><strong><code>external_url</code></strong>：GitLab 对外访问地址，配置为你的域名（如 <code>https://git.domain.com</code>），配合反向代理实现 HTTPS</li><li><strong><code>GITLAB_OMNIBUS_CONFIG</code></strong>：GitLab 的 omnibus 配置入口，所有 <code>gitlab.rb</code> 的配置项都可以写在这里，如 SMTP 邮件、超时时间、上传大小限制等</li><li><strong><code>.env</code> 文件</strong>：<code>WEB_HTTP_PORT</code> &#x2F; <code>WEB_HTTPS_PORT</code> &#x2F; <code>WEB_SSH_PORT</code> 为宿主机映射端口，<code>APP_PATH</code> 为数据持久化目录，升级或迁移时保留该目录即可</li><li><strong>首次登录</strong>：浏览器访问 <code>https://git.domain.com</code>，使用管理员账号 <code>root</code>，初始密码存放在容器 <code>/etc/gitlab/initial_root_password</code> 文件中，也可用 <code>docker exec gitlab cat /etc/gitlab/initial_root_password</code> 查看</li></ul><h2 id="邮件服务配置"><a href="#邮件服务配置" class="headerlink" title="邮件服务配置"></a>邮件服务配置</h2><p><a href="https://docs.gitlab.com/omnibus/settings/smtp/">官方文档</a></p><p>在docker-compose.yml 中GITLAB_OMNIBUS_CONFIG 修改</p><h2 id="网易免费企业邮箱"><a href="#网易免费企业邮箱" class="headerlink" title="网易免费企业邮箱"></a>网易免费企业邮箱</h2><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">gitlab_rails[<span class="string">&#x27;smtp_enable&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_address&#x27;</span>] = <span class="string">&quot;smtp.ym.163.com&quot;</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_port&#x27;</span>] = <span class="number">465</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_user_name&#x27;</span>] = <span class="string">&quot;xxxx@xx.com&quot;</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_password&#x27;</span>] = <span class="string">&quot;password&quot;</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_authentication&#x27;</span>] = <span class="string">&quot;login&quot;</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_enable_starttls_auto&#x27;</span>] = <span class="literal">false</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_tls&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;gitlab_email_from&#x27;</span>] = <span class="string">&#x27;xxxx@xx.com&#x27;</span></span><br><span class="line">gitlab_rails[<span class="string">&#x27;smtp_domain&#x27;</span>] = <span class="string">&quot;smtp.ym.163.com&quot;</span></span><br></pre></td></tr></table></figure><h2 id="测试-SMTP-配置"><a href="#测试-SMTP-配置" class="headerlink" title="测试 SMTP 配置"></a>测试 SMTP 配置</h2><p>您可以使用 Rails 控制台验证 GitLab 是否能够正常发送电子邮件。在 GitLab 服务器上，执行<code>gitlab-rails console</code>进入控制台。然后，您可以在控制台提示符下输入以下命令，让 GitLab 发送一封测试电子邮件：</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Notify</span>.test_email(<span class="string">&#x27;destination_email@address.com&#x27;</span>, <span class="string">&#x27;Message Subject&#x27;</span>, <span class="string">&#x27;Message Body&#x27;</span>).deliver_now</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20250905gl/</id>
    <link href="https://blog.zhangsifan.com/posts/20250905gl/"/>
    <published>2025-09-03T16:00:00.000Z</published>
    <summary>使用宝塔面板 Docker 安装 Gitlab CE 服务器</summary>
    <title>搭建 Gitlab CE 服务器</title>
    <updated>2025-09-03T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="OBS" scheme="https://blog.zhangsifan.com/categories/OBS/"/>
    <category term="OBS RTMP 服务器" scheme="https://blog.zhangsifan.com/tags/OBS-RTMP-%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    <content>
      <![CDATA[<p>RTMP（Real-Time Messaging Protocol）是目前直播推流最常用的协议，OBS、FFmpeg 等工具都支持向 RTMP 服务器推流。本文介绍如何使用开源的 <code>smart_rtmpd</code> 快速搭建一个<strong>本地 RTMP 推流服务器</strong>，配合 OBS 即可实现多端直播推流。</p><blockquote><p><a href="https://github.com/superconvert/smart_rtmpd?tab=readme-ov-file">官方网站</a></p></blockquote><h2 id="下载服务端软件"><a href="#下载服务端软件" class="headerlink" title="下载服务端软件"></a>下载服务端软件</h2><blockquote><p>下载 <code>rtmpd.zip</code> 文件</p></blockquote><p><a href="https://github.com/superconvert/smart_rtmpd/releases">rtmpd.zip</a></p><p><code>smart_rtmpd</code> 是一个跨平台（Windows &#x2F; Linux &#x2F; Mac）的 RTMP 流媒体服务器，支持：</p><ul><li>直播流推流与转发</li><li>HLS &#x2F; HTTP-FLV 拉流</li><li>按需转码与录制</li><li>简单易用的 Web 管理界面</li></ul><h2 id="解压运行"><a href="#解压运行" class="headerlink" title="解压运行"></a>解压运行</h2><ul><li>解压 <code>rtmpd.zip</code> 文件<ul><li>然后解压 <code>smart_rtmpd_win.zip</code> 文件夹</li><li>运行 <code>smart_rtmpd.exe</code> 重新</li><li>点击 启动 即可</li></ul></li></ul><p>启动后服务默认监听 <code>1935</code> 端口（RTMP 标准端口），Web 管理界面默认端口为 <code>8080</code>（以程序提示为准），通过浏览器访问即可管理推流与拉流地址。</p><h2 id="OBS-推流配置"><a href="#OBS-推流配置" class="headerlink" title="OBS 推流配置"></a>OBS 推流配置</h2><p>在 OBS 中配置推流地址，即可向本机 RTMP 服务器推流：</p><ol><li>打开 OBS，点击「设置」→「直播」</li><li>服务类型选择<strong>自定义</strong></li><li>服务器地址填写：<code>rtmp://127.0.0.1:1935/live</code></li><li>串流密钥填写一个自定义的名称，如：<code>test</code>（作为流的标识）</li><li>点击「开始推流」</li></ol><p>推流完成后，可通过以下地址拉流验证：</p><ul><li><strong>HTTP-FLV</strong>：<code>http://127.0.0.1:8080/live/test.flv</code></li><li><strong>HLS</strong>：<code>http://127.0.0.1:8080/live/test.m3u8</code></li></ul><blockquote><p>提示：如需将流推送到公网或其他设备观看，需要把服务器部署到公网主机，并在防火墙开放 <code>1935</code> 与 <code>8080</code> 端口。</p></blockquote>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20250707mp/</id>
    <link href="https://blog.zhangsifan.com/posts/20250707mp/"/>
    <published>2025-07-06T16:00:00.000Z</published>
    <summary>使用 smart_rtmpd 搭建 RTMP 推流服务器，配合 OBS 实现本地多端推流直播</summary>
    <title>搭建 RTMP 推流服务</title>
    <updated>2025-07-06T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="OBS" scheme="https://blog.zhangsifan.com/categories/OBS/"/>
    <category term="OBS 插件" scheme="https://blog.zhangsifan.com/tags/OBS-%E6%8F%92%E4%BB%B6/"/>
    <content>
      <![CDATA[<p><a href="https://sorayuki.github.io/obs-multi-rtmp/">官方网站</a></p><h2 id="OBS-Studio-下载"><a href="#OBS-Studio-下载" class="headerlink" title="OBS Studio 下载"></a>OBS Studio 下载</h2><p><a href="https://obsproject.com/download">Download | OBS</a></p><h2 id="OBS-多流推送插件下载"><a href="#OBS-多流推送插件下载" class="headerlink" title="OBS 多流推送插件下载"></a>OBS 多流推送插件下载</h2><p><a href="https://github.com/sorayuki/obs-multi-rtmp/releases">下载地址</a></p><h2 id="Windows-安装方式"><a href="#Windows-安装方式" class="headerlink" title="Windows 安装方式"></a>Windows 安装方式</h2><h3 id="下载插件"><a href="#下载插件" class="headerlink" title="下载插件"></a>下载插件</h3><p>下载 <code>obs-multi-rtmp-x.x.x.x-windows-x64.zip</code> 文件</p><h3 id="解压安装插件"><a href="#解压安装插件" class="headerlink" title="解压安装插件"></a>解压安装插件</h3><p>将 <code>data</code> 中的文件放到 OBS 安装目录的 <code>data</code> 文件夹中<br>将 <code>obs-plugins</code> 中的文件放到 OBS 安装目录的 <code>obs-plugins</code> 文件夹中</p><h2 id="Mac-OS-安装方式"><a href="#Mac-OS-安装方式" class="headerlink" title="Mac OS 安装方式"></a>Mac OS 安装方式</h2><h3 id="下载插件-1"><a href="#下载插件-1" class="headerlink" title="下载插件"></a>下载插件</h3><p>下载 <code>obs-multi-rtmp-x.x.x.x-macos-universal.pkg</code> 文件</p><h3 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h3><p>双击下载的文件安装即可 </p><h2 id="使用插件"><a href="#使用插件" class="headerlink" title="使用插件"></a>使用插件</h2><p>重新启动 OBS Studio 即可在 停靠窗口 -&gt; 多路推流 使用</p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><iframe src='https://sorayuki.github.io/obs-multi-rtmp/' style="width:100%;height:1000px;" frameborder="0"></iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20250707zb/</id>
    <link href="https://blog.zhangsifan.com/posts/20250707zb/"/>
    <published>2025-07-06T16:00:00.000Z</published>
    <summary>OBS多站点并发插件</summary>
    <title>OBS 多流推送</title>
    <updated>2025-07-06T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="我的世界" scheme="https://blog.zhangsifan.com/categories/%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C/"/>
    <category term="我的世界" scheme="https://blog.zhangsifan.com/tags/%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C/"/>
    <content>
      <![CDATA[<h2 id="下载服务器文件"><a href="#下载服务器文件" class="headerlink" title="下载服务器文件"></a>下载服务器文件</h2><ul><li>服务器系统 Ubuntu 22+</li><li>我们建议在至少具有 2 个内核和 1 Gb RAM 的 64 位 Intel 或 AMD 处理器机器上运行 Bedrock Minecraft 服务器。</li></ul><p>点击<a href="https://www.minecraft.net/en-us/download/server/bedrock">下载基岩版服务器文件</a></p><p>下载后上传至服务器 并解压文件</p><h2 id="宝塔服务器设置"><a href="#宝塔服务器设置" class="headerlink" title="宝塔服务器设置"></a>宝塔服务器设置</h2><p><img src="https://s2.loli.net/2025/04/24/byhiHTmlskXB2AN.png" alt="宝塔面板设置页面"></p><h2 id="服务器防火墙"><a href="#服务器防火墙" class="headerlink" title="服务器防火墙"></a>服务器防火墙</h2><h2 id="阿里云服务器防火墙设置"><a href="#阿里云服务器防火墙设置" class="headerlink" title="阿里云服务器防火墙设置"></a>阿里云服务器防火墙设置</h2><blockquote><p>记得 协议类型 选择全部流量!!!</p></blockquote><p><img src="https://s2.loli.net/2025/04/24/DTYAGhSljMirq3u.png" alt="阿里云防火墙设置.png"></p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>服务器将尝试读取名为 <code>server.properties</code> 的文件。其中一些选项仅在创建新世界时读取，而另一些选项则在每次启动时读取。该文件应包含一个列表，其中的键和值以等号分隔，每行一个。</p><p>有以下选项可用。如果值是括号中的数字，则可以使用该数字代替文本值。</p><table><thead><tr><th>Option name</th><th>Possible values</th><th>Default value</th><th>When is it used</th><th>Notes</th></tr></thead><tbody><tr><td>server-name</td><td>Any string (no semicolon allowed)</td><td>Dedicated Server</td><td>Always</td><td>这是游戏内服务器列表中显示的服务器名称。list.</td></tr><tr><td>gamemode</td><td>survival, creative, adventure</td><td>survival</td><td>Always or only for new players</td><td></td></tr><tr><td>force-gamemode</td><td>true, false</td><td>false</td><td>Always</td><td><strong>force-gamemode&#x3D;false</strong>（或 server.properties 文件中<strong>未定义 force-gamemode</strong>）可防止服务器向客户端发送除服务器在世界创建期间保存的 gamemode 值之外的 gamemodes 值，即使这些值是在世界创建后在 server.propertie 文件中设置的。force gamemode&#x3D;true强制服务器向客户端发送gamemode值，而不是服务器在世界创建期间保存的gamemode值（如果这些值在世界创建后在server.properties文件中设置）。</td></tr><tr><td>difficulty</td><td>peaceful, easy, normal, hard</td><td>easy</td><td>Always</td><td></td></tr><tr><td>allow-cheats</td><td>true, false</td><td>false</td><td>Always</td><td>如果为<strong>true</strong>，则可以使用命令等作弊手段。</td></tr><tr><td>max-players</td><td>Any integer</td><td>10</td><td>Always</td><td>The maximum numbers of players that should be able to play on the server. <strong>Higher values have performance impact.</strong></td></tr><tr><td>server-port</td><td>Integer between 1024 and 65535</td><td>19132</td><td>Always</td><td>可以使用低于1024的值，但通常保留用于众所周知的应用</td></tr><tr><td>server-portv6</td><td>Integer between 1024 and 65535</td><td>19133</td><td>Always</td><td>可以使用低于1024的值，但通常保留用于众所周知的应用</td></tr><tr><td>enable-lan-visibility</td><td>true, false</td><td>true</td><td>Always</td><td>倾听并回应在局域网上寻找服务器的客户端。这将导致服务器绑定到默认端口（19132、19133），即使“服务器端口”和“server-portv6”具有非默认值。如果不需要LAN发现，或者在同一主机上运行多个服务器可能导致端口冲突，请考虑关闭此功能。</td></tr><tr><td>level-name</td><td>Any string</td><td>Bedrock level</td><td>Always</td><td>The name of level to be used&#x2F;generated. Each level has its own folder in <code>/worlds</code>.</td></tr><tr><td>level-seed</td><td>Any string</td><td></td><td>World creation</td><td>The seed to be used for randomizing the world. If left empty a seed will be chosen at random.</td></tr><tr><td>online-mode</td><td>true, false</td><td>true</td><td>Always</td><td>If true, all connected players must be authenticated with Xbox Live. Clients connecting to remote (non-LAN) servers will always require Xbox Live authentication regardless of this setting. If the server accepts connections from the Internet, then it is <strong>highly</strong> recommended to enable online-mode.</td></tr><tr><td>allow-list</td><td>true, false</td><td>false</td><td>Always</td><td>If true then all connected players must be listed in the separate <code>allowlist.json</code> file. See the <em>Allowlist</em> section.</td></tr><tr><td>view-distance</td><td>Any integer greater than 5</td><td>32</td><td>Always</td><td>The maximum allowed view distance. <strong>Higher values have performance impact.</strong></td></tr><tr><td>player-idle-timeout</td><td>Any positive integer, including 0</td><td>30</td><td>Always</td><td>After a player has idled for this many minutes they will be kicked. If set to 0 then players can idle indefinitely.</td></tr><tr><td>max-threads</td><td>Any integer</td><td>8</td><td>Always</td><td>Maximum number of threads the server will try to use. If set to 0 or removed then it will use as many as possible.</td></tr><tr><td>tick-distance</td><td>An integer in the range [4, 12]</td><td>4</td><td>Always</td><td>The world will be ticked this many chunks away from any player. <strong>Higher values have performance impact.</strong></td></tr><tr><td>default-player-permission-level</td><td>visitor, member, operator</td><td>member</td><td>Always</td><td>Which permission level new players will have when they join for the first time.</td></tr><tr><td>texturepack-required</td><td>true, false</td><td>false</td><td>Always</td><td>If the world uses any specific texture packs then this setting will force the client to use it.</td></tr><tr><td>content-log-file-enabled</td><td>true, false</td><td>false</td><td>Always</td><td>Enables logging content errors to a file.</td></tr><tr><td>compression-threshold</td><td>An integer in the range [0-65535]</td><td>1</td><td>Always</td><td>Determines the smallest size of raw network payload to compress. Can be used to experiment with CPU-bandwidth tradeoffs.</td></tr><tr><td>compression-algorithm</td><td>zlib, snappy</td><td>zlib</td><td>Always</td><td>Determines the compression algorithm to use for networking.</td></tr><tr><td>server-authoritative-movement-strict</td><td>true, false</td><td>false</td><td>Always</td><td>If true, will be more strict toward the Player position and be less permissive in accepting the client info. This will impact Player around moving block if there is high latency.</td></tr><tr><td>server-authoritative-dismount-strict</td><td>true, false</td><td>false</td><td>Always</td><td>If true, will be more strict toward the Player dismount position. This means clients will receive a correction on their dismount position in higher latency situation.</td></tr><tr><td>server-authoritative-entity-interactions-strict</td><td>true, false</td><td>false</td><td>Always</td><td>If true, will be more strict toward the Entity interactions. This will impact Players interacting with each other in higher latency situations.</td></tr><tr><td>player-position-acceptance-threshold</td><td>Any positive float</td><td>0.5</td><td>Always</td><td>This is the tolerance of discrepancies between the Client and Server Player position. This helps in problematic scenarios. The higher the number, the more tolerant the server will be before asking for a correction. Passed value of 1.0, the chance of missing cheating increases.</td></tr><tr><td>player-movement-action-direction-threshold</td><td>Any positive float in the range of [-1.00, 1.00]</td><td>0.85</td><td>Always</td><td>The amount that the direction the player is attacking can differ from the direction the player is looking as cos(x) where x is the angle between the two vectors. A value of 1 means the two vectors must be parallel, 0 means anything in front of the player, and -1 means any vector.</td></tr><tr><td>server-authoritative-block-breaking</td><td>true, false</td><td>false</td><td>Not when client-auth</td><td>If true, the server will compute block mining operations in sync with the client so it can verify that the client should be able to break blocks when it thinks it can. This setting cannot be combined with client authoritative movement and will be disabled if that setting is enabled.</td></tr><tr><td>server-authoritative-block-breaking-pick-range-scalar</td><td>Any float above 1.0</td><td>1.5</td><td>When server-authoritative-block-breaking is true</td><td>This increase the range of block breaking. This is squared and multiplied with the default range.</td></tr><tr><td>chat-restriction</td><td>None, Dropped, Disabled</td><td>None</td><td>Always</td><td>This represents the level of restriction applied to the chat for each player that joins the server. “None” is the default and represents regular free chat. “Dropped” means the chat messages are dropped and never sent to any client. Players receive a message to let them know the feature is disabled. “Disabled” means that unless the player is an operator, the chat UI does not even appear. No information is displayed to the player.</td></tr><tr><td>disable-player-interaction</td><td>true, false</td><td>false</td><td>Always</td><td>If true, the server will inform clients that they should ignore other players when interacting with the world. This is not server authoritative.</td></tr><tr><td>client-side-chunk-generation-enabled</td><td>true, false</td><td>true</td><td>Always</td><td>If true, the server will inform clients that they have the ability to generate visual level chunks outside of player interaction distances.</td></tr><tr><td>block-network-ids-are-hashes</td><td>true, false</td><td>true</td><td>Always</td><td>If true, the server will send hashed block network ID’s instead of id’s that start from 0 and go up. These id’s are stable and won’t change regardless of other block changes.</td></tr><tr><td>disable-persona</td><td>true, false</td><td>false</td><td>Internal Use Only</td><td></td></tr><tr><td>disable-custom-skins</td><td>true, false</td><td>false</td><td>Always</td><td>If true, disable players customized skins that were customized outside of the Minecraft store assets or in game assets. This is used to disable possibly offensive custom skins players make.</td></tr><tr><td>server-build-radius-ratio</td><td>Disabled, 0.0-1.0</td><td>Disabled</td><td>Always</td><td>If “Disabled” the server will dynamically calculate how much of the player’s view it will generate, assigning the rest to the client to build. Otherwise from the overridden ratio tell the server how much of the player’s view to generate, disregarding client hardware capability. <strong>Only valid if client-side-chunk-generation-enabled is enabled.</strong></td></tr></tbody></table><h2 id="文件夹"><a href="#文件夹" class="headerlink" title="文件夹"></a>文件夹</h2><p>解压后，您将看到几个文件夹和一个二进制可执行文件。首次启动服务器时，将创建一堆新的（空）文件夹。您需要关注的文件夹如下：</p><table><thead><tr><th>Folder name</th><th>Purpose</th></tr></thead><tbody><tr><td>behavior_packs</td><td>这里可以安装新的行为包。目前还无法在关卡中激活它们。</td></tr><tr><td>resource_packs</td><td>这里可以安装新的资源包。目前无法在关卡中激活它们。</td></tr><tr><td>worlds</td><td>如果此文件夹不存在，则会在启动时创建。每个创建的世界都会有一个根据其文件<code>level-name</code>内容命名的文件夹<code>server.properties</code>。</td></tr></tbody></table><h2 id="允许列表"><a href="#允许列表" class="headerlink" title="允许列表"></a>允许列表</h2><p>如果<code>allow-list</code>启用了此属性，<code>server.properties</code>则服务器将仅允许选定的用户连接。要允许用户连接，您需要知道他们的 Xbox Live 玩家代号。将用户添加到允许列表的最简单方法是使用命令<code>allowlist add &lt;Gamertag&gt;</code>（例如：）<code>allowlist add ExampleName</code>。注意：如果玩家代号中有空格，则需要用双引号将其括起来：<code>allowlist add &quot;Example Name&quot;</code></p><p>如果您稍后想从列表中删除某人，您可以使用命令<code>allowlist remove &lt;Gamertag&gt;</code>。</p><p>允许列表将保存在名为 的文件中<code>allowlist.json</code>。如果您想自动执行从中添加或删除玩家的过程，您可以这样做。修改文件后，您需要运行该命令<code>allowlist reload</code>以确保服务器知道您的新更改。</p><p>注意：此文件之前名为<code>whitelist.json</code>。为了向后兼容，如果<code>whitelist.json</code>存在其他文件，则将使用该文件代替<code>allowlist.json</code>。要迁移，请删除默认<code>allowlist.json</code>文件，重命名<code>whitelist.json</code>为<code>allowlist.json</code>，然后重启服务器。</p><p>该文件包含一个 JSON 数组，其中的对象包含以下键&#x2F;值。</p><table><thead><tr><th>Key</th><th>Type</th><th>Value</th></tr></thead><tbody><tr><td>name</td><td>String</td><td>用户的玩家代号。</td></tr><tr><td>xuid</td><td>String</td><td>可选。用户的 XUID。如果未设置，则当名称匹配的用户连接时，系统会填充该值。</td></tr><tr><td>ignoresPlayerLimit</td><td>Boolean</td><td>如果此用户不应计入最大玩家限制，则为 True。目前，即使玩家使用此选项，连接玩家数量仍存在另一个软限制，即 30 人（或比指定的最大玩家数量多 1 人）。此限制的目的是即使服务器已满，也能让一些玩家加入。</td></tr></tbody></table><p>示例 <code>allowlist.json</code> 文件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;ignoresPlayerLimit&quot;: false,</span><br><span class="line">        &quot;name&quot;: &quot;MyPlayer&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;ignoresPlayerLimit&quot;: false,</span><br><span class="line">        &quot;name&quot;: &quot;AnotherPlayer&quot;,</span><br><span class="line">        &quot;xuid&quot;: &quot;274817248&quot;</span><br><span class="line">    &#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="权限"><a href="#权限" class="headerlink" title="权限"></a>权限</h2><p><code>permissions.json</code>您可以通过在与服务器可执行文件位于同一目录中的 中为玩家分配角色来调整玩家的特定权限。该文件包含一个带有 XUID 和权限的简单 JSON 对象。有效权限为： <code>operator</code>、<code>member</code>、<code>visitor</code>每个使用这些帐户连接的玩家都将根据设置的权限进行处理。如果您在服务器运行时更 ​​ 改此文件，请运行该命令<code>permission reload</code>以确保服务器知道您的新更改。您还可以使用 列出当前权限<code>permission list</code>。请注意，<code>online-mode</code>需要启用 才能使此功能正常工作，因为 xuid 需要对用户帐户进行在线验证。如果不在该列表中的新玩家连接，<code>default-player-permission-level</code> 则将应用该选项。</p><p>示例<code>permissions.json</code>文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;permission&quot;: &quot;operator&quot;,</span><br><span class="line">        &quot;xuid&quot;: &quot;451298348&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;permission&quot;: &quot;member&quot;,</span><br><span class="line">        &quot;xuid&quot;: &quot;52819329&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        &quot;permission&quot;: &quot;visitor&quot;,</span><br><span class="line">        &quot;xuid&quot;: &quot;234114123&quot;</span><br><span class="line">    &#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="崩溃报告"><a href="#崩溃报告" class="headerlink" title="崩溃报告"></a>崩溃报告</h2><p>如果服务器崩溃，它会自动向我们发送各种信息，帮助我们解决将来的问题。</p><h2 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h2><p>您可以通过在控制台中输入命令来向服务器发出命令。以下命令可用。&lt; &gt; 表示参数为必需参数，[ ] 表示参数为可选参数，| 表示不同的允许值。如果字符串包含空格，则可以用双引号 “ 括起来。</p><table><thead><tr><th>Command syntax</th><th>Description</th></tr></thead><tbody><tr><td>kick &lt; player name or xuid &gt; &lt; reason &gt;</td><td>立即踢出一名玩家。踢出原因将显示在被踢出玩家的屏幕上。</td></tr><tr><td>stop</td><td>正常关闭服务器。</td></tr></tbody></table><h2 id="备份"><a href="#备份" class="headerlink" title="备份"></a>备份</h2><p>服务器支持在运行时备份世界文件。手动备份不太方便，但自动备份效果更好。从服务器角度来看，备份包含三个命令。</p><table><thead><tr><th>Command</th><th>Description</th></tr></thead><tbody><tr><td>save hold</td><td>这将要求服务器准备备份。它是异步的，并将立即返回。</td></tr><tr><td>save query</td><td>调用此命令后，<code>save hold</code> 您应该反复调用该命令，以查看准备工作是否完成。如果返回成功，它将返回一个文件列表（包含每个文件的长度），其中包含您需要复制的文件。服务器在执行此操作时不会暂停，因此某些文件可能会在备份过程中被修改。只要您只复制给定文件列表中的文件，并将复制的文件截断为指定的长度，备份就应该是有效的。</td></tr><tr><td>save resume</td><td>当您完成文件复制后，您应该调用它来告诉服务器可以再次删除旧文件。</td></tr></tbody></table>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20250424mc/</id>
    <link href="https://blog.zhangsifan.com/posts/20250424mc/"/>
    <published>2025-04-23T16:00:00.000Z</published>
    <summary>使用Ubuntu Linux搭建Minecraft基岩(Bedrock)服务器</summary>
    <title>零基础搭建Minecraft基岩(Bedrock)服务器</title>
    <updated>2025-04-23T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="开发工具" scheme="https://blog.zhangsifan.com/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    <category term="PageSpy" scheme="https://blog.zhangsifan.com/tags/PageSpy/"/>
    <content>
      <![CDATA[<p><a href="https://www.pagespy.org/#/">官方网站</a></p><h2 id="什么是-PageSpy？"><a href="#什么是-PageSpy？" class="headerlink" title="什么是 PageSpy？"></a>什么是 PageSpy？</h2><p>PageSpy 是一款兼容 Web &#x2F; 小程序 &#x2F; React Native &#x2F; 鸿蒙 App 等平台项目的开源调试平台。基于对原生 API 的封装，它将调用原生方法时的参数进行过滤、转化，整理成一定格式的消息供调试端消费；调试端收到消息数据后，提供类似本地控制台的功能界面将数据呈现出来。</p><h2 id="为什么是-PageSpy"><a href="#为什么是-PageSpy" class="headerlink" title="为什么是 PageSpy?"></a>为什么是 PageSpy?</h2><blockquote><p>一图胜千言。</p></blockquote><p><img src="https://www.pagespy.org/assets/why-is-pagespy-zh-C1-Ci1jQ.png" alt="为什么选择 PageSpy 示意图"></p><h2 id="何时使用？"><a href="#何时使用？" class="headerlink" title="何时使用？"></a>何时使用？</h2><p>任何无法在本地使用控制台调试的场景，都是 PageSpy 可以大显身手的时候！ 一起来看下面的几个场景案例：</p><ul><li>本地调试 H5、Webview 应用：以往有些产品提供了可以在 H5 上查看信息的面板，但移动端屏幕太小操作不便、显示不友好，以及信息被截断等问题；</li><li>远程办公、跨地区协同：传统沟通方式如邮件、电话、视频会议等，沟通效率不高、故障信息不全面，容易误解误判；</li><li>用户终端上出现白屏问题：传统定位问题的方式包括数据监控、日志分析等，这些方式依赖排障人员要理解业务需求场景、技术实现；</li></ul><p>PageSpy 的目标，就是为包括以上场景的人员提供帮助。</p><h2 id="服务部署"><a href="#服务部署" class="headerlink" title="服务部署"></a>服务部署</h2><h2 id="使用-Docker-部署"><a href="#使用-Docker-部署" class="headerlink" title="使用 Docker 部署"></a>使用 Docker 部署</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">docker run -d --restart=always -v ./log:/app/log -v ./data:/app/data -p 6752:6752 --name=<span class="string">&quot;pageSpy&quot;</span> ghcr.io/huolalatech/page-spy-web:latest</span><br></pre></td></tr></table></figure><p>执行完成后，打开浏览器访问 <a href="http://localhost:6752/">http://localhost:6752</a> 即可访问服务。</p><h2 id="视频学习"><a href="#视频学习" class="headerlink" title="视频学习"></a>视频学习</h2><iframe width="100%" height="550px" src="https://player.bilibili.com/player.html?isOutside=true&amp;aid=658778004&amp;bvid=BV1Ph4y1y78R&amp;cid=1209124922&amp;p=1" scrolling="no" allowfullscreen=""></iframe><h2 id="客户端部署"><a href="#客户端部署" class="headerlink" title="客户端部署"></a>客户端部署</h2><h2 id="浏览器"><a href="#浏览器" class="headerlink" title="浏览器"></a>浏览器</h2><h3 id="导入-SDK"><a href="#导入-SDK" class="headerlink" title="导入 SDK"></a>导入 SDK</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- <span class="title class_">PageSpy</span> <span class="variable constant_">SDK</span> --&gt;</span><br><span class="line">&lt;script crossorigin=&quot;anonymous&quot; src=&quot;https://&lt;your-pagespy-host&gt;/page-spy/index.min.js&quot;&gt;&lt;/script&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- 插件（非必须，但建议使用） --&gt;</span><br><span class="line">&lt;script crossorigin=&quot;anonymous&quot; src=&quot;https://&lt;your-pagespy-host&gt;/plugin/data-harbor/index.min.js&quot;&gt;&lt;/script&gt;</span><br><span class="line">&lt;script crossorigin=&quot;anonymous&quot; src=&quot;https://&lt;your-pagespy-host&gt;/plugin/rrweb/index.min.js&quot;&gt;&lt;/script&gt;</span><br></pre></td></tr></table></figure><h3 id="初始化-PageSpy-和插件"><a href="#初始化-PageSpy-和插件" class="headerlink" title="初始化 PageSpy 和插件"></a>初始化 PageSpy 和插件</h3><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">$harbor</span> = <span class="keyword">new</span> <span class="title class_">DataHarborPlugin</span>();</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">$rrweb</span> = <span class="keyword">new</span> <span class="title class_">RRWebPlugin</span>();</span><br><span class="line"></span><br><span class="line">[<span class="variable language_">window</span>.<span class="property">$harbor</span>, <span class="variable language_">window</span>.<span class="property">$rrweb</span>].<span class="title function_">forEach</span>(<span class="function">(<span class="params">p</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title class_">PageSpy</span>.<span class="title function_">registerPlugin</span>(p);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">$pageSpy</span> = <span class="keyword">new</span> <span class="title class_">PageSpy</span>(&#123;</span><br><span class="line">  <span class="comment">// 配置项</span></span><br><span class="line">  <span class="attr">api</span>?: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">clientOrigin</span>?: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// &quot;project&quot; is an aggregation of information that can be searched in the room list on the debug side.</span></span><br><span class="line">  <span class="comment">// default: &#x27;default&#x27;</span></span><br><span class="line">  <span class="attr">project</span>?: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// &quot;title&quot; is a user-defined parameter that can be used to distinguish the current debugging client,</span></span><br><span class="line">  <span class="comment">// and the corresponding information is displayed under the &quot;device id&quot; in each debugging connection panel.</span></span><br><span class="line">  <span class="comment">// default: &#x27;--&#x27;</span></span><br><span class="line">  <span class="attr">title</span>?: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Indicates whether the SDK will automatically render the &quot;Circle with Logo on White Background&quot;</span></span><br><span class="line">  <span class="comment">// control in the bottom left corner of the client when initiation is complete. If set to false,</span></span><br><span class="line">  <span class="comment">// you can call window.$pageSpy.render() to render it manually.</span></span><br><span class="line">  <span class="comment">// default: true</span></span><br><span class="line">  <span class="attr">autoRender</span>?: <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Manually specify the scheme of the PageSpy service.</span></span><br><span class="line">  <span class="comment">// This works if the SDK can&#x27;t correctly analyse the scheme, e.g. if PageSpy&#x27;s browser plugin</span></span><br><span class="line">  <span class="comment">// is introduced into the SDK via chrome-extension://xxx/sdk/index.min.js, which will be</span></span><br><span class="line">  <span class="comment">// be parsed by the SDK as an invalid &quot;chrome-extension://&quot; and fallback to [&quot;http://&quot;, &quot;ws://&quot;].</span></span><br><span class="line">  <span class="comment">//   - (Default) Pass the value undefined or null: the SDK will parse it automatically;</span></span><br><span class="line">  <span class="comment">//   - Pass boolean value:</span></span><br><span class="line">  <span class="comment">//     - true: the SDK will access the PageSpy service via [&quot;https://&quot;, &quot;wss://&quot;].</span></span><br><span class="line">  <span class="comment">//     - false: the SDK will access the PageSpy service via [&quot;http://&quot;, &quot;wss://&quot;]</span></span><br><span class="line">  <span class="attr">enableSSL</span>?: <span class="built_in">boolean</span> | <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// All internal plugins are carried with PageSpy by default out of the box.</span></span><br><span class="line">  <span class="comment">// You can disable some plugins as needed.</span></span><br><span class="line">  <span class="attr">disabledPlugins</span>?: (<span class="title class_">InternalPlugins</span> | <span class="built_in">string</span>)[];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// After adding support for offline replay in PageSpy@1.7.4, the client-integrated SDK can work without</span></span><br><span class="line">  <span class="comment">// establishing a connection with the debugger.</span></span><br><span class="line">  <span class="comment">// Default value is false, when users set it to other values will enters &quot;offline mode&quot;, where PageSpy</span></span><br><span class="line">  <span class="comment">// will not create rooms or establish WebSocket connections.</span></span><br><span class="line">  <span class="attr">offline</span>?: <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Customize logo source url.</span></span><br><span class="line">  <span class="attr">logo</span>?: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="小程序"><a href="#小程序" class="headerlink" title="小程序"></a>小程序</h2><h3 id="导入-SDK-1"><a href="#导入-SDK-1" class="headerlink" title="导入 SDK"></a>导入 SDK</h3><p>在项目中安装依赖。我们提供了几种小程序平台的 SDK，请根据需要安装：</p><ul><li>微信小程序</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @huolala-tech/page-spy-wechat@latest</span><br></pre></td></tr></table></figure><ul><li>支付宝小程序</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @huolala-tech/page-spy-alipay@latest</span><br></pre></td></tr></table></figure><ul><li>UniAPP</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @huolala-tech/page-spy-uniapp@latest</span><br></pre></td></tr></table></figure><ul><li>Taro</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @huolala-tech/page-spy-taro@latest</span><br></pre></td></tr></table></figure><h3 id="配置白名单"><a href="#配置白名单" class="headerlink" title="配置白名单"></a>配置白名单</h3><p>将 PageSpy 服务域名填入小程序的 http、websocket 请求白名单中。注意除了开发环境，小程序强制要求使用 https 和 wss 协议：</p><figure class="highlight text"><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">https://&lt;your-pagespy-host&gt;</span><br><span class="line">wss://&lt;your-pagespy-host&gt;</span><br></pre></td></tr></table></figure><h3 id="初始化-PageSpy-和插件-1"><a href="#初始化-PageSpy-和插件-1" class="headerlink" title="初始化 PageSpy 和插件"></a>初始化 PageSpy 和插件</h3><p>在入口文件中引入 SDK 并实例化，初始化参数提供了可选的 配置项 用于自定义 SDK 的行为：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">PageSpy</span> <span class="keyword">from</span> <span class="string">&quot;@huolala-tech/page-spy-wechat&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> $pageSpy = <span class="keyword">new</span> <span class="title class_">PageSpy</span>(&#123;</span><br><span class="line">  <span class="attr">api</span>: <span class="string">&quot;&lt;your-pagespy-host&gt;&quot;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="鸿蒙-App"><a href="#鸿蒙-App" class="headerlink" title="鸿蒙 App"></a>鸿蒙 App</h2><h3 id="导入-SDK-2"><a href="#导入-SDK-2" class="headerlink" title="导入 SDK"></a>导入 SDK</h3><p>在待调试 HAP 目录下安装依赖：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">API 9</span></span><br><span class="line">ohpm install @huolala/page-spy-harmony@^1.0.0</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">API 11</span></span><br><span class="line">ohpm install @huolala/page-spy-harmony@^2</span><br></pre></td></tr></table></figure><h3 id="初始化-PageSpy-和插件-2"><a href="#初始化-PageSpy-和插件-2" class="headerlink" title="初始化 PageSpy 和插件"></a>初始化 PageSpy 和插件</h3><p>在合适的位置引入 SDK 并初始化，这里以 EntryAbility 为例。初始化参数提供了可选的 配置项 用于自定义 SDK 的行为：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PageSpy</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@huolala/page-spy-harmony&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> axiosInstance <span class="keyword">from</span> <span class="string">&quot;path/to/your/axios-instance&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">EntryAbility</span> <span class="keyword">extends</span> <span class="title class_ inherited__">UIAbility</span> &#123;</span><br><span class="line">  <span class="title function_">onWindowStageCreate</span>(<span class="params">windowStage: <span class="variable language_">window</span>.WindowStage</span>) &#123;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">PageSpy</span>(&#123;</span><br><span class="line">      <span class="attr">context</span>: <span class="variable language_">this</span>.<span class="property">context</span>,</span><br><span class="line">      <span class="attr">api</span>: <span class="string">&quot;&lt;your-pagespy-host&gt;&quot;</span>,</span><br><span class="line">      <span class="attr">enableSSL</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">axios</span>: axiosInstance,</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="常用-API"><a href="#常用-API" class="headerlink" title="常用 API"></a>常用 API</h2><h2 id="更新初始化参数"><a href="#更新初始化参数" class="headerlink" title="更新初始化参数"></a>更新初始化参数</h2><p>PageSpy 提供了 Device ID 用于识别设备，同时还提供了 project &#x2F; title 供开发者在初始化时自定义信息，用于辅助识别客户端。但你可能希望在初始化之后更新这些参数信息，操作方式如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 调用 updateRoomInfo 可以更新 project / title</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">$pageSpy</span>.<span class="title function_">updateRoomInfo</span>(&#123; <span class="attr">project</span>: <span class="string">&quot;xxx&quot;</span>, <span class="attr">title</span>: <span class="string">&quot;xxx&quot;</span> &#125;);</span><br></pre></td></tr></table></figure><h2 id="显示隐藏按钮"><a href="#显示隐藏按钮" class="headerlink" title="显示隐藏按钮"></a>显示隐藏按钮</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">$pageSpy</span> = <span class="keyword">new</span> <span class="title class_">PageSpy</span>(&#123;</span><br><span class="line">  <span class="comment">// ... 其他配置参数</span></span><br><span class="line">  <span class="attr">autoRender</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="手动渲染「圆形白底带-Logo」的控件"><a href="#手动渲染「圆形白底带-Logo」的控件" class="headerlink" title="手动渲染「圆形白底带 Logo」的控件"></a>手动渲染「圆形白底带 Logo」的控件</h3><blockquote><p>当 <code>config.autoRender</code> 为 <code>false</code> 时，需要手动调用 <code>render()</code> 方法渲染控件。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">$pageSpy</span>.<span class="title function_">render</span>();</span><br></pre></td></tr></table></figure><h3 id="销毁"><a href="#销毁" class="headerlink" title="销毁"></a>销毁</h3><blockquote><p>PageSpy 会断开连接、从文档中移除相关 DOM、清空已缓存的数据、调用所有已注册插件的 onReset() 方法。<br>当前上下文中被代理或者被重写的 API，如浏览器中的 window.fetch，都会恢复到实例化 PageSpy 之前的状态。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">$pageSpy</span>.<span class="title function_">abort</span>();</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h2 id="调试端如何加一些安全认证的保护，开发者通过认证才可以访问？"><a href="#调试端如何加一些安全认证的保护，开发者通过认证才可以访问？" class="headerlink" title="调试端如何加一些安全认证的保护，开发者通过认证才可以访问？"></a>调试端如何加一些安全认证的保护，开发者通过认证才可以访问？</h2><p>从 2.3.0 版本开始，PageSpy 支持在启动服务时使用变量设置密码以保护调试端安全。设置密码后，开发者访问调试面板时必须输入正确的密码。</p><p>允许输入的变量和含义：</p><ul><li>AUTH_PASSWORD：设置密码；</li><li>JWT_SECRET：设置 token 的密钥；</li><li>JWT_EXPIRATION_HOURS：设置 token 过期时间（小时），默认 24 小时；</li></ul><p>在启动服务时设置，具体的使用方式如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --restart=always -v ./log:/app/log -v ./data:/app/data -p 6752:6752 --name=&quot;pageSpy&quot; -e AUTH_PASSWORD=&lt;password&gt; -e JWT_SECRET=&lt;secret&gt; -e JWT_EXPIRATION_HOURS=&lt;hours&gt; ghcr.io/huolalatech/page-spy-web:latest</span><br></pre></td></tr></table></figure><h3 id="2-3-0-之前的版本如何设置密码？"><a href="#2-3-0-之前的版本如何设置密码？" class="headerlink" title="2.3.0 之前的版本如何设置密码？"></a>2.3.0 之前的版本如何设置密码？</h3><figure class="highlight nginx"><figcaption><span>配置</span></figcaption><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="attribute">auth_basic</span> <span class="string">&quot;请输入用户名和密码以访问 PageSpy 服务&quot;</span>;</span><br><span class="line"><span class="attribute">auth_basic_user_file</span> /etc/nginx/.htpasswd;</span><br></pre></td></tr></table></figure><h2 id="预览页面-CORS"><a href="#预览页面-CORS" class="headerlink" title="预览页面 CORS"></a>预览页面 CORS</h2><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">server</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="comment"># 设置CORS</span></span><br><span class="line">  <span class="comment"># 指定响应资源是否允许与给定的 origin 共享</span></span><br><span class="line">  <span class="string">add_header</span> <span class="string">Access-Control-Allow-Origin</span> <span class="string">&#x27;*&#x27;</span><span class="string">;</span></span><br><span class="line">  <span class="string">add_header</span> <span class="string">Access-Control-Allow-Credentials</span> <span class="string">&#x27;true&#x27;</span><span class="string">;</span></span><br><span class="line">  <span class="comment"># 配置允许跨域的请求方法</span></span><br><span class="line">  <span class="string">add_header</span> <span class="string">Access-Control-Allow-Methods</span> <span class="string">&#x27;GET,POST,PUT,DELETE,Options&#x27;</span><span class="string">;</span></span><br><span class="line">  <span class="comment"># 配置允许跨域的请求头</span></span><br><span class="line">  <span class="comment"># add_header Access-Control-Allow-Headers &#x27;Authorization,Content-Type,Accept,Origin,User-Agent,Cache-Control,X-Mx-ReqToken,X-Requested-With&#x27;;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="更改按钮大小和图片大小"><a href="#更改按钮大小和图片大小" class="headerlink" title="更改按钮大小和图片大小"></a>更改按钮大小和图片大小</h2><figure class="highlight css"><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="selector-id">#__pageSpy</span> <span class="selector-class">.page-spy-logo</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">50px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#__pageSpy</span> <span class="selector-class">.page-spy-logo</span> <span class="selector-tag">img</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">30px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="主题-个性化-设置"><a href="#主题-个性化-设置" class="headerlink" title="主题(个性化)设置"></a>主题(个性化)设置</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">PageSpy</span>(&#123;</span><br><span class="line">  ...,</span><br><span class="line">  <span class="attr">primaryColor</span>: <span class="string">&quot;&lt;主题色&gt;&quot;</span>,</span><br><span class="line">  <span class="attr">logo</span>: <span class="string">&quot;&lt;悬浮球上的 logo&gt;&quot;</span>,</span><br><span class="line">  <span class="attr">modal</span>: &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&quot;&lt;弹窗上的标题&gt;&quot;</span>,</span><br><span class="line">    <span class="attr">logo</span>: <span class="string">&quot;&lt;弹窗上的 logo&gt;&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20241017dv/</id>
    <link href="https://blog.zhangsifan.com/posts/20241017dv/"/>
    <published>2024-10-16T16:00:00.000Z</published>
    <summary>像使用谷歌浏览器的控制台一样简单地开始远程调试。</summary>
    <title>PageSpy使用教程</title>
    <updated>2024-10-16T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="辅助类" scheme="https://blog.zhangsifan.com/categories/%E8%BE%85%E5%8A%A9%E7%B1%BB/"/>
    <category term="辅助类" scheme="https://blog.zhangsifan.com/tags/%E8%BE%85%E5%8A%A9%E7%B1%BB/"/>
    <content>
      <![CDATA[<p>风灵月影（FLiNG）是目前国内使用最广泛的游戏修改器（Trainer）作者之一，其制作的修改器支持大量单机游戏，提供无限生命、无限金币、一击必杀等常用修改功能，适用于单人游戏场景。</p><blockquote><p><a href="https://flingtrainer.com/">官方网站</a></p></blockquote><h2 id="使用说明"><a href="#使用说明" class="headerlink" title="使用说明"></a>使用说明</h2><ol><li>下载对应游戏的修改器文件</li><li><strong>先启动游戏</strong>，进入游戏主界面后，再打开修改器</li><li>按修改器界面提示的快捷键启用对应功能（如 <code>F1</code> 无限生命）</li><li>使用前建议<strong>关闭杀毒软件</strong>或添加信任，防止误报</li></ol><blockquote><p>注意：修改器仅适用于单机模式，请勿在联机对战中使用，以免影响其他玩家体验。</p></blockquote><h2 id="下载列表"><a href="#下载列表" class="headerlink" title="下载列表"></a>下载列表</h2><h2 id="黑神话悟空-Black-Myth-Wukong-Trainer"><a href="#黑神话悟空-Black-Myth-Wukong-Trainer" class="headerlink" title="黑神话悟空 - Black Myth: Wukong Trainer"></a>黑神话悟空 - Black Myth: Wukong Trainer</h2><p><a href="https://static.zhangsifan.com/files/wukong.exe">Black.Myth.Wukong.v1.0-v1.0.9.Plus.44.Trainer-FLiNG</a></p><h2 id="荒野大镖客-2-Red-Dead-Redemption-2"><a href="#荒野大镖客-2-Red-Dead-Redemption-2" class="headerlink" title="荒野大镖客 2 - Red Dead Redemption 2"></a>荒野大镖客 2 - Red Dead Redemption 2</h2><p><a href="https://static.zhangsifan.com/files/red.exe">Red.Dead.Redemption.2.v1.0-v1491.50.Plus.12.Trainer-FLiNG</a></p><h2 id="侠盗猎车手-5-Grand-Theft-Auto-V"><a href="#侠盗猎车手-5-Grand-Theft-Auto-V" class="headerlink" title="侠盗猎车手 5 - Grand Theft Auto V"></a>侠盗猎车手 5 - Grand Theft Auto V</h2><p><a href="https://static.zhangsifan.com/files/gta5.rar">Grand.Theft.Auto.V.v1.0-v1.69.Plus.19.Trainer-FLiNG</a></p><h2 id="赛博朋克-2077-Cyberpunk-2077"><a href="#赛博朋克-2077-Cyberpunk-2077" class="headerlink" title="赛博朋克 2077 - Cyberpunk 2077"></a>赛博朋克 2077 - Cyberpunk 2077</h2><p><a href="https://static.zhangsifan.com/files/2077.exe">Cyberpunk.2077.v2.0-v2.13.Plus.46.Trainer-FLiNG</a></p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>修改器版本需与<strong>游戏版本</strong>对应，游戏更新后旧版修改器可能失效，请到官网下载最新版</li><li>如杀毒软件拦截，请将修改器文件加入信任列表后重新下载</li><li>修改器为 <code>.exe</code> &#x2F; <code>.rar</code> 压缩包，下载后解压或直接运行即可</li></ul>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20240924wg/</id>
    <link href="https://blog.zhangsifan.com/posts/20240924wg/"/>
    <published>2024-09-23T16:00:00.000Z</published>
    <summary>风灵月影（FLiNG）游戏修改器下载汇总：黑神话悟空、荒野大镖客2、GTA5、赛博朋克2077</summary>
    <title>风灵月影下载</title>
    <updated>2024-09-23T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="验证类" scheme="https://blog.zhangsifan.com/categories/%E9%AA%8C%E8%AF%81%E7%B1%BB/"/>
    <category term="reCAPTCHA" scheme="https://blog.zhangsifan.com/tags/reCAPTCHA/"/>
    <content>
      <![CDATA[<p><a href="https://www.google.com/recaptcha/about/">官方网站</a></p><h2 id="基于得分-v3"><a href="#基于得分-v3" class="headerlink" title="基于得分 (v3)"></a>基于得分 (v3)</h2><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><style>.grecaptcha-badge {  visibility: hidden;}</style><script src="https://www.recaptcha.net/recaptcha/api.js?render=6LfWnrwpAAAAAABWBUfPCn9qB6C0vH6K4A7oEz6d"  async defer></script><div id="v3_text">正在自动验证中...</div><script>function openV3() {  grecaptcha.ready(() => {    grecaptcha      .execute("6LfWnrwpAAAAAABWBUfPCn9qB6C0vH6K4A7oEz6d", { action: "submit" })      .then(async (token) => {        await fetch("https://api.zhangsifan.com/google/getReCAPTCHA", {          method: "POST",          body: JSON.stringify({            token: token,            id: "6LfWnrwpAAAAAABWBUfPCn9qB6C0vH6K4A7oEz6d",          }),          headers: {            "Content-Type": "application/json",          },        }).then(async (response) => {          const { result } = await response.json();          console.log(result);          document.getElementById("v3_text").innerText = result.success ? "验证成功,得分为" + result.score  : "验证失败";        });      });  });}setTimeout(() => openV3(), 3000);</script><h2 id="以编程方式调用验证方式"><a href="#以编程方式调用验证方式" class="headerlink" title="以编程方式调用验证方式"></a>以编程方式调用验证方式</h2><blockquote><p>如果您希望更好地控制 reCAPTCHA 的运行时间，可以在 grecaptcha 对象中使用 execute 方法。为此，您需要在 reCAPTCHA 脚本加载中添加 render 参数。</p></blockquote><ul><li><p>使用您的网站密钥加载 JavaScript API。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;script src=<span class="string">&quot;https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key&quot;</span>&gt;&lt;/script&gt;</span><br><span class="line"><span class="comment">// 如无法连接 Google 服务器，请使用以下地址。</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://recaptcha.net/recaptcha/api.js?render=reCAPTCHA_site_key&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure></li><li><p>针对您要保护的每项操作调用 grecaptcha.execute。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">onClick</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  e.<span class="title function_">preventDefault</span>();</span><br><span class="line">  grecaptcha.<span class="title function_">ready</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    grecaptcha</span><br><span class="line">      .<span class="title function_">execute</span>(<span class="string">&quot;reCAPTCHA_site_key&quot;</span>, &#123; <span class="attr">action</span>: <span class="string">&quot;submit&quot;</span> &#125;)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">token</span>) &#123;</span><br><span class="line">        <span class="comment">// Add your logic to submit to your backend server here.</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></li><li><p>在您的后端验证 reCAPTCHA 令牌。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line"><span class="string">&quot;success&quot;</span>: <span class="literal">true</span>|<span class="literal">false</span>,      <span class="comment">// whether this request was a valid reCAPTCHA token for your site</span></span><br><span class="line"><span class="string">&quot;score&quot;</span>: number             <span class="comment">// the score for this request (0.0 - 1.0)</span></span><br><span class="line"><span class="string">&quot;action&quot;</span>: string            <span class="comment">// the action name for this request (important to verify)</span></span><br><span class="line"><span class="string">&quot;challenge_ts&quot;</span>: timestamp,  <span class="comment">// timestamp of the challenge load (ISO format yyyy-MM-dd&#x27;T&#x27;HH:mm:ssZZ)</span></span><br><span class="line"><span class="string">&quot;hostname&quot;</span>: string,         <span class="comment">// the hostname of the site where the reCAPTCHA was solved</span></span><br><span class="line"><span class="string">&quot;error-codes&quot;</span>: [...]        <span class="comment">// optional</span></span><br><span class="line">&#125; = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;https://www.google.com/recaptcha/api/siteverify&quot;</span>, &#123;</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">  <span class="attr">headers</span>: &#123;</span><br><span class="line">    <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/x-www-form-urlencoded; charset=utf-8&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">body</span>: <span class="string">`secret=your_secret&amp;response=<span class="subst">$&#123;token&#125;</span>`</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></li></ul><h2 id="基于验证方式-v2"><a href="#基于验证方式-v2" class="headerlink" title="基于验证方式 (v2)"></a>基于验证方式 (v2)</h2><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h2><style>.grecaptcha-badge {  visibility: hidden;}</style><script src="https://www.recaptcha.net/recaptcha/api.js?render=6LfWnrwpAAAAAABWBUfPCn9qB6C0vH6K4A7oEz6d"  async defer></script><div id="html_element"></div><script>function open() {  grecaptcha.render("html_element", {    sitekey: "6Lf2irwpAAAAAIfBI_Bjo7TAccpnUAPsiI01rF7x",    callback: async function (response) {      await fetch("https://api.zhangsifan.com/google/getReCAPTCHA", {        method: "POST",        body: JSON.stringify({          token: response,          id: "6Lf2irwpAAAAAIfBI_Bjo7TAccpnUAPsiI01rF7x",        }),        headers: {          "Content-Type": "application/json",        },      }).then(async (response) => {        const { result } = await response.json();        if (result.success) {          if (window.confirm("验证成功,是否重置验证器?")) {            grecaptcha.reset();          }        } else {          window.alert("验证失败,是否重置验证器?");          grecaptcha.reset();        }      });    },  });}setTimeout(() => open(), 3000);</script><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</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">title</span>&gt;</span>reCAPTCHA<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></span><br><span class="line"><span class="tag">      <span class="attr">src</span>=<span class="string">&quot;https://www.recaptcha.net/recaptcha/api.js&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">async</span></span></span><br><span class="line"><span class="tag">      <span class="attr">defer</span></span></span><br><span class="line"><span class="tag">    &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">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">div</span> <span class="attr">id</span>=<span class="string">&quot;html_element&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">function</span> <span class="title function_">open</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        grecaptcha.<span class="title function_">render</span>(<span class="string">&quot;html_element&quot;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">sitekey</span>: <span class="string">&quot;reCAPTCHA_site_key&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">          <span class="attr">callback</span>: <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">response</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;https://api.zhangsifan.com/google/getReCAPTCHA&quot;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">              <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">token</span>: response,</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">id</span>: <span class="string">&quot;reCAPTCHA_site_key&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">              &#125;),</span></span><br><span class="line"><span class="language-javascript">              <span class="attr">headers</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">              &#125;,</span></span><br><span class="line"><span class="language-javascript">            &#125;).<span class="title function_">then</span>(<span class="title function_">async</span> (response) =&gt; &#123;</span></span><br><span class="line"><span class="language-javascript">              <span class="keyword">const</span> &#123; result &#125; = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span></span><br><span class="line"><span class="language-javascript">              <span class="keyword">if</span> (result.<span class="property">success</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="title function_">confirm</span>(<span class="string">&quot;验证成功,是否重置验证器?&quot;</span>)) &#123;</span></span><br><span class="line"><span class="language-javascript">                  grecaptcha.<span class="title function_">reset</span>();</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript">              &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">window</span>.<span class="title function_">alert</span>(<span class="string">&quot;验证失败,是否重置验证器?&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">                grecaptcha.<span class="title function_">reset</span>();</span></span><br><span class="line"><span class="language-javascript">              &#125;</span></span><br><span class="line"><span class="language-javascript">            &#125;);</span></span><br><span class="line"><span class="language-javascript">          &#125;,</span></span><br><span class="line"><span class="language-javascript">        &#125;);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">open</span>(), <span class="number">3000</span>);</span></span><br><span class="line"><span class="language-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>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20240417re/</id>
    <link href="https://blog.zhangsifan.com/posts/20240417re/"/>
    <published>2024-04-16T16:00:00.000Z</published>
    <summary>reCAPTCHA使用先进的风险分析引擎和自适应挑战来防止恶意软件在您的网站上进行滥用活动。与此同时，合法用户将能够登录、购买、查看页面或创建帐户，而虚假用户将被屏蔽。</summary>
    <title>Google reCAPTCHA使用教程</title>
    <updated>2024-04-16T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="通行秘钥" scheme="https://blog.zhangsifan.com/categories/%E9%80%9A%E8%A1%8C%E7%A7%98%E9%92%A5/"/>
    <category term="通行秘钥" scheme="https://blog.zhangsifan.com/tags/%E9%80%9A%E8%A1%8C%E7%A7%98%E9%92%A5/"/>
    <content>
      <![CDATA[<p><a href="https://developers.google.com/codelabs/passkey-form-autofill?hl=zh-cn#0">使用文档</a></p><blockquote><p>网站通过使用通行密钥代替密码，可提高用户帐号的安全性并简化用户帐号的管理和使用。借助通行密钥，用户可以使用设备的屏幕锁定功能（例如指纹锁、人脸识别锁或设备 PIN 码）来登录网站或应用。必须先创建通行密钥、将其与用户帐号关联，并将其公钥存储在服务器上，之后用户才能使用该通行密钥进行登录。</p></blockquote><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><blockquote><p><a href="https://account.zhangsifan.com/">示例网站</a></p></blockquote><iframe style="border: none;width:100%;height:800px;"  src="https://account.zhangsifan.com/" allow="publickey-credentials-create; publickey-credentials-get; clipboard-write" ></iframe><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><h2 id="前端代码"><a href="#前端代码" class="headerlink" title="前端代码"></a>前端代码</h2><h3 id="安装依赖库"><a href="#安装依赖库" class="headerlink" title="安装依赖库"></a>安装依赖库</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @simplewebauthn/browser</span><br></pre></td></tr></table></figure><h3 id="后端需实现的接口"><a href="#后端需实现的接口" class="headerlink" title="后端需实现的接口"></a>后端需实现的接口</h3><p>Passkey 的完整流程需要前后端配合，核心是以下 4 个接口：</p><h4 id="注册验证器"><a href="#注册验证器" class="headerlink" title="注册验证器"></a>注册验证器</h4><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">从依赖方（您的服务器）获取注册选项 (/passkey/generate-registration-options)</span><br><span class="line">将身份验证者的回复提交给依赖方进行验证 (/passkey/verify-registration)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="使用验证器"><a href="#使用验证器" class="headerlink" title="使用验证器"></a>使用验证器</h4><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">从依赖方（您的服务器）获取身份验证选项 (/passkey/generate-authentication-options)</span><br><span class="line">将身份验证者的回复提交给依赖方进行验证 (/passkey/verify-authentication)</span><br></pre></td></tr></table></figure><h3 id="前端流程说明"><a href="#前端流程说明" class="headerlink" title="前端流程说明"></a>前端流程说明</h3><ul><li><strong>注册</strong>：<code>startRegistration(result)</code> 调用浏览器原生 WebAuthn API，弹出系统级指纹&#x2F;人脸&#x2F;PIN 验证；成功后把 <code>attResp</code>（包含公钥凭证）发给后端验证并保存</li><li><strong>登录</strong>：<code>startAuthentication(result)</code> 同样调用 WebAuthn 完成设备验证，后端校验签名后返回 <code>token</code>，前端拿到后即可视为登录成功</li><li>出现 <code>InvalidStateError</code> 表示该设备上的凭据已注册过，需提示用户</li></ul><h3 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h3><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">el-input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">v-model.trim</span>=<span class="string">&quot;state.uniid&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&quot;width: 300px&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">placeholder</span>=<span class="string">&quot;请输入用户唯一标识符/邮箱账号&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">el-button</span> @<span class="attr">click</span>=<span class="string">&quot;init&quot;</span> <span class="attr">:disabled</span>=<span class="string">&quot;!state.uniid&quot;</span>&gt;</span>创建通行密钥<span class="tag">&lt;/<span class="name">el-button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">el-button</span> @<span class="attr">click</span>=<span class="string">&quot;login&quot;</span>&gt;</span>通过通行密钥登录<span class="tag">&lt;/<span class="name">el-button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&lt;/template&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  reactive,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  onMounted,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  getCurrentInstance,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  type <span class="title class_">ComponentInternalInstance</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  startRegistration,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  startAuthentication,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125; <span class="keyword">from</span> <span class="string">&quot;@simplewebauthn/browser&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123; <span class="title class_">ElMessage</span> &#125; <span class="keyword">from</span> <span class="string">&quot;element-plus&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> currentInstance = <span class="title function_">getCurrentInstance</span>() <span class="keyword">as</span> <span class="title class_">ComponentInternalInstance</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> &#123; $UtilsHttp &#125; = currentInstance.<span class="property">appContext</span>.<span class="property">config</span>.<span class="property">globalProperties</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> state = <span class="title function_">reactive</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">uniid</span>: <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">init</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">const</span> &#123; result &#125; = <span class="keyword">await</span> $UtilsHttp(</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="string">&quot;/passkey/generate-registration-options&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="string">&quot;get&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">uniid</span>: state.<span class="property">uniid</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    );</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">const</span> challenge = result.<span class="property">challenge</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">let</span> attResp;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      attResp = <span class="keyword">await</span> <span class="title function_">startRegistration</span>(result);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125; <span class="keyword">catch</span> (error) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="keyword">if</span> (error.<span class="property">name</span> === <span class="string">&quot;InvalidStateError&quot;</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">          <span class="string">&quot;Authenticator was probably already registered by user&quot;</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        );</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125; <span class="keyword">else</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(error.<span class="title function_">toString</span>());</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="keyword">throw</span> error;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="keyword">const</span> &#123; result &#125; = <span class="keyword">await</span> $UtilsHttp(</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="string">&quot;/passkey/verify-registration&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="string">&quot;post&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">          attResp,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">          challenge,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      );</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="keyword">if</span> (result) <span class="title class_">ElMessage</span>.<span class="title function_">success</span>(<span class="string">&quot;已成功添加通行密钥(PassKey)!&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125; <span class="keyword">catch</span> (error) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(error.<span class="property">response</span>.<span class="property">data</span>.<span class="property">error</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125; <span class="keyword">catch</span> (error) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(error.<span class="property">response</span>.<span class="property">data</span>.<span class="property">message</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">login</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">const</span> &#123; result &#125; = <span class="keyword">await</span> $UtilsHttp(</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="string">&quot;/passkey/generate-authentication-options&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="string">&quot;get&quot;</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  );</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">const</span> challenge = result.<span class="property">challenge</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">let</span> asseResp;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    asseResp = <span class="keyword">await</span> <span class="title function_">startAuthentication</span>(result);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125; <span class="keyword">catch</span> (error) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(error.<span class="title function_">toString</span>());</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">throw</span> error;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">try</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">const</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">result</span>: &#123; flag, token &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125; = <span class="keyword">await</span> $UtilsHttp(<span class="string">&quot;/passkey/verify-authentication&quot;</span>, <span class="string">&quot;post&quot;</span>, &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      asseResp,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      challenge,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">if</span> (flag) <span class="title class_">ElMessage</span>.<span class="title function_">success</span>(<span class="string">&quot;使用通行密钥(PassKey)登录成功!!&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125; <span class="keyword">catch</span> (error) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title class_">ElMessage</span>.<span class="title function_">error</span>(error.<span class="property">response</span>.<span class="property">data</span>.<span class="property">message</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;less&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="后端代码"><a href="#后端代码" class="headerlink" title="后端代码"></a>后端代码</h2><h3 id="安装依赖库-1"><a href="#安装依赖库-1" class="headerlink" title="安装依赖库"></a>安装依赖库</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @simplewebauthn/server</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20230806pa/</id>
    <link href="https://blog.zhangsifan.com/posts/20230806pa/"/>
    <published>2023-08-05T16:00:00.000Z</published>
    <summary>在 Web 应用中使用表单自动填充功能实现通行密钥</summary>
    <title>通行密钥开发 Passkey</title>
    <updated>2023-08-05T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="Tauri" scheme="https://blog.zhangsifan.com/categories/Tauri/"/>
    <category term="Tauri" scheme="https://blog.zhangsifan.com/tags/Tauri/"/>
    <content>
      <![CDATA[<p><a href="https://tauri.app/zh-cn/">官方网站</a></p><h2 id="Tauri-是什么？"><a href="#Tauri-是什么？" class="headerlink" title="Tauri 是什么？"></a>Tauri 是什么？</h2><p>Tauri 是一个构建适用于所有主流桌面和移动平台的轻快二进制文件的框架。开发者们可以集成任何用于创建用户界面的可以被编译成 HTML、JavaScript 和 CSS 的前端框架，同时可以在必要时使用 Rust、Swift 和 Kotlin 等语言编写后端逻辑。</p><h2 id="前置要求"><a href="#前置要求" class="headerlink" title="前置要求"></a>前置要求</h2><h2 id="系统依赖项"><a href="#系统依赖项" class="headerlink" title="系统依赖项"></a>系统依赖项</h2><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><p>Tauri 使用 Microsoft C++ 构建工具进行开发以及 Microsoft Edge WebView2。这些都是在 Windows 上进行开发所必需的。</p><p>按照以下步骤安装所需的依赖项。</p><h4 id="Microsoft-C-构建工具"><a href="#Microsoft-C-构建工具" class="headerlink" title="Microsoft C++ 构建工具"></a>Microsoft C++ 构建工具</h4><ul><li>下载 <a href="https://visualstudio.microsoft.com/visual-cpp-build-tools/">Microsoft C++</a> 构建工具 安装程序并打开它以开始安装。</li><li>在安装过程中，选中“使用 C++ 进行桌面开发”选项。</li></ul><p><img src="https://tauri.app/_astro/visual-studio-build-tools-installer.TFOm5FVI_RDwYY.webp" alt="Visual Studio C++ 构建工具 安装程序 截图"></p><h4 id="WebView2"><a href="#WebView2" class="headerlink" title="WebView2"></a>WebView2</h4><blockquote><p>WebView 2 已安装在 Windows 10（从版本 1803 开始）和更高版本的 Windows 上。如果你正在这些版本之一上进行开发，则可以跳过此步骤并直接转到 下载并安装 Rust。</p></blockquote><p>Tauri 使用 Microsoft Edge WebView2 在 Windows 上呈现内容。</p><p>通过访问 <a href="https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/#download-section">WebView2 Runtime 下载区</a> 安装 WebView2。下载“常青版独立安装程序（Evergreen Boostrapper）”并安装它。</p><h3 id="MacOS"><a href="#MacOS" class="headerlink" title="MacOS"></a>MacOS</h3><p>Tauri 使用 Xcode 以及各种 macOS 和 iOS 开发依赖项。</p><p>从以下位置之一下载并安装 Xcode：</p><ul><li><a href="https://apps.apple.com/gb/app/xcode/id497799835?mt=12">Mac App Store</a></li><li><a href="https://developer.apple.com/xcode/resources/">Apple Developer 网站</a></li></ul><p>请务必在安装后启动 Xcode，以使它完成设置。</p><h2 id="下载并安装-Rust"><a href="#下载并安装-Rust" class="headerlink" title="下载并安装 Rust"></a>下载并安装 Rust</h2><p>Tauri 使用 Rust 构建并需要它进行开发。使用以下方法之一安装 Rust。你可以在 <a href="https://www.rust-lang.org/tools/install">https://www.rust-lang.org/tools/install</a> 查看更多安装方法。</p><h3 id="Linux-MacOs"><a href="#Linux-MacOs" class="headerlink" title="Linux&#x2F;MacOs"></a>Linux&#x2F;MacOs</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --proto &#x27;=https&#x27; --tlsv1.2 https://sh.rustup.rs -sSf | sh</span><br></pre></td></tr></table></figure><h3 id="Windows-1"><a href="#Windows-1" class="headerlink" title="Windows"></a>Windows</h3><p>前往 <a href="https://www.rust-lang.org/tools/install">https://www.rust-lang.org/tools/install</a> 下载 rustup。</p><p>或者，你可以在 PowerShell 中使用 winget 安装 rustup：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">winget install --id Rustlang.Rustup</span><br></pre></td></tr></table></figure><h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><h2 id="使用-create-tauri-app"><a href="#使用-create-tauri-app" class="headerlink" title="使用 create-tauri-app"></a>使用 create-tauri-app</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm create tauri-app@latest</span><br></pre></td></tr></table></figure><blockquote><p>我们建议从基础模板开始（使用 HTML、CSS 和 JavaScript，不使用前端框架），以便快速入门。随后您可以随时引入前端框架。</p><ul><li>选择您的前端语言：<code>TypeScript / JavaScript</code></li><li>选择您的包管理器：<code>pnpm</code></li><li>选择您的 UI 模板：<code>Vanilla</code></li><li>选择您的 UI 风格：<code>TypeScript</code></li></ul></blockquote><p>在 <code>create-tauri-app</code> 创建完项目后，您可以进入项目文件夹，安装依赖，然后使用 <a href="https://tauri.app/zh-cn/reference/cli/">Tauri CLI</a> 启动开发服务器：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cd tauri-app</span><br><span class="line">npm install</span><br><span class="line">npm run tauri dev</span><br></pre></td></tr></table></figure><h2 id="常用功能"><a href="#常用功能" class="headerlink" title="常用功能"></a>常用功能</h2><h2 id="System-Tray-系统托盘"><a href="#System-Tray-系统托盘" class="headerlink" title="System Tray (系统托盘)"></a>System Tray (系统托盘)</h2><p>Tauri 允许您为您的应用程序创建和自定义系统托盘。这可以通过提供对常见操作的快速访问来增强用户体验。</p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>首先，更新您的<code>Cargo.toml</code>，以包括系统托盘的必要功能。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">src-tauri/Cargo.toml</span></span><br><span class="line"></span><br><span class="line">tauri = &#123; version = &quot;2.0.0&quot;, features = [ &quot;tray-icon&quot; ] &#125;</span><br></pre></td></tr></table></figure><h3 id="JavsScript-用法"><a href="#JavsScript-用法" class="headerlink" title="JavsScript 用法"></a>JavsScript 用法</h3><h4 id="创建托盘图标"><a href="#创建托盘图标" class="headerlink" title="创建托盘图标"></a>创建托盘图标</h4><p>使用 TrayIcon<a href="https://tauri.app/reference/javascript/api/namespacetray/#new">TrayIcon.new</a>静态功能创建新的托盘图标：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">TrayIcon</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@tauri-apps/api/tray&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = &#123;</span><br><span class="line">  <span class="comment">// here you can add a tray menu, title, tooltip, event handler, etc</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> tray = <span class="keyword">await</span> <span class="title class_">TrayIcon</span>.<span class="title function_">new</span>(options);</span><br></pre></td></tr></table></figure><p>有关自定义选项的更多信息，请参阅<a href="https://tauri.app/reference/javascript/api/namespacetray/#trayiconoptions">TrayIconOptions</a>。</p><h4 id="更改托盘图标"><a href="#更改托盘图标" class="headerlink" title="更改托盘图标"></a>更改托盘图标</h4><p>创建托盘时，您可以使用应用程序图标作为托盘图标：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">TrayIcon</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@tauri-apps/api/tray&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = &#123;</span><br><span class="line">  <span class="attr">icon</span>: <span class="string">&quot;icons/32x32.png&quot;</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> tray = <span class="keyword">await</span> <span class="title class_">TrayIcon</span>.<span class="title function_">new</span>(options);</span><br></pre></td></tr></table></figure><h4 id="添加菜单和事件"><a href="#添加菜单和事件" class="headerlink" title="添加菜单和事件"></a>添加菜单和事件</h4><p>要附加在点击托盘时显示的菜单，您可以使用 menu 选项。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">TrayIcon</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@tauri-apps/api/tray&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Menu</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@tauri-apps/api/menu&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; getCurrentWindow &#125; <span class="keyword">from</span> <span class="string">&quot;@tauri-apps/api/window&quot;</span>; <span class="comment">// 获取当前窗口</span></span><br><span class="line"><span class="keyword">import</span> &#123; exit &#125; <span class="keyword">from</span> <span class="string">&quot;@tauri-apps/plugin-process&quot;</span>; <span class="comment">// 进程管理</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> menu = <span class="keyword">await</span> <span class="title class_">Menu</span>.<span class="title function_">new</span>(&#123;</span><br><span class="line">    [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="string">&quot;show&quot;</span>,</span><br><span class="line">        <span class="attr">text</span>: <span class="string">&quot;显示窗口&quot;</span>,</span><br><span class="line">        <span class="attr">action</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">          <span class="title function_">winShowFocus</span>();</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="string">&quot;quit&quot;</span>,</span><br><span class="line">        <span class="attr">text</span>: <span class="string">&quot;退出&quot;</span>,</span><br><span class="line">        <span class="attr">action</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">          <span class="title function_">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = &#123;</span><br><span class="line">  menu,</span><br><span class="line">  <span class="attr">menuOnLeftClick</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">winShowFocus</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> win = <span class="title function_">getCurrentWindow</span>();</span><br><span class="line">  <span class="keyword">if</span> (!(<span class="keyword">await</span> win.<span class="title function_">isVisible</span>())) &#123;</span><br><span class="line">    win.<span class="title function_">show</span>();</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">await</span> win.<span class="title function_">isMinimized</span>()) &#123;</span><br><span class="line">      <span class="keyword">await</span> win.<span class="title function_">unminimize</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">await</span> win.<span class="title function_">setFocus</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> tray = <span class="keyword">await</span> <span class="title class_">TrayIcon</span>.<span class="title function_">new</span>(options);</span><br></pre></td></tr></table></figure><h5 id="显示窗口"><a href="#显示窗口" class="headerlink" title="显示窗口"></a>显示窗口</h5><p><code>tauri</code> 遵循最小权限原则，在运行中可能会碰到如下权限不足的问题：</p><p>在 <code>src-tauri/capabilities/default.json</code> 文件的 <code>permissions</code> 属性数组中加入这几个权限：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-set-focus&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-close&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-show&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-is-visible&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-unminimize&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;core:window:allow-is-minimized&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h5 id="退出-重启应用"><a href="#退出-重启应用" class="headerlink" title="退出&#x2F;重启应用"></a>退出&#x2F;重启应用</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run tauri add process</span><br></pre></td></tr></table></figure><p>引入 API</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; exit, relaunch &#125; <span class="keyword">from</span> ‘@tauri-apps/plugin-process’;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 退出应用</span></span><br><span class="line"><span class="keyword">await</span> <span class="title function_">exit</span>(<span class="number">0</span>);</span><br><span class="line"><span class="comment">// 重启应用</span></span><br><span class="line"><span class="keyword">await</span> <span class="title function_">relaunch</span>();</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20230505tr/</id>
    <link href="https://blog.zhangsifan.com/posts/20230505tr/"/>
    <published>2023-05-04T16:00:00.000Z</published>
    <summary>创建小型、快速、安全、跨平台的应用程序</summary>
    <title>Tauri 2 使用教程</title>
    <updated>2023-05-04T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="验证类" scheme="https://blog.zhangsifan.com/categories/%E9%AA%8C%E8%AF%81%E7%B1%BB/"/>
    <category term="GeeTest" scheme="https://blog.zhangsifan.com/tags/GeeTest/"/>
    <content>
      <![CDATA[<p><a href="https://www.geetest.com/Sensebot">官方网站</a></p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><script src="https://static.geetest.com/static/js/gt.0.4.9.js"></script><div id="captcha"></div><script>fetch("https://api.zhangsifan.com/geetest/register?").then(async (response) => {  const { result } = await response.json();  console.log(result);  initGeetest(    {      // 以下配置参数来自服务端 SDK      gt: result.gt,      challenge: result.challenge,      offline: !result.success,      new_captcha: true,    },    function (captchaObj) {      captchaObj.appendTo("#captcha");      captchaObj.onSuccess(() => {        var result = captchaObj.getValidate();        console.log(result);        fetch("https://api.zhangsifan.com/geetest/validate", {          method: "POST",          body: JSON.stringify(result),          headers: {            "Content-Type": "application/json",          },        }).then(async (response) => {          const { result } = await response.json();          if (result.success) {            if (window.confirm("验证成功,是否重置验证器?")) {              captchaObj.reset();            }          }        });      });    }  );});</script><h2 id="CodePen-示例"><a href="#CodePen-示例" class="headerlink" title="CodePen 示例"></a>CodePen 示例</h2><iframe height="500" style="width: 100%;" scrolling="no" title="GeeTest3.0使用教程" src="https://codepen.io/AlwaysTeam/embed/pvgyWya?default-tab=js%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">  See the Pen <a href="https://codepen.io/AlwaysTeam/pen/pvgyWya">  GeeTest3.0使用教程</a> by AlwaysTeam (<a href="https://codepen.io/AlwaysTeam">@AlwaysTeam</a>)  on <a href="https://codepen.io">CodePen</a>.</iframe><h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><blockquote><p>准备工作：确保已经在极验用户后台获取到了 captchaId</p></blockquote><p><a href="https://docs.geetest.com/sensebot/apirefer/api/web">配置参数</a></p><h2 id="1-引入初始化函数"><a href="#1-引入初始化函数" class="headerlink" title="1.引入初始化函数"></a>1.引入初始化函数</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://static.geetest.com/static/js/gt.0.4.9.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="2-初始化"><a href="#2-初始化" class="headerlink" title="2.初始化"></a>2.初始化</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;captcha&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">ajax</span>(&#123;</span><br><span class="line">  <span class="attr">url</span>: <span class="string">&quot;API1接口（详见服务端部署）&quot;</span>,</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&quot;get&quot;</span>,</span><br><span class="line">  <span class="attr">dataType</span>: <span class="string">&quot;json&quot;</span>,</span><br><span class="line">  <span class="attr">success</span>: <span class="keyword">function</span> (<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="comment">//请检测data的数据结构， 保证data.gt, data.challenge, data.success有值</span></span><br><span class="line">    <span class="title function_">initGeetest</span>(</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 以下配置参数来自服务端 SDK</span></span><br><span class="line">        <span class="attr">gt</span>: data.<span class="property">gt</span>,</span><br><span class="line">        <span class="attr">challenge</span>: data.<span class="property">challenge</span>,</span><br><span class="line">        <span class="attr">offline</span>: !data.<span class="property">success</span>,</span><br><span class="line">        <span class="attr">new_captcha</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">function</span> (<span class="params">captchaObj</span>) &#123;</span><br><span class="line">        captchaObj.<span class="title function_">appendTo</span>(<span class="string">&quot;#captcha&quot;</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></pre></td></tr></table></figure><h2 id="3-二次验证"><a href="#3-二次验证" class="headerlink" title="3.二次验证"></a>3.二次验证</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">initGeetest</span>(</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">// 省略配置参数</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">function</span> (<span class="params">captchaObj</span>) &#123;</span><br><span class="line">    <span class="comment">// 省略其他方法的调用</span></span><br><span class="line">    captchaObj.<span class="title function_">onSuccess</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">var</span> result = captchaObj.<span class="title function_">getValidate</span>();</span><br><span class="line">      <span class="comment">// ajax 伪代码</span></span><br><span class="line">      $.<span class="title function_">ajax</span>(&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&quot;服务端&quot;</span>,</span><br><span class="line">        <span class="attr">data</span>: result,</span><br><span class="line">        <span class="attr">dataType</span>: <span class="string">&quot;json&quot;</span>,</span><br><span class="line">        <span class="attr">success</span>: <span class="keyword">function</span> (<span class="params">res</span>) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">result</span>);</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="重置"><a href="#重置" class="headerlink" title="重置"></a>重置</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">captchaObj.<span class="title function_">reset</span>();</span><br></pre></td></tr></table></figure><h2 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h2><blockquote><p>后端使用 Nodejs + Express</p></blockquote><p><a href="https://github.com/GeeTeam/gt3-server-node-express-bypass">官方 Demo</a></p>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20230330gt/</id>
    <link href="https://blog.zhangsifan.com/posts/20230330gt/"/>
    <published>2023-03-29T16:00:00.000Z</published>
    <summary>GeeTest3.0使用教程。极验「行为验证」是一项可以帮助你的网站与APP识别与拦截机器程序批量自动化操作的SaaS应用。它是由极验开发的新一代人机验证产品，它不基于传统“问题-答案”的检测模式，而是通过利用深度学习对验证过程中产生的行为数据进行高维分析，发现人机行为模式与行为特征的差异，更加精准地区分人机行为。</summary>
    <title>GeeTest3.0使用教程</title>
    <updated>2023-03-29T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="验证类" scheme="https://blog.zhangsifan.com/categories/%E9%AA%8C%E8%AF%81%E7%B1%BB/"/>
    <category term="GeeTest" scheme="https://blog.zhangsifan.com/tags/GeeTest/"/>
    <content>
      <![CDATA[<p><a href="https://www.geetest.com/adaptive-captcha-demo">官方网站</a></p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><script src="https://static.geetest.com/v4/gt4.js"></script><div id="captcha"></div><script>initGeetest4(  {    captchaId: "37e44d588298c886c5598e761b83efbb",    nativeButton: {      width: "300px",      height: "40px",    }, // 极验按钮样式设置    userInfo: "user@zhangsifan.com", // 用户信息  },  function (captcha) {    // captcha为验证码实例    captcha.appendTo("#captcha"); // 调用appendTo将验证码插入到页的某一个元素中，这个元素用户可以自定义    captcha.onSuccess(() => {      var result = captcha.getValidate();      fetch("https://api.zhangsifan.com/geetest/validate_v4", {        method: "POST",        body: JSON.stringify(result),        headers: {          "Content-Type": "application/json",        },      }).then(async (response) => {        const { result } = await response.json();        if (result.success) {          if (window.confirm("验证成功,是否重置验证器?")) {            captcha.reset();          }        }      });    });  });</script><h2 id="CodePen-示例"><a href="#CodePen-示例" class="headerlink" title="CodePen 示例"></a>CodePen 示例</h2><iframe height="500" style="width: 100%;" scrolling="no" title="GeeTest4.0使用教程" src="https://codepen.io/AlwaysTeam/embed/VYeaMPY?default-tab=js%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">  See the Pen <a href="https://codepen.io/AlwaysTeam/pen/VYeaMPY">  GeeTest4.0使用教程</a> by AlwaysTeam (<a href="https://codepen.io/AlwaysTeam">@AlwaysTeam</a>)  on <a href="https://codepen.io">CodePen</a>.</iframe><h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><blockquote><p>准备工作：确保已经在极验用户后台获取到了 captchaId</p></blockquote><p><a href="https://docs.geetest.com/gt4/apirefer/api/web/#%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0">配置参数</a></p><h2 id="1-引入初始化函数"><a href="#1-引入初始化函数" class="headerlink" title="1.引入初始化函数"></a>1.引入初始化函数</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://static.geetest.com/v4/gt4.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="2-初始化"><a href="#2-初始化" class="headerlink" title="2.初始化"></a>2.初始化</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;captcha&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">initGeetest4</span>(</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">captchaId</span>: <span class="string">&quot;您的captchaId&quot;</span>,</span><br><span class="line">    <span class="attr">nativeButton</span>: &#123;</span><br><span class="line">      <span class="attr">width</span>: <span class="string">&quot;300px&quot;</span>,</span><br><span class="line">      <span class="attr">height</span>: <span class="string">&quot;40px&quot;</span>,</span><br><span class="line">    &#125;, <span class="comment">// 极验按钮样式设置</span></span><br><span class="line">    <span class="attr">userInfo</span>: <span class="string">&quot;user@geetest.com&quot;</span>, <span class="comment">// 用户信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">function</span> (<span class="params">captcha</span>) &#123;</span><br><span class="line">    <span class="comment">// captcha为验证码实例</span></span><br><span class="line">    captcha.<span class="title function_">appendTo</span>(<span class="string">&quot;#captcha&quot;</span>); <span class="comment">// 调用appendTo将验证码插入到页的某一个元素中，这个元素用户可以自定义</span></span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="3-二次验证"><a href="#3-二次验证" class="headerlink" title="3.二次验证"></a>3.二次验证</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">initGeetest4</span>(</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">// 省略配置参数</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">function</span> (<span class="params">captchaObj</span>) &#123;</span><br><span class="line">    <span class="comment">// 省略其他方法的调用</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里调用了 onSuccess 方法，该方法介绍见下文</span></span><br><span class="line">    captchaObj.<span class="title function_">onSuccess</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">var</span> result = captchaObj.<span class="title function_">getValidate</span>();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// ajax 伪代码</span></span><br><span class="line">      $.<span class="title function_">ajax</span>(&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&quot;服务端&quot;</span>,</span><br><span class="line">        <span class="attr">data</span>: result,</span><br><span class="line">        <span class="attr">dataType</span>: <span class="string">&quot;json&quot;</span>,</span><br><span class="line">        <span class="attr">success</span>: <span class="keyword">function</span> (<span class="params">res</span>) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">result</span>);</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="重置"><a href="#重置" class="headerlink" title="重置"></a>重置</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">captchaObj.<span class="title function_">reset</span>();</span><br></pre></td></tr></table></figure><h2 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h2><blockquote><p>后端使用 Nodejs + Express</p></blockquote><p><a href="https://github.com/GeeTeam/gt4_node_express_demo">官方 Demo</a></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&quot;express&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> querystring = <span class="built_in">require</span>(<span class="string">&quot;querystring&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&quot;crypto&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> axios = <span class="built_in">require</span>(<span class="string">&quot;axios&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> router = express.<span class="title class_">Router</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// geetest 公钥</span></span><br><span class="line"><span class="comment">// geetest public key</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CAPTCHA_ID</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// geetest 密钥</span></span><br><span class="line"><span class="comment">// geetest secret key</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CAPTCHA_KEY</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// geetest 服务地址</span></span><br><span class="line"><span class="comment">// geetest server url</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">API_SERVER</span> = <span class="string">&quot;http://gcaptcha4.geetest.com&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// geetest 验证接口</span></span><br><span class="line"><span class="comment">// geetest server interface</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">API_URL</span> = <span class="variable constant_">API_SERVER</span> + <span class="string">&quot;/validate&quot;</span> + <span class="string">&quot;?captcha_id=&quot;</span> + <span class="variable constant_">CAPTCHA_ID</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* GET home page. */</span></span><br><span class="line">router.<span class="title function_">get</span>(<span class="string">&quot;/&quot;</span>, <span class="keyword">function</span> (<span class="params">req, res, next</span>) &#123;</span><br><span class="line">  res.<span class="title function_">render</span>(<span class="string">&quot;index&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">router.<span class="title function_">get</span>(<span class="string">&quot;/login&quot;</span>, <span class="keyword">function</span> (<span class="params">req, res, next</span>) &#123;</span><br><span class="line">  req.<span class="property">query</span> = querystring.<span class="title function_">parse</span>(req.<span class="property">url</span>.<span class="title function_">split</span>(<span class="string">&quot;?&quot;</span>)[<span class="number">1</span>]);</span><br><span class="line">  <span class="comment">// 前端参数</span></span><br><span class="line">  <span class="comment">// web parameter</span></span><br><span class="line">  <span class="keyword">var</span> lot_number = req.<span class="property">query</span>[<span class="string">&quot;lot_number&quot;</span>];</span><br><span class="line">  <span class="keyword">var</span> captcha_output = req.<span class="property">query</span>[<span class="string">&quot;captcha_output&quot;</span>];</span><br><span class="line">  <span class="keyword">var</span> pass_token = req.<span class="property">query</span>[<span class="string">&quot;pass_token&quot;</span>];</span><br><span class="line">  <span class="keyword">var</span> gen_time = req.<span class="property">query</span>[<span class="string">&quot;gen_time&quot;</span>];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 生成签名, 使用标准的hmac算法，使用用户当前完成验证的流水号lot_number作为原始消息message，使用客户验证私钥作为key</span></span><br><span class="line">  <span class="comment">// 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名</span></span><br><span class="line">  <span class="comment">// use lot_number + CAPTCHA_KEY, generate the signature</span></span><br><span class="line">  <span class="keyword">var</span> sign_token = <span class="title function_">hmac_sha256_encode</span>(lot_number, <span class="variable constant_">CAPTCHA_KEY</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 向极验转发前端数据 + “sign_token” 签名</span></span><br><span class="line">  <span class="comment">// send web parameter and “sign_token” to geetest server</span></span><br><span class="line">  <span class="keyword">var</span> datas = &#123;</span><br><span class="line">    <span class="attr">lot_number</span>: lot_number,</span><br><span class="line">    <span class="attr">captcha_output</span>: captcha_output,</span><br><span class="line">    <span class="attr">pass_token</span>: pass_token,</span><br><span class="line">    <span class="attr">gen_time</span>: gen_time,</span><br><span class="line">    <span class="attr">sign_token</span>: sign_token,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// post request</span></span><br><span class="line">  <span class="comment">// 根据极验返回的用户验证状态, 网站主进行自己的业务逻辑</span></span><br><span class="line">  <span class="comment">// According to the user authentication status returned by the geetest, the website owner carries out his own business logic</span></span><br><span class="line">  <span class="title function_">post_form</span>(datas, <span class="variable constant_">API_URL</span>)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">(<span class="params">result</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (result[<span class="string">&quot;result&quot;</span>] == <span class="string">&quot;success&quot;</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;validate success&quot;</span>);</span><br><span class="line">        res.<span class="title function_">send</span>(<span class="string">&quot;success&quot;</span>);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;validate fail:&quot;</span> + result[<span class="string">&quot;reason&quot;</span>]);</span><br><span class="line">        res.<span class="title function_">send</span>(<span class="string">&quot;fail&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 当请求Geetest服务接口出现异常，应放行通过，以免阻塞正常业务。</span></span><br><span class="line">      <span class="comment">// When the request geetest service interface is abnormal, it shall be released to avoid blocking normal business.</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Geetest server error:&quot;</span> + err);</span><br><span class="line">      res.<span class="title function_">send</span>(<span class="string">&quot;success&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成签名</span></span><br><span class="line"><span class="comment">// Generate signature</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">hmac_sha256_encode</span>(<span class="params">value, key</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> hash = crypto</span><br><span class="line">    .<span class="title function_">createHmac</span>(<span class="string">&quot;sha256&quot;</span>, key)</span><br><span class="line">    .<span class="title function_">update</span>(value, <span class="string">&quot;utf8&quot;</span>)</span><br><span class="line">    .<span class="title function_">digest</span>(<span class="string">&quot;hex&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> hash;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 发送post请求, 响应json数据如：&#123;&quot;result&quot;: &quot;success&quot;, &quot;reason&quot;: &quot;&quot;, &quot;captcha_args&quot;: &#123;&#125;&#125;</span></span><br><span class="line"><span class="comment">// Send a post request and respond to JSON data, such as: &#123;result &quot;:&quot; success &quot;,&quot; reason &quot;:&quot; &quot;,&quot; captcha_args &quot;: &#123;&#125;&#125;</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">post_form</span>(<span class="params">datas, url</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> options = &#123;</span><br><span class="line">    <span class="attr">url</span>: url,</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">    <span class="attr">params</span>: datas,</span><br><span class="line">    <span class="attr">timeout</span>: <span class="number">5000</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> result = <span class="keyword">await</span> <span class="title function_">axios</span>(options);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (result.<span class="property">status</span> != <span class="number">200</span>) &#123;</span><br><span class="line">    <span class="comment">// geetest服务响应异常</span></span><br><span class="line">    <span class="comment">// geetest service response exception</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Geetest Response Error, StatusCode:&quot;</span> + result.<span class="property">status</span>);</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Geetest Response Error&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result.<span class="property">data</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = router;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20230329gt/</id>
    <link href="https://blog.zhangsifan.com/posts/20230329gt/"/>
    <published>2023-03-28T16:00:00.000Z</published>
    <summary>GeeTest4.0使用教程。行为验证4.0产品是极验于2022年6月正式推出的最新一代验证码产品，结合环境检测、行为特征、POW工作量证明、视觉模型热更等多项技术，在注册、登录、下单、防作弊等多种场景提供人机智能分流验证服务，为企业安全保驾护航。</summary>
    <title>GeeTest4.0使用教程</title>
    <updated>2023-03-28T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="Web Office" scheme="https://blog.zhangsifan.com/categories/Web-Office/"/>
    <category term="kkFileViews" scheme="https://blog.zhangsifan.com/tags/kkFileViews/"/>
    <category term="在线预览" scheme="https://blog.zhangsifan.com/tags/%E5%9C%A8%E7%BA%BF%E9%A2%84%E8%A7%88/"/>
    <content>
      <![CDATA[<p><a href="https://kkview.cn/zh-cn/docs/home.html">官方文档</a><br><a href="https://public.zsxq.com/groups/48844125114258.html">KK 开源社区</a></p><h2 id="Docker-容器环境运行"><a href="#Docker-容器环境运行" class="headerlink" title="Docker 容器环境运行"></a>Docker 容器环境运行</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 拉取镜像</span></span><br><span class="line">docker pull keking/kkfileview</span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">docker run -it -p 8012:8012 keking/kkfileview</span><br><span class="line"><span class="comment"># 浏览器访问容器8012端口 http://172.0.0.1:8012 即可看到项目演示用首页</span></span><br></pre></td></tr></table></figure><h2 id="源码运行"><a href="#源码运行" class="headerlink" title="源码运行"></a>源码运行</h2><h2 id="1-下载源码"><a href="#1-下载源码" class="headerlink" title="1. 下载源码"></a>1. 下载源码</h2><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"><span class="comment"># gitee</span></span><br><span class="line">https://gitee.com/kekingcn/file-online-preview.git</span><br><span class="line"><span class="comment"># github</span></span><br><span class="line">https://github.com/kekingcn/kkFileView.git</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><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入 kkFileView 目录</span></span><br><span class="line"><span class="built_in">cd</span> file-online-preview/server/src/main/java/cn.keking/serverMain</span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">main</span><br></pre></td></tr></table></figure><h2 id="3-制作Docker镜像"><a href="#3-制作Docker镜像" class="headerlink" title="3. 制作Docker镜像"></a>3. 制作Docker镜像</h2><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> docker/kkfileview-base</span><br><span class="line">docker build --tag keking/kkfileview-base:5.0.0 .</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> ../..</span><br><span class="line">docker build --tag keking/kkfileview:5.0.0 .</span><br><span class="line"></span><br><span class="line">docker run -d -p 8012:8012 --name kkfileview keking/kkfileview:5.0.0</span><br><span class="line"></span><br><span class="line">docker save -o kkfileview-5.0.0.tar keking/kkfileview:5.0.0</span><br></pre></td></tr></table></figure><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><p>file-online-preview&#x2F;server&#x2F;src&#x2F;main&#x2F;config&#x2F;application.properties</p><figure class="highlight properties"><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><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br></pre></td><td class="code"><pre><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="comment"></span></span><br><span class="line"><span class="comment"># 服务器端口号，默认8012</span></span><br><span class="line"><span class="comment"># 可以通过环境变量 KK_SERVER_PORT 覆盖</span></span><br><span class="line"><span class="attr">server.port</span> = <span class="string">$&#123;KK_SERVER_PORT:8012&#125;</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"># 可以通过环境变量 KK_CONTEXT_PATH 覆盖</span></span><br><span class="line"><span class="attr">server.servlet.context-path</span> = <span class="string">$&#123;KK_CONTEXT_PATH:/&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 字符编码设置，统一使用UTF-8</span></span><br><span class="line"><span class="attr">server.servlet.encoding.charset</span> = <span class="string">utf-8</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="attr">server.compression.enabled</span> = <span class="string">true</span></span><br><span class="line"><span class="attr">server.compression.min-response-size</span> = <span class="string">2048</span></span><br><span class="line"><span class="attr">server.compression.mime-types</span> = <span class="string">application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain,font/woff,application/font-woff,font/eot,image/svg+xml,image/x-icon</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 文件上传大小限制，默认500MB</span></span><br><span class="line"><span class="comment"># 注意：需要同时设置spring.servlet.multipart.max-file-size和max-request-size</span></span><br><span class="line"><span class="attr">spring.servlet.multipart.max-file-size</span> = <span class="string">500MB</span></span><br><span class="line"><span class="attr">spring.servlet.multipart.max-request-size</span> = <span class="string">500MB</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># FreeMarker模板引擎配置</span></span><br><span class="line"><span class="attr">spring.freemarker.template-loader-path</span> = <span class="string">classpath:/web/</span></span><br><span class="line"><span class="attr">spring.freemarker.cache</span> = <span class="string">false</span></span><br><span class="line"><span class="attr">spring.freemarker.charset</span> = <span class="string">UTF-8</span></span><br><span class="line"><span class="attr">spring.freemarker.check-template-location</span> = <span class="string">true</span></span><br><span class="line"><span class="attr">spring.freemarker.content-type</span> = <span class="string">text/html</span></span><br><span class="line"><span class="attr">spring.freemarker.expose-request-attributes</span> = <span class="string">true</span></span><br><span class="line"><span class="attr">spring.freemarker.expose-session-attributes</span> = <span class="string">true</span></span><br><span class="line"><span class="attr">spring.freemarker.request-context-attribute</span> = <span class="string">request</span></span><br><span class="line"><span class="attr">spring.freemarker.suffix</span> = <span class="string">.ftl</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Spring Boot Actuator监控端点配置</span></span><br><span class="line"><span class="attr">management.endpoints.web.exposure.include</span> = <span class="string">health,info,metrics</span></span><br><span class="line"><span class="attr">management.endpoint.health.show-details</span> = <span class="string">always</span></span><br><span class="line"><span class="attr">management.health.defaults.enabled</span> = <span class="string">true</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="comment"># 二、Office文档处理配置（部分支持动态配置）</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"># Office组件安装路径，默认为自动查找</span></span><br><span class="line"><span class="comment"># Windows示例（注意双反斜杠）：C:\\Program Files (x86)\\OpenOffice 4</span></span><br><span class="line"><span class="comment"># Linux示例：/opt/libreoffice</span></span><br><span class="line"><span class="comment"># MacOS示例：/Applications/LibreOffice.app/Contents</span></span><br><span class="line"><span class="attr">office.home</span> = <span class="string">$&#123;KK_OFFICE_HOME:default&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office组件服务端口，支持多个端口实现负载均衡</span></span><br><span class="line"><span class="attr">office.plugin.server.ports</span> = <span class="string">2001,2002</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office组件任务超时时间，默认5分钟</span></span><br><span class="line"><span class="attr">office.plugin.task.timeout</span> = <span class="string">5m</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="attr">office.plugin.task.maxtasksperprocess</span> = <span class="string">200</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 任务执行超时时间，默认5分钟</span></span><br><span class="line"><span class="attr">office.plugin.task.taskexecutiontimeout</span> = <span class="string">5m</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office文档分页范围，支持动态配置</span></span><br><span class="line"><span class="comment"># 默认false，开启后可以指定转换的页面范围</span></span><br><span class="line"><span class="attr">office.pagerange</span> = <span class="string">$&#123;KK_OFFICE_PAGERANGE:false&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office文档水印功能，支持动态配置</span></span><br><span class="line"><span class="comment"># 默认false，开启后会在Office文档上添加水印</span></span><br><span class="line"><span class="attr">office.watermark</span> = <span class="string">$&#123;KK_OFFICE_WATERMARK:false&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office图片质量，1-100，默认80</span></span><br><span class="line"><span class="comment"># 值越高图片质量越好，但文件越大</span></span><br><span class="line"><span class="attr">office.quality</span> = <span class="string">$&#123;KK_OFFICE_QUALITY:80&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office图片最大分辨率，默认150</span></span><br><span class="line"><span class="comment"># 控制生成图片的最大分辨率</span></span><br><span class="line"><span class="attr">office.maximageresolution</span> = <span class="string">$&#123;KK_OFFICE_MAXIMAGERESOLUTION:150&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 导出Office书签，支持动态配置</span></span><br><span class="line"><span class="comment"># 默认true，转换PDF时保留书签</span></span><br><span class="line"><span class="attr">office.exportbookmarks</span> = <span class="string">$&#123;KK_OFFICE_EXPORTBOOKMARKS:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否将Office文档中的批注作为PDF注释导出，默认为true（导出）</span></span><br><span class="line"><span class="comment"># 保留批注便于文档审阅</span></span><br><span class="line"><span class="attr">office.exportnotes</span> = <span class="string">$&#123;KK_OFFICE_EXPORTNOTES:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 加密文档生成的PDF是否添加密码，默认为true（添加）</span></span><br><span class="line"><span class="comment"># 密码为原始加密文档的密码，增强文档安全性</span></span><br><span class="line"><span class="attr">office.documentopenpasswords</span> = <span class="string">$&#123;KK_OFFICE_DOCUMENTOPENPASSWORD:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Excel文档(xlsx)的前端解析方式，默认为web（Web端解析）</span></span><br><span class="line"><span class="comment"># web: 使用前端SheetJS库解析，减轻服务器压力</span></span><br><span class="line"><span class="comment"># image: 服务器转换为图片，兼容性更好</span></span><br><span class="line"><span class="attr">office.type.web</span> = <span class="string">$&#123;KK_OFFICE_TYPE_WEB:web&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Office文档预览类型</span></span><br><span class="line"><span class="comment"># 支持动态配置，可选值：image/pdf，默认使用pdf模式</span></span><br><span class="line"><span class="attr">office.preview.type</span> = <span class="string">$&#123;KK_OFFICE_PREVIEW_TYPE:pdf&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否关闭Office预览模式切换开关，默认为true（关闭切换）</span></span><br><span class="line"><span class="comment"># 设置为false时，用户可以在图片和PDF模式间切换</span></span><br><span class="line"><span class="attr">office.preview.switch.disabled</span> = <span class="string">$&#123;KK_OFFICE_PREVIEW_SWITCH_DISABLED:true&#125;</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="comment"># 三、CAD文件处理配置（支持动态配置）</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"># CAD文件预览类型</span></span><br><span class="line"><span class="comment">#   svg: 转换为SVG矢量格式（缩放不失真）</span></span><br><span class="line"><span class="comment">#   pdf: 转换为PDF格式（便于打印和标注）</span></span><br><span class="line"><span class="attr">cad.preview.type</span> = <span class="string">$&#123;KK_CAD_PREVIEW_TYPE:svg&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Cad转换模块设置(aspose-cad=1 ,cadviewer=3) </span></span><br><span class="line"><span class="comment"># aspose-cad 默认集成到系统,但是特别吃服务器性能  (支持转换格式为 svg、pdf)</span></span><br><span class="line"><span class="comment"># cadviewer 下载地址 https://cadviewer.com/alldownloads/autoxchange/ 更具自己系统下载转换包(支持转换格式为 svg、svgz、pdf)</span></span><br><span class="line"><span class="comment"># 1=aspose-cad  转换格式为 pdf,svg,tif   支持类型最多</span></span><br><span class="line"><span class="comment"># 2=cadviewer   转换格式为 pdf,svg  支持的类型 dwg  dxf  dwf</span></span><br><span class="line"><span class="attr">cad.conversionmodule</span> = <span class="string">1</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Cad 后端转换包路径 （linux 严格注意大小写）</span></span><br><span class="line"><span class="comment"># cadviewer windows 修改名称为 cadviewer.exe   inux修改名称为  cadviewer   需要安装字体 </span></span><br><span class="line"><span class="comment"># cadviewer 字体下载 https://cadviewer.com/downloads/fonts/fonts.tar.gz  放在 cad.file.path 目录里面的fonts.</span></span><br><span class="line"><span class="attr">cad.cadconverterpath</span>  = <span class="string">false</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"># CAD文件处理线程数</span></span><br><span class="line"><span class="attr">cad.thread</span> = <span class="string">$&#123;KK_CAD_THREAD:5&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># CAD文件处理超时时间（秒）</span></span><br><span class="line"><span class="attr">cad.timeout</span> = <span class="string">$&#123;KK_CAD_TIMEOUT:90&#125;</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="comment"># 四、PDF文件处理配置（支持动态配置）</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"># 是否禁止PDF演示模式，默认为true（禁止）</span></span><br><span class="line"><span class="attr">pdf.presentationMode.disable</span> = <span class="string">$&#123;KK_PDF_PRESENTATION_MODE_DISABLE:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁止PDF文件菜单中的&quot;打开文件&quot;选项，默认为true（禁止）</span></span><br><span class="line"><span class="attr">pdf.openFile.disable</span> = <span class="string">$&#123;KK_PDF_OPEN_FILE_DISABLE:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁止PDF打印功能，默认为true（禁止）</span></span><br><span class="line"><span class="attr">pdf.print.disable</span> = <span class="string">$&#123;KK_PDF_PRINT_DISABLE:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁止PDF下载功能，默认为true（禁止）</span></span><br><span class="line"><span class="attr">pdf.download.disable</span> = <span class="string">$&#123;KK_PDF_DOWNLOAD_DISABLE:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁止PDF书签/大纲功能，默认为true（禁止）</span></span><br><span class="line"><span class="attr">pdf.bookmark.disable</span> = <span class="string">$&#123;KK_PDF_BOOKMARK_DISABLE:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁止PDF编辑功能（注释、表单等），默认为false（允许编辑）</span></span><br><span class="line"><span class="attr">pdf.disable.editing</span> = <span class="string">$&#123;KK_PDF_DISABLE_EDITING:false&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># PDF处理最大线程数，控制并发处理能力</span></span><br><span class="line"><span class="attr">pdf.max.threads</span> = <span class="string">10</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># PDF处理超时配置</span></span><br><span class="line"><span class="attr">pdf.timeout.small</span> = <span class="string">90</span></span><br><span class="line"><span class="attr">pdf.timeout.medium</span> = <span class="string">180</span></span><br><span class="line"><span class="attr">pdf.timeout.large</span> = <span class="string">300</span></span><br><span class="line"><span class="attr">pdf.timeout.xlarge</span> = <span class="string">600</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># PDF智能DPI优化</span></span><br><span class="line"><span class="comment"># 是否启用PDF DPI智能调整，默认为true（启用）</span></span><br><span class="line"><span class="comment"># 根据PDF页数自动调整DPI，平衡清晰度和性能</span></span><br><span class="line"><span class="attr">pdf.dpi.enabled</span> = <span class="string">true</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># PDF转图片的基准DPI，默认为144</span></span><br><span class="line"><span class="comment"># 当DPI优化禁用时使用此值</span></span><br><span class="line"><span class="attr">pdf2jpg.dpi</span> = <span class="string">$&#123;KK_PDF2JPG_DPI:144&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 智能DPI分级配置</span></span><br><span class="line"><span class="comment"># 小文件（0-50页）：150 DPI（高质量）</span></span><br><span class="line"><span class="attr">pdf.dpi.small</span> = <span class="string">150</span></span><br><span class="line"><span class="comment"># 中等文件（50-100页）：120 DPI（平衡质量与性能）</span></span><br><span class="line"><span class="attr">pdf.dpi.medium</span> = <span class="string">120</span></span><br><span class="line"><span class="comment"># 大文件（100-200页）：96 DPI（优化性能）</span></span><br><span class="line"><span class="attr">pdf.dpi.large</span> = <span class="string">96</span></span><br><span class="line"><span class="comment"># 超大文件（200-500页）：72 DPI（快速转换）</span></span><br><span class="line"><span class="attr">pdf.dpi.xlarge</span> = <span class="string">72</span></span><br><span class="line"><span class="comment"># 巨量文件（&gt;500页）：72 DPI（最小资源消耗）</span></span><br><span class="line"><span class="attr">pdf.dpi.xxlarge</span> = <span class="string">72</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="comment"># 五、TIF文件处理配置（支持动态配置）</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"># TIF文件预览类型</span></span><br><span class="line"><span class="comment">#   tif: 使用前端Tiff.js插件直接浏览（需要浏览器支持）</span></span><br><span class="line"><span class="comment">#   jpg: 服务器转换为JPG格式后显示</span></span><br><span class="line"><span class="comment">#   pdf: 服务器转换为PDF格式显示</span></span><br><span class="line"><span class="attr">tif.preview.type</span> = <span class="string">$&#123;KK_TIF_PREVIEW_TYPE:tif&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># TIF文件处理线程数</span></span><br><span class="line"><span class="attr">tif.thread</span> = <span class="string">5</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># TIF文件处理超时时间（秒）</span></span><br><span class="line"><span class="attr">tif.timeout</span> = <span class="string">90</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="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="comment"># 媒体文件类型（音频、视频）</span></span><br><span class="line"><span class="attr">media</span> = <span class="string">$&#123;KK_MEDIA:mp3,wav,mp4,flv,mpd,m3u8,ts,mpeg,m4a&#125;</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="attr">convertMedias</span> = <span class="string">$&#123;KK_CONVERTMEDIAS:avi,mov,wmv,mkv,3gp,rm,mpeg&#125;</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="attr">media.timeout.enabled</span> = <span class="string">true</span></span><br><span class="line"><span class="attr">media.small.file.timeout</span> = <span class="string">30</span></span><br><span class="line"><span class="attr">media.medium.file.timeout</span> = <span class="string">60</span></span><br><span class="line"><span class="attr">media.large.file.timeout</span> = <span class="string">180</span></span><br><span class="line"><span class="attr">media.xl.file.timeout</span> = <span class="string">300</span></span><br><span class="line"><span class="attr">media.xxl.file.timeout</span> = <span class="string">600</span></span><br><span class="line"><span class="attr">media.xxxl.file.timeout</span> = <span class="string">1200</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 媒体文件转换最大大小（MB）</span></span><br><span class="line"><span class="attr">media.convert.max.size</span> = <span class="string">300</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否禁用视频格式转换功能，默认为false（禁用）</span></span><br><span class="line"><span class="comment"># ⚠️ 重要：视频转换非常消耗CPU和内存资源</span></span><br><span class="line"><span class="attr">media.convert.disable</span> = <span class="string">$&#123;KK_MEDIA_CONVERT_DISABLE:false&#125;</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="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="comment"># 预览生成资源的存储路径，默认为应用根路径下的file目录</span></span><br><span class="line"><span class="comment"># Windows示例：D:\\kkFileview\\（注意双反斜杠）</span></span><br><span class="line"><span class="comment"># Linux示例：/opt/kkfileview/file/</span></span><br><span class="line"><span class="comment"># 重要：确保应用有该目录的读写权限</span></span><br><span class="line"><span class="attr">file.dir</span> = <span class="string">$&#123;KK_FILE_DIR:default&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 允许预览的本地文件夹路径，默认为default（禁止所有本地文件预览）</span></span><br><span class="line"><span class="comment"># ⚠️ 安全警告：配置此路径可能允许访问系统文件，请谨慎配置</span></span><br><span class="line"><span class="comment"># Windows示例（注意前面加反斜杠）：\D:\\kkFileview\\1\\1.txt</span></span><br><span class="line"><span class="comment"># Linux示例（注意前面加正斜杠）：/opt/1.txt</span></span><br><span class="line"><span class="comment"># 使用file协议访问：file://d:/1/1.txt（Windows）或 file:/opt/1/1.txt（Linux）</span></span><br><span class="line"><span class="attr">local.preview.dir</span> = <span class="string">$&#123;KK_LOCAL_PREVIEW_DIR:default&#125;</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"># 默认true，开启缓存提高性能</span></span><br><span class="line"><span class="attr">cache.enabled</span> = <span class="string">$&#123;KK_CACHE_ENABLED:true&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 缓存实现类型，默认为jdk（使用JDK内置对象实现）</span></span><br><span class="line"><span class="comment"># 可选值：</span></span><br><span class="line"><span class="comment">#   jdk: JDK内置ConcurrentHashMap，单机部署推荐</span></span><br><span class="line"><span class="comment">#   redis: Redis分布式缓存，集群部署推荐</span></span><br><span class="line"><span class="comment">#   default: 内嵌RocksDB，支持持久化</span></span><br><span class="line"><span class="attr">cache.type</span> = <span class="string">$&#123;KK_CACHE_TYPE:jdk&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Redis部署模式，默认为single（单机模式）</span></span><br><span class="line"><span class="comment"># 可选值：</span></span><br><span class="line"><span class="comment">#   single: 单机模式（默认）</span></span><br><span class="line"><span class="comment">#   cluster: 集群模式</span></span><br><span class="line"><span class="comment">#   sentinel: 哨兵模式（高可用）</span></span><br><span class="line"><span class="comment">#   master-slave: 主从模式</span></span><br><span class="line"><span class="attr">spring.redisson.mode</span> = <span class="string">single</span></span><br><span class="line"><span class="comment"># Redis连接地址，支持多种格式：</span></span><br><span class="line"><span class="comment"># 单机模式：redis://127.0.0.1:6379</span></span><br><span class="line"><span class="comment"># 集群模式：redis://node1:6379,redis://node2:6379,redis://node3:6379</span></span><br><span class="line"><span class="comment"># 哨兵模式：redis://sentinel1:26379,redis://sentinel2:26379</span></span><br><span class="line"><span class="attr">spring.redisson.address</span> = <span class="string">$&#123;KK_SPRING_REDISSON_ADDRESS:redis://127.0.0.1:6379&#125;</span></span><br><span class="line"><span class="comment"># Redis连接密码，无密码时留空</span></span><br><span class="line"><span class="attr">spring.redisson.password</span> = <span class="string">$&#123;KK_SPRING_REDISSON_PASSWORD:&#125;</span></span><br><span class="line"><span class="comment"># Redis数据库索引，默认为0（0-15）</span></span><br><span class="line"><span class="comment"># 不同业务可使用不同数据库隔离</span></span><br><span class="line"><span class="attr">spring.redisson.database</span> = <span class="string">$&#123;KK_SPRING_REDISSON_DATABASE:0&#125;</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"># 是否启用缓存自动清理，默认为true（启用）</span></span><br><span class="line"><span class="comment"># 定期清理过期缓存，避免磁盘空间无限增长</span></span><br><span class="line"><span class="attr">cache.clean.enabled</span> = <span class="string">$&#123;KK_CACHE_CLEAN_ENABLED:true&#125;</span></span><br><span class="line"><span class="comment"># 缓存自动清理时间，使用Quartz cron表达式，默认为每天凌晨3点执行</span></span><br><span class="line"><span class="comment"># 表达式格式：秒 分 时 日 月 周 年（可选）</span></span><br><span class="line"><span class="comment"># 0 0 3 * * ? 表示每天3:00:00执行清理</span></span><br><span class="line"><span class="attr">cache.clean.cron</span> = <span class="string">$&#123;KK_CACHE_CLEAN_CRON:0 0 3 * * ?&#125;</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="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="comment"># 提供预览服务的地址，默认从请求url读，如果使用nginx等反向代理，需要手动设置</span></span><br><span class="line"><span class="comment"># base.url = https://file.keking.cn</span></span><br><span class="line"><span class="attr">base.url</span> = <span class="string">$&#123;KK_BASE_URL:default&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 信任站点白名单配置，多个用&#x27;,&#x27;隔开</span></span><br><span class="line"><span class="comment"># ⚠️ 安全提示：为防止SSRF攻击，强烈建议配置信任主机白名单</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"># trust.host = kkview.cn,yourdomain.com,cdn.example.com</span></span><br><span class="line"><span class="comment"># 如果需要允许所有域名（不推荐，仅用于测试环境），请设置为：</span></span><br><span class="line"><span class="comment"># trust.host = *</span></span><br><span class="line"><span class="comment"># 当前配置：默认本机测试 （正式启用请修改）</span></span><br><span class="line"><span class="attr">trust.host</span> = <span class="string">$&#123;KK_TRUST_HOST:default&#125;</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="comment"># 建议配置：禁止访问内网地址和本地地址，防止内部信息泄露</span></span><br><span class="line"><span class="comment"># 配置示例：</span></span><br><span class="line"><span class="comment"># not.trust.host = localhost,127.0.0.1,0.0.0.0,192.168.*,10.*,172.16.*,172.17.*,172.18.*,172.19.*,172.20.*,172.21.*,172.22.*,172.23.*,172.24.*,172.25.*,172.26.*,172.27.*,172.28.*,172.29.*,172.30.*,172.31.*</span></span><br><span class="line"><span class="attr">not.trust.host</span> = <span class="string">$&#123;KK_NOT_TRUST_HOST:default&#125;</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"># 支持动态配置，格式：exe,dll,dat</span></span><br><span class="line"><span class="attr">prohibit</span> = <span class="string">$&#123;KK_PROHIBIT:exe,dll,dat&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否忽略SSL证书验证，默认为true（忽略）</span></span><br><span class="line"><span class="comment"># 用于开发环境或自签名证书场景</span></span><br><span class="line"><span class="comment"># 生产环境建议设置为false，启用完整的证书验证</span></span><br><span class="line"><span class="attr">kk.ignore.ssl</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否启用URL重定向功能，默认为true（启用）</span></span><br><span class="line"><span class="comment"># 用于处理文件下载、外部资源引用等场景</span></span><br><span class="line"><span class="attr">kk.enable.redirect</span> = <span class="string">false</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="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="comment"># 水印文本内容</span></span><br><span class="line"><span class="comment"># 可以通过环境变量 WATERMARK_TXT 覆盖</span></span><br><span class="line"><span class="attr">watermark.txt</span> = <span class="string">$&#123;WATERMARK_TXT:&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 水印X轴间距</span></span><br><span class="line"><span class="comment"># 可以通过环境变量 WATERMARK_X_SPACE 覆盖</span></span><br><span class="line"><span class="attr">watermark.x.space</span> = <span class="string">$&#123;WATERMARK_X_SPACE:10&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 水印Y轴间距</span></span><br><span class="line"><span class="comment"># 可以通过环境变量 WATERMARK_Y_SPACE 覆盖</span></span><br><span class="line"><span class="attr">watermark.y.space</span> = <span class="string">$&#123;WATERMARK_Y_SPACE:10&#125;</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"># 可以通过环境变量 WATERMARK_FONT 覆盖</span></span><br><span class="line"><span class="attr">watermark.font</span> = <span class="string">$&#123;WATERMARK_FONT:微软雅黑&#125;</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"># 可以通过环境变量 WATERMARK_FONTSIZE 覆盖</span></span><br><span class="line"><span class="attr">watermark.fontsize</span> = <span class="string">$&#123;WATERMARK_FONTSIZE:18px&#125;</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"># 可以通过环境变量 WATERMARK_COLOR 覆盖</span></span><br><span class="line"><span class="attr">watermark.color</span> = <span class="string">$&#123;WATERMARK_COLOR:black&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 水印透明度，0.0-1.0</span></span><br><span class="line"><span class="comment"># 可以通过环境变量 WATERMARK_ALPHA 覆盖</span></span><br><span class="line"><span class="attr">watermark.alpha</span> = <span class="string">$&#123;WATERMARK_ALPHA:0.2&#125;</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"># 可以通过环境变量 WATERMARK_WIDTH 覆盖</span></span><br><span class="line"><span class="attr">watermark.width</span> = <span class="string">$&#123;WATERMARK_WIDTH:180&#125;</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"># 可以通过环境变量 WATERMARK_HEIGHT 覆盖</span></span><br><span class="line"><span class="attr">watermark.height</span> = <span class="string">$&#123;WATERMARK_HEIGHT:80&#125;</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"># 可以通过环境变量 WATERMARK_ANGLE 覆盖</span></span><br><span class="line"><span class="attr">watermark.angle</span> = <span class="string">$&#123;WATERMARK_ANGLE:10&#125;</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="comment"># 十、FTP文件访问配置（支持动态配置）</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"># FTP模块设置</span></span><br><span class="line"><span class="comment"># 预览源为FTP时，可在ftp url后面加参数?ftp.username=ftpuser&amp;ftp.password=123456&amp;ftp.control.encoding=GBK,指定,不指定默认用配置的 (为了安全我们强烈建议在配置中设置相关信息)</span></span><br><span class="line"><span class="comment"># ftp.control.encodin (根据FTP服务器操作系统选择，Linux一般为UTF-8，Windows一般为GBK)</span></span><br><span class="line"><span class="comment"># 使用方法,支持,分割第一个是域名或者IP地址后面是用户名在后面是密码,在后面是编码(用户名密码和编码用:分割, 域名用,分割),切记url不需要任何协议头</span></span><br><span class="line"><span class="comment"># ftp.username = 地址:端口:用户名:密码:编码,192.168.0.2:21:name:123456:UTF-8,www.xxx.com:21:admin:pass:UTF-8  多客户端,分割</span></span><br><span class="line"><span class="comment"># ftp.username =10.99.1.2:21:666:88888:GBK</span></span><br><span class="line"><span class="attr">ftp.username</span> = <span class="string">false</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="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="comment"># 是否禁用首页文件上传功能，默认为true（禁用）</span></span><br><span class="line"><span class="comment"># 设置为true可关闭上传功能，仅用于预览</span></span><br><span class="line"><span class="attr">file.upload.disable</span> = <span class="string">true</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="attr">beian</span> = <span class="string">$&#123;KK_BEIAN:default&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 首页初始化加载的页码，默认为1（第一页）</span></span><br><span class="line"><span class="attr">home.pagenumber</span> = <span class="string">$&#123;DEFAULT_HOME_PAGENUMBER:1&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 首页每页显示的文件数量，默认为20</span></span><br><span class="line"><span class="attr">home.pagesize</span> = <span class="string">$&#123;DEFAULT_HOME_PAGSIZE:20&#125;</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"># 是否启用验证码验证删除文件，默认为false（不启用）</span></span><br><span class="line"><span class="comment"># 启用后删除文件需要输入验证码，防止误删</span></span><br><span class="line"><span class="attr">delete.captcha</span> = <span class="string">$&#123;KK_DELETE_CAPTCHA:false&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 删除文件密码，默认为123456</span></span><br><span class="line"><span class="attr">delete.password</span> = <span class="string">$&#123;KK_DELETE_PASSWORD:123456&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否删除转换后的源文件，默认为true（删除）</span></span><br><span class="line"><span class="comment"># 启用可节约磁盘空间，但会丢失原始文件</span></span><br><span class="line"><span class="attr">delete.source.file</span> = <span class="string">$&#123;KK_DELETE_SOURCE_FILE:true&#125;</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="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="comment"># 是否启用图片预览权限，默认为true（启用）</span></span><br><span class="line"><span class="comment"># 设置为false可禁用所有图片预览功能</span></span><br><span class="line"><span class="attr">kk.Picturespreview</span> = <span class="string">true</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否启用跨域文件获取权限，默认为true（启用）</span></span><br><span class="line"><span class="attr">kk.Getcorsfile</span> = <span class="string">true</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否启用添加异步任务权限，默认为true（启用）</span></span><br><span class="line"><span class="comment"># 大文件转换通常使用异步任务处理</span></span><br><span class="line"><span class="attr">kk.addTask</span> = <span class="string">true</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># API密钥功能，默认为false（禁用）</span></span><br><span class="line"><span class="comment"># 启用后需要提供密钥才能调用API</span></span><br><span class="line"><span class="attr">kk.key</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># AES加密密钥，必须为16位字符</span></span><br><span class="line"><span class="comment"># 启用AES加密时，接入方需使用相同的密钥</span></span><br><span class="line"><span class="comment"># 用于敏感数据传输加密</span></span><br><span class="line"><span class="attr">aes.key</span> = <span class="string">1234567890123456</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Basic认证配置，格式：域名:用户名:密码，多个用逗号分隔</span></span><br><span class="line"><span class="comment"># 用于保护特定域名的访问</span></span><br><span class="line"><span class="comment"># 示例：192.168.0.1:admin:pass123,example.com:user:pass456</span></span><br><span class="line"><span class="attr">basic.name</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># User-Agent验证字符串，默认不启用</span></span><br><span class="line"><span class="comment"># 可用于简单的客户端验证</span></span><br><span class="line"><span class="attr">useragent</span> = <span class="string">false</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="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="comment"># 异步配置刷新定时时间（秒）</span></span><br><span class="line"><span class="attr">kk.refreshschedule</span> = <span class="string">2</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 首页是否显示AES密钥 默认为false（禁用）</span></span><br><span class="line"><span class="attr">kk.isshowaeskey</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否允许XLSX编辑</span></span><br><span class="line"><span class="attr">kk.xlsxallowedit</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 是否显示XLSX工具栏</span></span><br><span class="line"><span class="attr">kk.xlsxshowtoolbar</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 首页是否显示key密钥 默认为false（禁用）</span></span><br><span class="line"><span class="attr">kk.isshowkey</span> = <span class="string">false</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 预览html文件 是否启用JavaScript 默认为true（启用）</span></span><br><span class="line"><span class="attr">kk.scriptjs</span> = <span class="string">true</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="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="comment"># 纯文本文件类型，直接显示</span></span><br><span class="line"><span class="attr">simText</span> = <span class="string">$&#123;KK_SIMTEXT:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd&#125;</span></span><br></pre></td></tr></table></figure><h2 id="前端使用"><a href="#前端使用" class="headerlink" title="前端使用"></a>前端使用</h2><h2 id="普通文件地址预览"><a href="#普通文件地址预览" class="headerlink" title="普通文件地址预览"></a>普通文件地址预览</h2><p>适用于系统已经直接暴露出可下载文件地址的情况。前端只需要编码后打开新窗口。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> url = <span class="string">&#x27;http://127.0.0.1:8080/file/test.txt&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(<span class="string">&#x27;https://file.kkview.cn/onlinePreview?url=&#x27;</span> + <span class="built_in">encodeURIComponent</span>(<span class="title function_">base64Encode</span>(url)));</span><br></pre></td></tr></table></figure><h2 id="流式接口预览"><a href="#流式接口预览" class="headerlink" title="流式接口预览"></a>流式接口预览</h2><p>很多业务系统通过 <code>fileId</code>、<code>code</code> 等参数走统一下载接口，此时原始 URL 没有后缀名，需要额外指定完整文件名。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> originUrl = <span class="string">&#x27;http://127.0.0.1:8080/filedownload?fileId=1&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> previewUrl = originUrl + <span class="string">&#x27;&amp;fullfilename=test.txt&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(<span class="string">&#x27;https://file.kkview.cn/onlinePreview?url=&#x27;</span> + <span class="built_in">encodeURIComponent</span>(<span class="title class_">Base64</span>.<span class="title function_">encode</span>(previewUrl)));</span><br></pre></td></tr></table></figure><h2 id="FTP-资源预览"><a href="#FTP-资源预览" class="headerlink" title="FTP 资源预览"></a>FTP 资源预览</h2><p>FTP 如果允许匿名访问，可以直接预览；如果需要认证，则把连接参数拼到 URL 后面传入。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 匿名 FTP</span></span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&#x27;ftp://127.0.0.1/file/test.txt&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(<span class="string">&#x27;https://file.kkview.cn/onlinePreview?url=&#x27;</span> + <span class="built_in">encodeURIComponent</span>(<span class="title class_">Base64</span>.<span class="title function_">encode</span>(url)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 认证 FTP</span></span><br><span class="line"><span class="keyword">var</span> originUrl = <span class="string">&#x27;ftp://127.0.0.1/file/test.txt&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> previewUrl = originUrl + <span class="string">&#x27;?ftp.control.port=21&amp;ftp.username=admin&amp;ftp.password=123456&amp;ftp.control.encoding=utf8&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(<span class="string">&#x27;https://file.kkview.cn/onlinePreview?url=&#x27;</span> + <span class="built_in">encodeURIComponent</span>(<span class="title class_">Base64</span>.<span class="title function_">encode</span>(previewUrl)));</span><br></pre></td></tr></table></figure><h2 id="带-Basic-鉴权的-HTTP-资源"><a href="#带-Basic-鉴权的-HTTP-资源" class="headerlink" title="带 Basic 鉴权的 HTTP 资源"></a>带 Basic 鉴权的 HTTP 资源</h2><p>如果文件源本身需要用户名和密码，可以直接把 Basic 鉴权参数拼到地址中，再交给 kkFileView。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> originUrl = <span class="string">&#x27;http://127.0.0.1/file/test.txt&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> previewUrl = originUrl + <span class="string">&#x27;?basic.name=admin&amp;basic.pass=123456&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(<span class="string">&#x27;https://file.kkview.cn/onlinePreview?url=&#x27;</span> + <span class="built_in">encodeURIComponent</span>(<span class="title class_">Base64</span>.<span class="title function_">encode</span>(previewUrl)));</span><br></pre></td></tr></table></figure><h2 id="前后端同秘钥加密接入"><a href="#前后端同秘钥加密接入" class="headerlink" title="前后端同秘钥加密接入"></a>前后端同秘钥加密接入</h2><p>如果不希望明文传递原始文件地址，可以在前端先做 AES 加密，再通过 <code>encryption=aes</code> 告知服务端按 AES 方式解密。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://file.kkview.cn/js/crypto-js.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">script</span> <span class="attr">src</span>=<span class="string">&quot;https://file.kkview.cn/js/aes.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">function aesEncrypt(encryptString, key) &#123;</span><br><span class="line">  var keyBytes = CryptoJS.enc.Utf8.parse(key);</span><br><span class="line">  var srcs = CryptoJS.enc.Utf8.parse(encryptString);</span><br><span class="line">  var encrypted = CryptoJS.AES.encrypt(srcs, keyBytes, &#123;</span><br><span class="line">    mode: CryptoJS.mode.ECB,</span><br><span class="line">    padding: CryptoJS.pad.Pkcs7</span><br><span class="line">  &#125;);</span><br><span class="line">  return encrypted.toString();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var key = &#x27;1234567890123456&#x27;;</span><br><span class="line">var url = &#x27;http://127.0.0.1/file/test.txt&#x27;;</span><br><span class="line">window.open(&#x27;https://file.kkview.cn/onlinePreview?url=&#x27; + encodeURIComponent(aesEncrypt(url, key)) + &#x27;&amp;encryption=aes&#x27;);</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h2 id="预览乱码"><a href="#预览乱码" class="headerlink" title="预览乱码"></a>预览乱码</h2><p>大部分Linux系统上并没有预装中文字体或字体不全，需要把常用字体拷贝到Linux服务器上，具体操作如下： </p><ul><li>下载字体包 <a href="https://kkview.cn/resource/fonts.zip">https://kkview.cn/resource/fonts.zip</a> </li><li>文件解压完整拷贝到Linux下的 <code>/usr/share/fonts</code> 目录。</li><li>然后依次执行以下命令使字体生效<ul><li>mkfontscale</li><li>mkfontdir</li><li>fc-cache</li></ul></li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><iframe src='https://file.kkview.cn' style="width:100%;height:1000px;" frameborder="0"></iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20221021ov/</id>
    <link href="https://blog.zhangsifan.com/posts/20221021ov/"/>
    <published>2022-10-20T16:00:00.000Z</published>
    <summary>kkFileView为文件文档在线预览解决方案，该项目使用流行的spring boot搭建，易上手和部署，基本支持主流办公文档的在线预览，如doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,图片,视频,音频等等</summary>
    <title>利用 kkFileView 实现在线预览文件</title>
    <updated>2022-10-20T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="Web Office" scheme="https://blog.zhangsifan.com/categories/Web-Office/"/>
    <category term="WPS Office" scheme="https://blog.zhangsifan.com/tags/WPS-Office/"/>
    <content>
      <![CDATA[<p>WPS WebOffice 提供了网页版的 Office 文档在线查看与编辑能力，通过官方 JS-SDK 可以在浏览器中打开 WPS 文档，并支持在线编辑、保存、打印等操作。整体流程为：<strong>下载 JS-SDK → 引用 SDK → 后端提供文档链接与 token → 前端初始化</strong>。</p><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><ul><li><a href="https://open.wps.cn/">WPS 开放平台</a></li><li><a href="https://wwo.wps.cn/docs/">WebOffice 文档</a></li><li><a href="https://wwo.wps.cn/docs/update-log/jssdk/">JS-SDK 下载</a></li></ul><h2 id="下载-JS-SDK"><a href="#下载-JS-SDK" class="headerlink" title="下载 JS-SDK"></a>下载 JS-SDK</h2><p>在使用之前，请先下载最新版本的 js-sdk 代码。</p><h2 id="引用-JS-SDK"><a href="#引用-JS-SDK" class="headerlink" title="引用 JS-SDK"></a>引用 JS-SDK</h2><ul><li>非模块化</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;web-office-sdk.umd.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>CommonJS 规范</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">WebOfficeSDK</span> = <span class="built_in">require</span>(<span class="string">&quot;./web-office-sdk.cjs.js&quot;</span>);</span><br></pre></td></tr></table></figure><ul><li>非模块化</li></ul><figure class="highlight ts"><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> <span class="title class_">WebOfficeSDK</span> <span class="keyword">from</span> <span class="string">&quot;./web-office-sdk.es.js&quot;</span>;</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>核心步骤说明：</p><ol><li><code>WebOfficeSDK.config({ url, mount })</code>：初始化 SDK，<code>url</code> 为后端提供的文档地址，<code>mount</code> 指定挂载的 DOM 元素</li><li><code>wps.setToken({ token, timeout })</code>：设置鉴权 token，<code>timeout</code> 为 token 有效期（毫秒），到期后需重新设置</li><li>需在 <code>onMounted</code> 中调用，确保挂载容器已渲染完成</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;wps&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;custom-mount&quot;</span>&gt;</span><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">&lt;/template&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123; onMounted &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> <span class="title class_">WebOfficeSDK</span> <span class="keyword">from</span> <span class="string">&#x27;./web-office-sdk.es.js&#x27;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">init</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">const</span> url = <span class="string">&#x27;&#x27;</span>; <span class="comment">// 后端提供的链接</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">const</span> token = <span class="string">&quot;&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">const</span> wps = <span class="title class_">WebOfficeSDK</span>.<span class="title function_">config</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">url</span>: url,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">mount</span>: <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&quot;.custom-mount&quot;</span>)!,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  wps.<span class="title function_">setToken</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">token</span>: token,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">timeout</span>: <span class="number">10000</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="title function_">init</span>();</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;less&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="language-css"></span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.wps</span> &#123;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">height</span>: <span class="number">900px</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">background-color</span>: <span class="number">#66ccff</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="selector-class">.custom-mount</span> &#123;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">    <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">    <span class="attribute">height</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">&#125;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20221010bg/</id>
    <link href="https://blog.zhangsifan.com/posts/20221010bg/"/>
    <published>2022-10-09T16:00:00.000Z</published>
    <summary>使用WPS WEB Office,可以在浏览器中打开WPS Office文档,并且可以在线编辑,保存,打印等操作,本文将介绍如何在前端使用WPS WEB Office.</summary>
    <title>WPS WEB Office 前端使用教程</title>
    <updated>2022-10-09T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="使用文档" scheme="https://blog.zhangsifan.com/categories/%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3/"/>
    <category term="Element Plus" scheme="https://blog.zhangsifan.com/categories/%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3/Element-Plus/"/>
    <category term="文件夹上传" scheme="https://blog.zhangsifan.com/tags/%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%8A%E4%BC%A0/"/>
    <content>
      <![CDATA[<p>Element Plus 的 <code>el-upload</code> 组件默认只支持选择文件，通过操作其内部 <code>&lt;input&gt;</code> 的 <code>webkitdirectory</code> 属性，可以开启<strong>选择整个文件夹上传</strong>的功能，并且可以选择文件夹或单个文件两种模式切换。</p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><p><code>el-upload</code> 内部隐藏了一个 <code>&lt;input type=&quot;file&quot;&gt;</code>，通过 <code>document.querySelector(&quot;.el-upload__input&quot;)</code> 获取该元素，动态设置 <code>webkitdirectory</code> 属性即可切换文件夹选择模式：</p><ul><li><code>webkitdirectory = true</code>：选择文件夹（会展开该目录下所有文件）</li><li><code>webkitdirectory = false</code>：选择单个文件</li></ul><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">el-upload</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">multiple</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">action</span>=<span class="string">&quot;http://192.168.1.8:3030/file/upload&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">:auto-upload</span>=<span class="string">&quot;false&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">:show-file-list</span>=<span class="string">&quot;false&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">:on-change</span>=<span class="string">&quot;handleChange&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  &gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">template</span> #<span class="attr">trigger</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">el-button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span> @<span class="attr">click</span>=<span class="string">&quot;folderMode(false)&quot;</span>&gt;</span>上传文件<span class="tag">&lt;/<span class="name">el-button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">el-button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span> @<span class="attr">click</span>=<span class="string">&quot;folderMode(true)&quot;</span>&gt;</span>上传文件夹<span class="tag">&lt;/<span class="name">el-button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">el-upload</span>&gt;</span></span></span><br><span class="line">&lt;/template&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123; reactive, onMounted, nextTick &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> type &#123; <span class="title class_">UploadFile</span>, <span class="title class_">UploadFiles</span> &#125; <span class="keyword">from</span> <span class="string">&quot;element-plus&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> state = <span class="title function_">reactive</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">uploadEle</span>: <span class="literal">null</span> <span class="keyword">as</span> <span class="title class_">Element</span> | <span class="literal">null</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">uploadList</span>: [],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">folderMode</span> = (<span class="params">type: boolean</span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">if</span> (state.<span class="property">uploadEle</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    state.<span class="property">uploadEle</span>.<span class="property">webkitdirectory</span> = type;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">handleChange</span> = <span class="keyword">async</span> (<span class="params"></span></span></span></span><br><span class="line"><span class="params"><span class="language-javascript"><span class="language-xml">  uploadFile: UploadFile,</span></span></span></span><br><span class="line"><span class="params"><span class="language-javascript"><span class="language-xml">  uploadFiles: UploadFiles</span></span></span></span><br><span class="line"><span class="params"><span class="language-javascript"><span class="language-xml"></span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="variable language_">console</span>.<span class="title function_">log</span>(uploadFile, uploadFiles);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    state.<span class="property">uploadEle</span> = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&quot;.el-upload__input&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;less&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h2 id="代码说明"><a href="#代码说明" class="headerlink" title="代码说明"></a>代码说明</h2><ul><li><code>onMounted</code> 中通过 <code>nextTick</code> 等待组件渲染完成后，用 <code>.el-upload__input</code> 拿到内部文件选择框</li><li><code>folderMode(true)</code> &#x2F; <code>folderMode(false)</code> 两个按钮分别切换「上传文件夹」和「上传文件」模式</li><li><code>handleChange</code> 中 <code>uploadFiles</code> 是本次选择的文件列表，文件夹模式下会包含所有子文件，可在此进行上传处理</li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><iframe src="https://codesandbox.io/p/sandbox/element-upload-n73szt?file=%2Fsrc%2FApp.vue&embed=1"     style="width:100%; height: 800px; border:0; border-radius: 4px; overflow:hidden;"     title="element-upload"     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"   ></iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20220915up/</id>
    <link href="https://blog.zhangsifan.com/posts/20220915up/"/>
    <published>2022-09-14T16:00:00.000Z</published>
    <summary>为 El-upload 组件 开启文件夹上传功能</summary>
    <title>El-upload 开启上传文件夹功能</title>
    <updated>2022-09-14T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="使用文档" scheme="https://blog.zhangsifan.com/categories/%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3/"/>
    <category term="Element Plus" scheme="https://blog.zhangsifan.com/categories/%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3/Element-Plus/"/>
    <category term="Element-Plus" scheme="https://blog.zhangsifan.com/tags/Element-Plus/"/>
    <category term="拖拽" scheme="https://blog.zhangsifan.com/tags/%E6%8B%96%E6%8B%BD/"/>
    <content>
      <![CDATA[<p><a href="https://github.com/SortableJS/vue.draggable.next">官方文档</a></p><p><code>vuedraggable</code> 是基于 Sortable.js 的 Vue 拖拽排序组件，支持列表拖拽排序、跨列表拖拽、拖拽动画等能力，Vue3 版本使用 <code>@next</code> 标签安装。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">yarn add vuedraggable@next</span><br><span class="line"></span><br><span class="line">npm i -S vuedraggable@next</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">draggable</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">:list</span>=<span class="string">&quot;state.form_list&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">ghost-class</span>=<span class="string">&quot;ghost&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">:force-fallback</span>=<span class="string">&quot;true&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">chosen-class</span>=<span class="string">&quot;chosenClass&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">animation</span>=<span class="string">&quot;200&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    @<span class="attr">start</span>=<span class="string">&quot;onStart&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    @<span class="attr">end</span>=<span class="string">&quot;onEnd&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  &gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">template</span> #<span class="attr">item</span>=<span class="string">&quot;&#123; element, index &#125;&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;item_box&quot;</span>&gt;</span>&#123;&#123; element &#125;&#125;&#123;&#123; index &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">draggable</span>&gt;</span></span></span><br><span class="line">&lt;/template&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> draggable <span class="keyword">from</span> <span class="string">&quot;vuedraggable&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123; reactive, onMounted &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> state = <span class="title function_">reactive</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">form_list</span>: [&#123; <span class="attr">title</span>: <span class="string">&quot;肯德基&quot;</span> &#125;, &#123; <span class="attr">title</span>: <span class="string">&quot;麦当劳&quot;</span> &#125;],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="comment">//开始拖拽事件</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">onStart</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="comment">//结束拖拽事件</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">onEnd</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;less&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h2 id="属性说明"><a href="#属性说明" class="headerlink" title="属性说明"></a>属性说明</h2><table><thead><tr><th>属性</th><th>说明</th></tr></thead><tbody><tr><td><code>list</code></td><td>绑定拖拽排序的数组，拖拽结束后会自动更新顺序</td></tr><tr><td><code>animation</code></td><td>拖拽动画时长（ms）</td></tr><tr><td><code>ghost-class</code></td><td>拖拽时占位元素的 class</td></tr><tr><td><code>chosen-class</code></td><td>被选中元素的 class</td></tr><tr><td><code>force-fallback</code></td><td>强制使用 fallback 拖拽模式（移动端兼容）</td></tr><tr><td><code>@start</code> &#x2F; <code>@end</code></td><td>拖拽开始 &#x2F; 结束事件</td></tr></tbody></table><blockquote><p>拖拽结束（<code>@end</code>）后 <code>list</code> 数组顺序已被组件自动更新，直接使用即可，无需手动处理排序逻辑。</p></blockquote><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><iframe src="https://codesandbox.io/p/sandbox/vue-draggable-4knfkf?file=%2Fsrc%2FApp.vue&embed=1"     style="width:100%; height: 800px; border:0; border-radius: 4px; overflow:hidden;"     title="vue.draggable"     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"   ></iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20220909px/</id>
    <link href="https://blog.zhangsifan.com/posts/20220909px/"/>
    <published>2022-09-08T16:00:00.000Z</published>
    <summary>vue.draggable vue3-拖拽排序组件</summary>
    <title>vue.draggable vue3-拖拽排序组件</title>
    <updated>2022-09-08T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="地图类" scheme="https://blog.zhangsifan.com/categories/%E5%9C%B0%E5%9B%BE%E7%B1%BB/"/>
    <category term="高德地图" scheme="https://blog.zhangsifan.com/tags/%E9%AB%98%E5%BE%B7%E5%9C%B0%E5%9B%BE/"/>
    <content>
      <![CDATA[<p>在 Vue3 项目中使用高德地图 JSAPI 2.0，官方推荐通过 <code>@amap/amap-jsapi-loader</code> 按需加载地图脚本。本文介绍从申请 Key 到封装组件的完整流程。</p><blockquote><p><a href="https://lbs.amap.com/api/jsapi-v2/guide/webcli/map-vue1">官方文档</a></p></blockquote><h2 id="申请高德-Key"><a href="#申请高德-Key" class="headerlink" title="申请高德 Key"></a>申请高德 Key</h2><ol><li>打开 <a href="https://console.amap.com/dev/key/app">高德开放平台</a> 并登录</li><li>创建应用，服务平台选择「Web端（JS API）」</li><li>获取 Key 与安全密钥（新版需要配置代理服务或安全密钥，用于 JSAPI 2.0 鉴权）</li></ol><h2 id="安装-Loader"><a href="#安装-Loader" class="headerlink" title="安装 Loader"></a>安装 Loader</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 i @amap/amap-jsapi-loader</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><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></pre></td><td class="code"><pre><span class="line">&lt;!-- <span class="title class_">MyMap</span>.<span class="property">vue</span> --&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">:id</span>=<span class="string">&quot;state.id&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> &#123; onMounted, reactive &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">import</span> <span class="title class_">AMapLoader</span> <span class="keyword">from</span> <span class="string">&quot;@amap/amap-jsapi-loader&quot;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> props = <span class="title function_">defineProps</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">modelValue</span>: <span class="literal">null</span>, <span class="comment">// 坐标</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> state = <span class="title function_">reactive</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">id</span>: <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">map</span>: <span class="literal">null</span> <span class="keyword">as</span> any,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">const</span> <span class="title function_">initMap</span> = (<span class="params"></span>) =&gt; &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  state.<span class="property">id</span> =</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title class_">Math</span>.<span class="title function_">random</span>().<span class="title function_">toString</span>(<span class="number">36</span>).<span class="title function_">substring</span>(<span class="number">7</span>) + <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">100</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="title class_">AMapLoader</span>.<span class="title function_">load</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">key</span>: <span class="string">&quot;&quot;</span>, <span class="comment">// 申请好的Web端开发者Key，首次调用 load 时必填</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">version</span>: <span class="string">&quot;2.0&quot;</span>, <span class="comment">// 指定要加载的 JSAPI 的版本</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="attr">plugins</span>: [<span class="string">&quot;AMap.Scale&quot;</span>, <span class="string">&quot;AMap.Marker&quot;</span>], <span class="comment">// 需要使用的的插件列表</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;)</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    .<span class="title function_">then</span>(<span class="function">(<span class="params">AMap</span>) =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      state.<span class="property">map</span> = <span class="keyword">new</span> <span class="title class_">AMap</span>.<span class="title class_">Map</span>(state.<span class="property">id</span>, &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="comment">//设置地图容器id</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">viewMode</span>: <span class="string">&quot;3D&quot;</span>, <span class="comment">//是否为3D地图模式</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">zoom</span>: <span class="number">18</span>, <span class="comment">//初始化地图级别</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">center</span>: props.<span class="property">modelValue</span>, <span class="comment">//初始化地图中心点位置</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">doubleClickZoom</span>: <span class="literal">false</span>, <span class="comment">// 禁止双击放大地图</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="attr">layers</span>: [],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="keyword">if</span> (state.<span class="property">map</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="comment">//   比例尺</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">const</span> scale = <span class="keyword">new</span> <span class="title class_">AMap</span>.<span class="title class_">Scale</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">          <span class="attr">visible</span>: <span class="literal">true</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        state.<span class="property">map</span>.<span class="title function_">addControl</span>(scale);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="comment">// 点</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">const</span> marker = <span class="keyword">new</span> <span class="title class_">AMap</span>.<span class="title class_">Marker</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">          <span class="attr">position</span>: props.<span class="property">modelValue</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        state.<span class="property">map</span>.<span class="title function_">add</span>(marker);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;)</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    .<span class="title function_">catch</span>(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="variable language_">console</span>.<span class="title function_">log</span>(e);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="title function_">initMap</span>();</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;less&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="language-css"></span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-id">#container</span> &#123;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">padding</span>: <span class="number">0px</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">margin</span>: <span class="number">0px</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">width</span>: <span class="number">100%</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">  <span class="attribute">height</span>: <span class="number">800px</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">&#125;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h2 id="代码说明"><a href="#代码说明" class="headerlink" title="代码说明"></a>代码说明</h2><ul><li><strong>随机容器 id</strong>：每次初始化生成随机 id 并作为容器，避免多实例（如列表循环）时 id 冲突导致地图渲染异常</li><li><strong><code>AMapLoader.load</code></strong>：按需加载 JSAPI 脚本，<code>key</code> 填入申请到的 Web 端 Key，<code>version</code> 指定版本，<code>plugins</code> 声明需要使用的插件</li><li><strong><code>new AMap.Map</code></strong>：创建地图实例，<code>center</code> 传入经纬度数组（如 <code>[116.39, 39.9]</code>），<code>viewMode: &quot;3D&quot;</code> 开启 3D 视角</li><li><strong><code>AMap.Scale</code> &#x2F; <code>AMap.Marker</code></strong>：通过 <code>addControl</code> 添加比例尺，通过 <code>add</code> 在地图上添加标记点</li></ul><h2 id="页面中使用"><a href="#页面中使用" class="headerlink" title="页面中使用"></a>页面中使用</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;MyMap v-model=&quot;[116.397428, 39.90923]&quot; /&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>AMap.Map</code> 必须在容器<strong>已挂载且有宽高</strong>后创建，因此初始化逻辑放在 <code>onMounted</code> 中执行；如果容器是异步渲染（如弹窗），需要等 DOM 渲染完成后（<code>nextTick</code>）再调用。</p></blockquote>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20220905ap/</id>
    <link href="https://blog.zhangsifan.com/posts/20220905ap/"/>
    <published>2022-09-04T16:00:00.000Z</published>
    <summary>Vue3 使用高德地图 JSAPI 2.0 的完整教程：申请 Key、安装 Loader、封装地图组件</summary>
    <title>Vue3使用高德地图</title>
    <updated>2022-09-04T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="Nprogress" scheme="https://blog.zhangsifan.com/categories/Nprogress/"/>
    <category term="Nprogress" scheme="https://blog.zhangsifan.com/tags/Nprogress/"/>
    <content>
      <![CDATA[<p><a href="https://www.npmjs.com/package/nprogress">官方文档</a></p><p>NProgress 是一个轻量的<strong>页面顶部加载进度条</strong>库，常用于 SPA 路由切换、Ajax 请求时提示用户”页面正在加载”。它依赖 <code>requestAnimationFrame</code> 自动添加过渡动画，体积小、无依赖。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</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 nprogress</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;nprogress/nprogress.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">NProgress</span> <span class="keyword">from</span> <span class="string">&quot;nprogress&quot;</span>;</span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">configure</span>(&#123; <span class="attr">showSpinner</span>: <span class="literal">false</span> &#125;);</span><br><span class="line"><span class="comment">// 显示进度条</span></span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">start</span>();</span><br><span class="line"><span class="comment">// 隐藏进度条</span></span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">done</span>();</span><br><span class="line"></span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">set</span>(<span class="number">0.0</span>);     <span class="comment">// Sorta same as .start()</span></span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">set</span>(<span class="number">0.4</span>);</span><br><span class="line"><span class="title class_">NProgress</span>.<span class="title function_">set</span>(<span class="number">1.0</span>);     <span class="comment">// Sorta same as .done()</span></span><br></pre></td></tr></table></figure><h2 id="在-Vue-Router-中使用"><a href="#在-Vue-Router-中使用" class="headerlink" title="在 Vue Router 中使用"></a>在 Vue Router 中使用</h2><figure class="highlight ts"><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">router.<span class="title function_">beforeEach</span>(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title class_">NProgress</span>.<span class="title function_">start</span>(); <span class="comment">// 路由跳转前显示进度条</span></span><br><span class="line">  <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br><span class="line">router.<span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title class_">NProgress</span>.<span class="title function_">done</span>(); <span class="comment">// 跳转完成后关闭</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="常用配置"><a href="#常用配置" class="headerlink" title="常用配置"></a>常用配置</h2><figure class="highlight ts"><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="title class_">NProgress</span>.<span class="title function_">configure</span>(&#123;</span><br><span class="line">  <span class="attr">showSpinner</span>: <span class="literal">false</span>, <span class="comment">// 关闭右上角加载图标</span></span><br><span class="line">  <span class="attr">minimum</span>: <span class="number">0.1</span>, <span class="comment">// 起始最小百分比</span></span><br><span class="line">  <span class="attr">easing</span>: <span class="string">&quot;ease&quot;</span>, <span class="comment">// 动画缓动方式</span></span><br><span class="line">  <span class="attr">speed</span>: <span class="number">200</span>, <span class="comment">// 动画速度</span></span><br><span class="line">  <span class="attr">trickleSpeed</span>: <span class="number">200</span>, <span class="comment">// 自动递增间隔</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="API-一览"><a href="#API-一览" class="headerlink" title="API 一览"></a>API 一览</h2><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>NProgress.start()</code></td><td>开始显示进度条，从 0 开始自动递增</td></tr><tr><td><code>NProgress.done()</code></td><td>进度到 100% 并隐藏进度条</td></tr><tr><td><code>NProgress.set(n)</code></td><td>手动设置进度值（0 ~ 1）</td></tr><tr><td><code>NProgress.inc(n?)</code></td><td>按指定量递增（默认随机小增量）</td></tr><tr><td><code>NProgress.configure(obj)</code></td><td>全局配置（是否显示 spinner、动画参数等）</td></tr></tbody></table><h2 id="自定义进度条样式"><a href="#自定义进度条样式" class="headerlink" title="自定义进度条样式"></a>自定义进度条样式</h2><p>NProgress 默认显示在页面顶部，可通过 CSS 修改 <code>#nprogress .bar</code> 的高度与颜色，使其融入站点主题：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#nprogress</span> <span class="selector-class">.bar</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#409eff</span>; <span class="comment">/* 改为站点主题色 */</span></span><br><span class="line">  <span class="attribute">height</span>: <span class="number">3px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="CodePen-示例"><a href="#CodePen-示例" class="headerlink" title="CodePen 示例"></a>CodePen 示例</h2><iframe height="300" style="width: 100%;" scrolling="no" title="nprogress使用教程" src="https://codepen.io/AlwaysTeam/embed/wBKPWWz?default-tab=js%2Cresult&editable=true" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">  See the Pen <a href="https://codepen.io/AlwaysTeam/pen/wBKPWWz">  nprogress使用教程</a> by AlwaysTeam (<a href="https://codepen.io/AlwaysTeam">@AlwaysTeam</a>)  on <a href="https://codepen.io">CodePen</a>.</iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20220903nr/</id>
    <link href="https://blog.zhangsifan.com/posts/20220903nr/"/>
    <published>2022-09-02T16:00:00.000Z</published>
    <summary>Nprogress使用教程</summary>
    <title>Nprogress使用教程</title>
    <updated>2022-09-02T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>JCAlways</name>
    </author>
    <category term="验证类" scheme="https://blog.zhangsifan.com/categories/%E9%AA%8C%E8%AF%81%E7%B1%BB/"/>
    <category term="Fingerprintjs" scheme="https://blog.zhangsifan.com/tags/Fingerprintjs/"/>
    <content>
      <![CDATA[<p><a href="https://fingerprint.com/">官方网站</a></p><p>FingerprintJS 是一个<strong>浏览器指纹识别</strong>库，通过采集浏览器环境信息（UA、Canvas、WebGL、字体、时区等）生成稳定的 <code>visitorId</code>，可用于无感防刷、风险控制、账号安全等场景。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</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 i @fingerprintjs/fingerprintjs</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">FingerprintJS</span> <span class="keyword">from</span> <span class="string">&quot;@fingerprintjs/fingerprintjs&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getFingerPrintID</span> =  <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> fpPromise = <span class="keyword">await</span> <span class="title class_">FingerprintJS</span>.<span class="title function_">load</span>();</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> fpPromise.<span class="title function_">get</span>();</span><br><span class="line">    <span class="keyword">return</span> result.<span class="property">visitorId</span>;</span><br><span class="line">&#125;,</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">await</span> <span class="title function_">getFingerPrintID</span>())</span><br><span class="line">&#125;, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="代码说明"><a href="#代码说明" class="headerlink" title="代码说明"></a>代码说明</h2><ul><li><code>FingerprintJS.load()</code>：加载指纹计算模块，<strong>只需加载一次</strong>，可存入 Promise 复用</li><li><code>fpPromise.get()</code>：返回指纹结果对象，其中 <code>visitorId</code> 即为该浏览器的唯一标识</li><li>同一浏览器在<strong>不同时间</strong>、<strong>不同页面</strong>获取的 <code>visitorId</code> 保持一致，但清除浏览器数据或更换设备后可能变化</li></ul><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><ul><li><strong>无感登录</strong>：记录用户首次访问生成的 <code>visitorId</code>，下次访问自动识别老用户</li><li><strong>防刷验证</strong>：投票、抽奖、评论等场景通过 <code>visitorId</code> 限制同一设备操作次数</li><li><strong>风控识别</strong>：检测批量注册、撞库、爬虫等异常行为</li></ul><h2 id="与-Cookie-的区别"><a href="#与-Cookie-的区别" class="headerlink" title="与 Cookie 的区别"></a>与 Cookie 的区别</h2><p>Cookie 存储在浏览器本地，用户清除浏览器数据后即失效，且可被隐私模式拦截；而 FingerprintJS 通过硬件与浏览器环境特征生成标识，即使清除 Cookie 也能再次识别，稳定性更高。</p><h2 id="CodePen-示例"><a href="#CodePen-示例" class="headerlink" title="CodePen 示例"></a>CodePen 示例</h2><iframe height="300" style="width: 100%;" scrolling="no" title="Fingerprintjs使用教程" src="https://codepen.io/AlwaysTeam/embed/XWOgZqW?default-tab=js%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">  See the Pen <a href="https://codepen.io/AlwaysTeam/pen/XWOgZqW">  Fingerprintjs使用教程</a> by AlwaysTeam (<a href="https://codepen.io/AlwaysTeam">@AlwaysTeam</a>)  on <a href="https://codepen.io">CodePen</a>.</iframe>]]>
    </content>
    <id>https://blog.zhangsifan.com/posts/20220903sg/</id>
    <link href="https://blog.zhangsifan.com/posts/20220903sg/"/>
    <published>2022-09-02T16:00:00.000Z</published>
    <summary>Fingerprintjs使用教程</summary>
    <title>Fingerprintjs使用教程</title>
    <updated>2022-09-02T16:00:00.000Z</updated>
  </entry>
</feed>
