<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>猪蹄宝宝</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.gadfly.vip/</id>
  <link href="https://blog.gadfly.vip/" rel="alternate"/>
  <link href="https://blog.gadfly.vip/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 猪蹄宝宝</rights>
  <subtitle>有人问我，我就会讲，但是无人来</subtitle>
  <title>猪蹄宝宝的博客n(*≧▽≦*)n</title>
  <updated>2026-04-13T18:01:56.000Z</updated>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="Claude" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/Claude/"/>
    <category term="Claude" scheme="https://blog.gadfly.vip/tags/Claude/"/>
    <category term="SDK" scheme="https://blog.gadfly.vip/tags/SDK/"/>
    <category term="User-Agent" scheme="https://blog.gadfly.vip/tags/User-Agent/"/>
    <category term="CLI" scheme="https://blog.gadfly.vip/tags/CLI/"/>
    <category term="身份伪装" scheme="https://blog.gadfly.vip/tags/%E8%BA%AB%E4%BB%BD%E4%BC%AA%E8%A3%85/"/>
    <content>
      <![CDATA[<p>Claude Agent SDK 在调用 API 时会暴露自己是 SDK 的身份，导致 Claude 在 system prompt 中声称自己是 &quot;running within the Claude Agent SDK&quot;，而不是官方 CLI。如果你想让 Claude 表现得像原生 CLI，需要从多个层面进行身份伪装。</p><span id="more"></span><h2 id="问题背景">问题背景</h2><h3 id="遇到的实际问题">遇到的实际问题</h3><p>在维护 <a href="https://github.com/zhukunpenglinyutong/jetbrains-cc-gui">jetbrains-cc-gui</a> 项目时，我发现通过 Claude Agent SDK 调用 Claude Code 时，系统提示词会变成：</p><pre class="line-numbers language-text" data-language="text"><code class="language-text">You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>或者：</p><pre class="line-numbers language-text" data-language="text"><code class="language-text">You are a Claude agent, built on Anthropic's Claude Agent SDK.<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>而智谱（作为 Anthropic API 的代理服务商）对系统提示词有严格要求：必须开头为 <code>You are Claude Code, Anthropic's official CLI for Claude.</code>，否则会返回速率限制错误：</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span><span class="token property">"error"</span><span class="token operator">:</span><span class="token punctuation">&#123;</span><span class="token property">"code"</span><span class="token operator">:</span><span class="token string">"1302"</span><span class="token punctuation">,</span><span class="token property">"message"</span><span class="token operator">:</span><span class="token string">"您的账户已达到速率限制，请您控制请求频率"</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token property">"request_id"</span><span class="token operator">:</span><span class="token string">"2026041314372115099c8b3a024339"</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>这个错误看起来像是速率限制，但实际上是因为系统提示词不符合智谱的要求而被拒绝。</p><h3 id="CLI-与-SDK-的身份差异">CLI 与 SDK 的身份差异</h3><p>在 <code>cli.js</code>（位于 <code>~/.codemoss/dependencies/claude-sdk/node_modules</code>）中，<code>gv8()</code> 函数根据运行模式选择不同的 system prompt 首句：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">gv8</span><span class="token punctuation">(</span><span class="token parameter">q</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">cq</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">"vertex"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> Fh1<span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>q<span class="token operator">?.</span>isNonInteractive<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>q<span class="token punctuation">.</span>hasAppendSystemPrompt<span class="token punctuation">)</span> <span class="token keyword">return</span> feq<span class="token punctuation">;</span>    <span class="token keyword">return</span> Zeq<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> Fh1<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>三个变体对应不同的身份声明：</p><table><thead><tr><th>变量</th><th>模式</th><th>提示词</th><th>是否暴露 SDK</th></tr></thead><tbody><tr><td>Fh1</td><td>CLI / Vertex</td><td><code>You are Claude Code, Anthropic's official CLI for Claude.</code></td><td>否</td></tr><tr><td>feq</td><td>SDK 非交互 + hasAppendSystemPrompt</td><td><code>You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.</code></td><td>是</td></tr><tr><td>Zeq</td><td>SDK 非交互（无 append）</td><td><code>You are a Claude agent, built on Anthropic's Claude Agent SDK.</code></td><td>是</td></tr></tbody></table><h3 id="API-层面的身份暴露">API 层面的身份暴露</h3><p>除了 system prompt，SDK 还会在 API 请求中暴露身份：</p><ol><li><strong>User-Agent Header</strong>：SDK 会发送包含 <code>agent-sdk</code> 版本的信息</li><li><strong>环境变量</strong>：<code>CLAUDE_AGENT_SDK_VERSION</code> 和 <code>CLAUDE_CODE_ENTRYPOINT=sdk-ts</code> 会被传递给子进程</li><li><strong>请求头</strong>：缺少 CLI 特有的 <code>x-app: cli</code> header</li></ol><h2 id="解决方案">解决方案</h2><p>CLI 身份伪装需要从两个层面入手：<strong>System Prompt 层面</strong> 和 <strong>API 请求层面</strong>。前者解决 Claude 自述身份的问题，后者解决 API 识别 SDK 的问题。两者可以独立使用，也可以组合使用。</p><h3 id="System-Prompt-层面（方案一-方案二，任选其一）">System Prompt 层面（方案一/方案二，任选其一）</h3><p>SDK 在 <code>cli.js</code> 中通过 <code>gv8()</code> 函数根据运行模式选择不同的身份声明，导致非交互模式下 Claude 自称是 &quot;running within the Claude Agent SDK&quot;。有两种解决方式：</p><h4 id="方案一：修改-cli-js（直接破解）">方案一：修改 cli.js（直接破解）</h4><p>直接修改 SDK 的 <code>cli.js</code>，将 <code>feq</code> 和 <code>Zeq</code> 的值改为与 <code>Fh1</code> 一致：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// cli.js 修改前</span>feq<span class="token operator">=</span><span class="token string">"You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."</span>Zeq<span class="token operator">=</span><span class="token string">"You are a Claude agent, built on Anthropic's Claude Agent SDK."</span><span class="token comment">// cli.js 修改后</span>feq<span class="token operator">=</span><span class="token string">"You are Claude Code, Anthropic's official CLI for Claude."</span>Zeq<span class="token operator">=</span><span class="token string">"You are Claude Code, Anthropic's official CLI for Claude."</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这种方式简单粗暴，但每次 SDK 更新都需要重新修改。</p><h4 id="方案二：包装-System-Prompt（推荐）">方案二：包装 System Prompt（推荐）</h4><p>通过 preset append 形式强行追加身份约束，不修改 SDK 源码：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">CLAUDE_CODE_IDENTITY_APPEND_PROMPT</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">## Runtime IdentityYou are Claude Code, Anthropic's official CLI for Claude.When you describe your identity or runtime, use that phrasing. Do not proactively present yourself as a Claude Agent SDK agent unless the user explicitly asks about implementation details.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">wrapClaudeSdkSystemPrompt</span><span class="token punctuation">(</span><span class="token parameter">systemPrompt</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> sections <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token constant">CLAUDE_CODE_IDENTITY_APPEND_PROMPT</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> systemPrompt <span class="token operator">===</span> <span class="token string">"string"</span> <span class="token operator">&amp;&amp;</span> systemPrompt<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    sections<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>systemPrompt<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>    systemPrompt <span class="token operator">&amp;&amp;</span>    <span class="token keyword">typeof</span> systemPrompt <span class="token operator">===</span> <span class="token string">"object"</span> <span class="token operator">&amp;&amp;</span>    systemPrompt<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">"preset"</span> <span class="token operator">&amp;&amp;</span>    <span class="token keyword">typeof</span> systemPrompt<span class="token punctuation">.</span>append <span class="token operator">===</span> <span class="token string">"string"</span> <span class="token operator">&amp;&amp;</span>    systemPrompt<span class="token punctuation">.</span>append<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    sections<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>systemPrompt<span class="token punctuation">.</span>append<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"preset"</span><span class="token punctuation">,</span>    <span class="token literal-property property">append</span><span class="token operator">:</span> sections<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">"\n\n"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在调用 SDK 时，把原来的 <code>systemPrompt: config.systemPrompt</code> 改成：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token literal-property property">systemPrompt</span><span class="token operator">:</span> <span class="token function">wrapClaudeSdkSystemPrompt</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>systemPrompt<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="API-请求层面（方案三，可与方案一-方案二组合使用）">API 请求层面（方案三，可与方案一/方案二组合使用）</h3><p>这是 <a href="https://github.com/zhukunpenglinyutong/jetbrains-cc-gui">jetbrains-cc-gui</a> 项目采用的方案（完整实现代码可参考提交 <a href="https://github.com/zhukunpenglinyutong/jetbrains-cc-gui/commit/1aaec32b80fde5fb693acdcbfce41fe9b499af69">1aaec32</a>），从多个层面伪装 CLI 身份。</p><h4 id="1-配置环境变量">1. 配置环境变量</h4><p>在进程启动时配置 CLI 身份环境变量：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">configureCliIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">CLAUDE_CODE_ENTRYPOINT</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">CLAUDE_CODE_ENTRYPOINT</span> <span class="token operator">=</span> <span class="token string">'cli'</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">USER_TYPE</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">USER_TYPE</span> <span class="token operator">=</span> <span class="token string">'external'</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">delete</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">CLAUDE_AGENT_SDK_VERSION</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-构建-CLI-风格的-User-Agent">2. 构建 CLI 风格的 User-Agent</h4><p>从 SDK 的 <code>manifest.json</code> 动态获取 CLI 版本：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">FALLBACK_CLI_VERSION</span> <span class="token operator">=</span> <span class="token string">'2.1.88'</span><span class="token punctuation">;</span><span class="token keyword">let</span> _cachedCliVersion <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">resolveCliVersionFromSdk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>_cachedCliVersion<span class="token punctuation">)</span> <span class="token keyword">return</span> _cachedCliVersion<span class="token punctuation">;</span>  <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> depsBase <span class="token operator">=</span> <span class="token function">join</span><span class="token punctuation">(</span><span class="token function">getCodemossDir</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'dependencies'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> sdkDir <span class="token operator">=</span> <span class="token function">join</span><span class="token punctuation">(</span>depsBase<span class="token punctuation">,</span> <span class="token string">'claude-sdk'</span><span class="token punctuation">,</span> <span class="token string">'node_modules'</span><span class="token punctuation">,</span> <span class="token string">'@anthropic-ai'</span><span class="token punctuation">,</span> <span class="token string">'claude-agent-sdk'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// Try manifest.json first (contains the bundled CLI version)</span>    <span class="token keyword">const</span> manifestPath <span class="token operator">=</span> <span class="token function">join</span><span class="token punctuation">(</span>sdkDir<span class="token punctuation">,</span> <span class="token string">'manifest.json'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">existsSync</span><span class="token punctuation">(</span>manifestPath<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> manifest <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>manifestPath<span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>manifest<span class="token operator">?.</span>version<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        _cachedCliVersion <span class="token operator">=</span> manifest<span class="token punctuation">.</span>version<span class="token punctuation">;</span>        <span class="token keyword">return</span> _cachedCliVersion<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// Fallback: derive from SDK package.json version (0.x.y -> x.1.y)</span>    <span class="token comment">// e.g., SDK 0.2.88 -> CLI 2.1.88</span>    <span class="token keyword">const</span> pkgPath <span class="token operator">=</span> <span class="token function">join</span><span class="token punctuation">(</span>sdkDir<span class="token punctuation">,</span> <span class="token string">'package.json'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">existsSync</span><span class="token punctuation">(</span>pkgPath<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> pkg <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>pkgPath<span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>pkg<span class="token operator">?.</span>version<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">const</span> parts <span class="token operator">=</span> pkg<span class="token punctuation">.</span>version<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>parts<span class="token punctuation">.</span>length <span class="token operator">>=</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          _cachedCliVersion <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>parts<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.1.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>parts<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>          <span class="token keyword">return</span> _cachedCliVersion<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// Ignore errors, use fallback</span>  <span class="token punctuation">&#125;</span>  _cachedCliVersion <span class="token operator">=</span> <span class="token constant">FALLBACK_CLI_VERSION</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> _cachedCliVersion<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getCliUserAgent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> version <span class="token operator">=</span> <span class="token function">getCliVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> userType <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">USER_TYPE</span> <span class="token operator">||</span> <span class="token string">'external'</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> entrypoint <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">CLAUDE_CODE_ENTRYPOINT</span> <span class="token operator">||</span> <span class="token string">'cli'</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">claude-cli/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>version<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>userType<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>entrypoint<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3-为-SDK-子进程构建干净的环境变量">3. 为 SDK 子进程构建干净的环境变量</h4><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">buildCliEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> env <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token operator">...</span>process<span class="token punctuation">.</span>env<span class="token punctuation">,</span>    <span class="token constant">CLAUDE_CODE_ENTRYPOINT</span><span class="token operator">:</span> <span class="token string">'cli'</span><span class="token punctuation">,</span>    <span class="token constant">USER_TYPE</span><span class="token operator">:</span> <span class="token string">'external'</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token keyword">delete</span> env<span class="token punctuation">.</span><span class="token constant">CLAUDE_AGENT_SDK_VERSION</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> env<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="4-在-Anthropic-SDK-初始化时添加-CLI-Headers">4. 在 Anthropic SDK 初始化时添加 CLI Headers</h4><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> cliHeaders <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token string-property property">'x-app'</span><span class="token operator">:</span> <span class="token string">'cli'</span><span class="token punctuation">,</span>  <span class="token string-property property">'User-Agent'</span><span class="token operator">:</span> <span class="token function">getCliUserAgent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Anthropic</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">authToken</span><span class="token operator">:</span> apiKey<span class="token punctuation">,</span>  <span class="token literal-property property">apiKey</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>  <span class="token literal-property property">baseURL</span><span class="token operator">:</span> baseUrl <span class="token operator">||</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>  <span class="token literal-property property">defaultHeaders</span><span class="token operator">:</span> cliHeaders  <span class="token comment">// 关键：添加 CLI 风格的 headers</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="5-在-SDK-query-调用时传入干净的环境">5. 在 SDK query 调用时传入干净的环境</h4><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">cwd</span><span class="token operator">:</span> workingDirectory<span class="token punctuation">,</span>  <span class="token literal-property property">permissionMode</span><span class="token operator">:</span> <span class="token string">'default'</span><span class="token punctuation">,</span>  <span class="token literal-property property">model</span><span class="token operator">:</span> sdkModelName<span class="token punctuation">,</span>  <span class="token literal-property property">maxTurns</span><span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span>  <span class="token literal-property property">env</span><span class="token operator">:</span> <span class="token function">buildCliEnv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment">// 关键：使用 CLI 环境变量</span>  <span class="token literal-property property">enableFileCheckpointing</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>  <span class="token literal-property property">tools</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'preset'</span><span class="token punctuation">,</span> <span class="token literal-property property">preset</span><span class="token operator">:</span> <span class="token string">'claude_code'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token comment">// ...</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">query</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="总结">总结</h2><p>完整的 CLI 身份伪装需要覆盖以下层面：</p><table><thead><tr><th>层面</th><th>SDK 默认行为</th><th>CLI 伪装方案</th></tr></thead><tbody><tr><td>System Prompt</td><td>声明 SDK 身份</td><td>包装成 preset append 追加 CLI 身份约束</td></tr><tr><td>User-Agent Header</td><td>包含 agent-sdk 版本</td><td>使用 <code>claude-cli/{VERSION} (external, cli)</code></td></tr><tr><td>x-app Header</td><td>无</td><td>设置为 <code>cli</code></td></tr><tr><td>CLAUDE_CODE_ENTRYPOINT</td><td><code>sdk-ts</code></td><td><code>cli</code></td></tr><tr><td>CLAUDE_AGENT_SDK_VERSION</td><td>存在</td><td>删除</td></tr><tr><td>USER_TYPE</td><td>无</td><td><code>external</code></td></tr></tbody></table><p>方案三解决了 API 请求层面的身份伪装，它确保了：</p><ol><li>API 请求看起来像是来自 CLI（User-Agent、x-app header）</li><li>SDK 子进程继承的是 CLI 环境变量（删除 CLAUDE_AGENT_SDK_VERSION）</li></ol><p>方案三可以与方案一或方案二组合使用：方案一/方案二解决 System Prompt 层面的身份声明，方案三解决 API 请求层面的身份识别。两者组合使用可以实现最完整的 CLI 身份伪装效果。</p><p>这样 Claude 就会表现得像原生 CLI，而不是 &quot;running within the Claude Agent SDK&quot;。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2026/04/claude-sdk-cli-identity-simulation/</id>
    <link href="https://blog.gadfly.vip/2026/04/claude-sdk-cli-identity-simulation/"/>
    <published>2026-04-14T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Claude Agent SDK 在调用 API 时会暴露自己是 SDK 的身份，导致 Claude 在 system prompt 中声称自己是 &quot;running within the Claude Agent SDK&quot;，而不是官方 CLI。如果你想让 Claude 表现得像原生 CLI，需要从多个层面进行身份伪装。</p>]]>
    </summary>
    <title>Claude Agent SDK 身份伪装：让 API 把 SDK 当成 CLI</title>
    <updated>2026-04-13T18:01:56.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="SSH" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/SSH/"/>
    <category term="SSH" scheme="https://blog.gadfly.vip/tags/SSH/"/>
    <category term="GitHub" scheme="https://blog.gadfly.vip/tags/GitHub/"/>
    <category term="GitLab" scheme="https://blog.gadfly.vip/tags/GitLab/"/>
    <category term="代理" scheme="https://blog.gadfly.vip/tags/%E4%BB%A3%E7%90%86/"/>
    <category term="机场" scheme="https://blog.gadfly.vip/tags/%E6%9C%BA%E5%9C%BA/"/>
    <content>
      <![CDATA[<p>最近次元链接等一些机场开始屏蔽 22 端口，导致无法通过 SSH 协议访问 GitHub/GitLab 等代码托管平台。遇到这种情况，可以使用 SSH over HTTPS 功能，通过 443 端口来连接 SSH 服务，从而绕过端口屏蔽的问题。</p><span id="more"></span><p>SSH 默认用的是 22 端口，但 GitHub 和 GitLab 其实都提供了 HTTPS 端口（443）的 SSH 服务，专门用来应付这种情况。改一下 SSH 配置就能解决。</p><h2 id="GitHub">GitHub</h2><p>GitHub 的 SSH over HTTPS 地址是 <code>ssh.github.com</code>，配置如下：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">Host github.com    Hostname ssh.github.com    Port <span class="token number">443</span>    User <span class="token function">git</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="GitLab">GitLab</h2><p>GitLab 的叫法有点不一样，叫 Alt SSH，地址是 <code>altssh.gitlab.com</code>：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">Host gitlab.com    Hostname altssh.gitlab.com    Port <span class="token number">443</span>    User <span class="token function">git</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="完整配置">完整配置</h2><p>顺便加个全局的连接超时，避免卡住太久：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">~/.ssh/configHost *    ConnectTimeout <span class="token number">5</span>Host github.com    Hostname ssh.github.com    Port <span class="token number">443</span>    User <span class="token function">git</span>Host gitlab.com    Hostname altssh.gitlab.com    Port <span class="token number">443</span>    User <span class="token function">git</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>改完直接 <code>git push</code> 就能用了，SSH 协议还是那个 SSH，只是换了条路走而已。</p><h2 id="验证">验证</h2><p>想确认一下配置有没有生效，可以这么测：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># GitHub</span><span class="token function">ssh</span> <span class="token parameter variable">-T</span> git@github.com<span class="token comment"># GitLab</span><span class="token function">ssh</span> <span class="token parameter variable">-T</span> git@gitlab.com<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>能看到对应的欢迎信息就没问题了。</p><hr><p>顺带一提，如果你用的是自建 GitLab 或 Gitea 等平台，也可以自己在 Nginx/反向代理层把 SSH 流量代理一下，原理是一样的。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2026/02/ssh-over-https/</id>
    <link href="https://blog.gadfly.vip/2026/02/ssh-over-https/"/>
    <published>2026-02-03T03:36:22.000Z</published>
    <summary>
      <![CDATA[<p>最近次元链接等一些机场开始屏蔽 22 端口，导致无法通过 SSH 协议访问 GitHub/GitLab 等代码托管平台。遇到这种情况，可以使用 SSH over HTTPS 功能，通过 443 端口来连接 SSH 服务，从而绕过端口屏蔽的问题。</p>]]>
    </summary>
    <title>当机场屏蔽了 22 端口：SSH over HTTPS</title>
    <updated>2026-02-03T03:44:18.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="系统" scheme="https://blog.gadfly.vip/categories/%E7%B3%BB%E7%BB%9F/"/>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E7%B3%BB%E7%BB%9F/%E5%BC%80%E5%8F%91/"/>
    <category term="Linux" scheme="https://blog.gadfly.vip/categories/%E7%B3%BB%E7%BB%9F/%E5%BC%80%E5%8F%91/Linux/"/>
    <category term="Linux" scheme="https://blog.gadfly.vip/tags/Linux/"/>
    <category term="dotnet" scheme="https://blog.gadfly.vip/tags/dotnet/"/>
    <category term="Debian" scheme="https://blog.gadfly.vip/tags/Debian/"/>
    <category term="Ubuntu" scheme="https://blog.gadfly.vip/tags/Ubuntu/"/>
    <category term="apt" scheme="https://blog.gadfly.vip/tags/apt/"/>
    <category term="依赖修复" scheme="https://blog.gadfly.vip/tags/%E4%BE%9D%E8%B5%96%E4%BF%AE%E5%A4%8D/"/>
    <content>
      <![CDATA[<p>抽象的微软前两年对 dotnet 的 apt 源进行了调整，</p><blockquote><p>从 Ubuntu 22.04 开始，Microsoft 不再将适用于 Ubuntu 的 .NET 分发到 Microsoft 软件包存储库。</p></blockquote><p>而它自己对官方 Debian 源的维护进入了只要 Debian 能跑，其他系统不管死活的状态，之前为了 dotnet 8 在 ubuntu 24 还能不能继续用官方 Debian 源的问题纠结了几个月： <a href="https://github.com/dotnet/sdk/issues/40506">https://github.com/dotnet/sdk/issues/40506</a> 。现在 dotnet 10 发布了，又出现了类似的问题（有人直接在这个 issue 里提了），因为 sb 微软把 <code>dotnet-runtime-deps-10.0</code> 里的 libicu 依赖变成了只有 78、77、76、72，中间的 74 被跳掉了。显然 <code>libicu</code> 这些小版本对于 dotnet 来说都是支持的，而我使用的 Deepin V25 正好只有 <code>libicu74</code>。这个问题我也提了个issue: <a href="https://github.com/dotnet/runtime/issues/121829">https://github.com/dotnet/runtime/issues/121829</a></p><p>显然不能指望微软极低的工作效率，我的解决方案是创建一个 <code>libicu78</code> 的虚拟包，这样 apt 就不会阻止我通过微软的 debian 源安装和升级了。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 1. 安装 equivs（若未安装）</span><span class="token function">sudo</span> <span class="token function">apt</span> update <span class="token operator">&amp;&amp;</span> <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> <span class="token parameter variable">-y</span> equivs<span class="token comment"># 2. 创建虚拟包声明 libicu78（选一个缺失但最高的版本，如 78）</span>equivs-control libicu78.control<span class="token comment"># 编辑控制文件</span><span class="token function">cat</span> <span class="token operator">></span> libicu78.control <span class="token operator">&lt;&lt;</span><span class="token string">EOFSection: miscPriority: optionalStandards-Version: 3.9.2Package: libicu78Version: 74.2-1+dummyMaintainer: homolo &lt;homolo@example.com>Provides: libicu78Description: Dummy package to satisfy dotnet-runtime-deps-10.0 dependency This is a dummy package that claims to provide libicu78, while the actual implementation is libicu74 (which is ABI-compatible).EOF</span><span class="token comment"># 3. 构建并安装虚拟包</span>equivs-build libicu78.control<span class="token function">sudo</span> dpkg <span class="token parameter variable">-i</span> libicu78_74.2-1+dummy_all.deb<span class="token comment"># 4. 现在再尝试安装 dotnet-sdk-10.0</span><span class="token function">sudo</span> <span class="token function">apt</span> update<span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> dotnet-sdk-10.0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]>
    </content>
    <id>https://blog.gadfly.vip/2025/11/dotnet-10-installation-fix-debian-ubuntu/</id>
    <link href="https://blog.gadfly.vip/2025/11/dotnet-10-installation-fix-debian-ubuntu/"/>
    <published>2025-11-25T07:18:54.000Z</published>
    <summary>
      <![CDATA[<p>抽象的微软前两年对 dotnet 的 apt 源进行了调整，</p>
<blockquote>
<p>从 Ubuntu 22.04 开始，Microsoft 不再将适用于 Ubuntu 的 .NET 分发到 Microsoft 软件包存储库。</p>
</blockquot]]>
    </summary>
    <title>dotnet sdk/runtime 10 在 类 Debian/Ubuntu 环境下安装的临时修复</title>
    <updated>2025-11-25T07:23:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="git" scheme="https://blog.gadfly.vip/categories/git/"/>
    <category term="git" scheme="https://blog.gadfly.vip/tags/git/"/>
    <category term="签名" scheme="https://blog.gadfly.vip/tags/%E7%AD%BE%E5%90%8D/"/>
    <category term="commit" scheme="https://blog.gadfly.vip/tags/commit/"/>
    <category term="verify" scheme="https://blog.gadfly.vip/tags/verify/"/>
    <category term="signature" scheme="https://blog.gadfly.vip/tags/signature/"/>
    <category term="gpg" scheme="https://blog.gadfly.vip/tags/gpg/"/>
    <category term="ssh" scheme="https://blog.gadfly.vip/tags/ssh/"/>
    <content>
      <![CDATA[<p>Git 支持使用 GPG 密钥或 SSH 密钥对提交进行签名验证。本文将详细介绍如何配置和使用这两种签名方式。</p><h2 id="前置要求">前置要求</h2><ul><li>Git 2.34.0+</li><li>OpenSSH 8.8+ (注意: OpenSSH 8.7 版本签名功能异常)</li><li>如果使用 GPG 签名,需要安装 gpg 工具</li><li>如果使用 SSH 签名,需要 ED25519 或 RSA 类型的 SSH 密钥</li></ul><h2 id="签名验证">签名验证</h2><h3 id="查看提交签名">查看提交签名</h3><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 查看最近的提交签名</span><span class="token function">git</span> log --show-signature<span class="token comment"># 查看指定提交的签名</span><span class="token function">git</span> verify-commit <span class="token operator">&lt;</span>commit-hash<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="本地验证-SSH-签名">本地验证 SSH 签名</h3><ol><li>创建 allowed_signers 文件:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">touch</span> ~/.ssh/allowed_signers<span class="token comment"># 添加签名者信息</span><span class="token builtin class-name">echo</span> <span class="token string">"your_email@example.com namespaces=<span class="token entity" title="\&quot;">\"</span>git<span class="token entity" title="\&quot;">\"</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">cat</span> ~/.ssh/id_ed25519.pub<span class="token variable">)</span></span>"</span> <span class="token operator">>></span> ~/.ssh/allowed_signers<span class="token comment"># 配置 Git 使用此文件</span><span class="token function">git</span> config <span class="token parameter variable">--global</span> gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="获取并导入他人的密钥">获取并导入他人的密钥</h2><h3 id="GitHub">GitHub</h3><h4 id="SSH-密钥">SSH 密钥</h4><ol><li>访问用户个人主页: <code>https://github.com/{username}.keys</code></li><li>将获取的公钥添加到 allowed_signers 文件:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">curl</span> https://github.com/<span class="token punctuation">&#123;</span>username<span class="token punctuation">&#125;</span>.keys <span class="token operator">|</span> <span class="token keyword">while</span> <span class="token builtin class-name">read</span> key<span class="token punctuation">;</span> <span class="token keyword">do</span>    <span class="token builtin class-name">echo</span> <span class="token string">"&#123;their-email&#125; namespaces=<span class="token entity" title="\&quot;">\"</span>git<span class="token entity" title="\&quot;">\"</span> <span class="token variable">$key</span>"</span> <span class="token operator">>></span> ~/.ssh/allowed_signers<span class="token keyword">done</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h4 id="GPG-密钥">GPG 密钥</h4><ol><li>访问用户个人主页: <code>https://github.com/{username}.gpg</code></li><li>导入公钥:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">curl</span> https://github.com/<span class="token punctuation">&#123;</span>username<span class="token punctuation">&#125;</span>.gpg <span class="token operator">|</span> gpg <span class="token parameter variable">--import</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="GitLab">GitLab</h3><h4 id="SSH-密钥-2">SSH 密钥</h4><ol><li>访问用户个人主页: <code>https://{gitlab-instance}/-/users/{username}/keys</code></li><li>同 GitHub 方式添加到 allowed_signers</li></ol><h4 id="GPG-密钥-2">GPG 密钥</h4><ol><li>在用户个人主页找到 GPG Keys 部分</li><li>点击 Download 下载公钥</li><li>导入公钥:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">gpg <span class="token parameter variable">--import</span> username_public.gpg<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="平台身份验证">平台身份验证</h3><p>GitHub 和 GitLab 的签名验证状态会显示为&quot;已验证&quot;的绿色标记，表明这是平台本身验证过的提交。这些提交通常来自:</p><ul><li>Web 界面的编辑</li><li>PR/MR 的自动合并</li><li>GitHub Actions/GitLab CI 的自动提交</li></ul><p>要验证这些签名:</p><ol><li>导入平台的公钥:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># GitHub</span><span class="token function">curl</span> https://github.com/web-flow.gpg <span class="token operator">|</span> gpg <span class="token parameter variable">--import</span><span class="token comment"># GitLab (替换为你的实例地址)</span><span class="token function">curl</span> https://<span class="token punctuation">&#123;</span>gitlab-instance<span class="token punctuation">&#125;</span>/-/openid_connect/key <span class="token operator">|</span> gpg <span class="token parameter variable">--import</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="常见问题">常见问题</h2><h3 id="GPG-签名相关">GPG 签名相关</h3><ol><li>提示 &quot;secret key not available&quot;:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">git</span> config <span class="token parameter variable">--global</span> gpg.program gpg2<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ol start="2"><li>GPG 密码输入框不弹出:</li></ol><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token builtin class-name">export</span> <span class="token assign-left variable">GPG_TTY</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">tty</span><span class="token variable">)</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="SSH-签名相关">SSH 签名相关</h3><ol><li>确保 SSH 密钥类型正确(ED25519 或 RSA)</li><li>验证 OpenSSH 版本是否满足要求(8.8+)</li><li>检查 allowed_signers 文件格式是否正确</li></ol><h2 id="最佳实践">最佳实践</h2><ol><li>使用强密码保护你的密钥</li><li>定期更新密钥</li><li>妥善保管私钥文件</li><li>配置自动签名避免遗漏</li><li>在多设备间同步签名配置</li></ol><p>签名验证可以:</p><ul><li>验证代码提交者身份</li><li>防止他人冒用身份</li><li>提高代码可信度</li></ul><p>记得将公钥上传到 Git 平台(如 GitHub、GitLab)以便在 Web 界面验证签名。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2025/05/verify-git-commit-sign/</id>
    <link href="https://blog.gadfly.vip/2025/05/verify-git-commit-sign/"/>
    <published>2025-05-02T13:24:08.000Z</published>
    <summary>
      <![CDATA[<p>Git 支持使用 GPG 密钥或 SSH 密钥对提交进行签名验证。本文将详细介绍如何配置和使用这两种签名方式。</p>
<h2 id="前置要求">前置要求</h2>
<ul>
<li>Git 2.34.0+</li>
<li>OpenSSH 8.8+ (注意: OpenS]]>
    </summary>
    <title>校验 git 提交签名</title>
    <updated>2025-05-05T14:02:52.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="技术" scheme="https://blog.gadfly.vip/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E6%8A%80%E6%9C%AF/%E5%BC%80%E5%8F%91/"/>
    <category term="时区" scheme="https://blog.gadfly.vip/categories/%E6%8A%80%E6%9C%AF/%E5%BC%80%E5%8F%91/%E6%97%B6%E5%8C%BA/"/>
    <category term="时区" scheme="https://blog.gadfly.vip/tags/%E6%97%B6%E5%8C%BA/"/>
    <category term="时间戳" scheme="https://blog.gadfly.vip/tags/%E6%97%B6%E9%97%B4%E6%88%B3/"/>
    <category term="时间格式化" scheme="https://blog.gadfly.vip/tags/%E6%97%B6%E9%97%B4%E6%A0%BC%E5%BC%8F%E5%8C%96/"/>
    <category term="SourceGit" scheme="https://blog.gadfly.vip/tags/SourceGit/"/>
    <category term="C#" scheme="https://blog.gadfly.vip/tags/C/"/>
    <category term="UTC" scheme="https://blog.gadfly.vip/tags/UTC/"/>
    <category term="夏令时" scheme="https://blog.gadfly.vip/tags/%E5%A4%8F%E4%BB%A4%E6%97%B6/"/>
    <content>
      <![CDATA[<p>时区、时间戳与时间格式化：解决 SourceGit 中的时区问题。本文由文心一言撰写，本人润色。</p><span id="more"></span><p>在参与 SourceGit 项目的过程中，我亲自遇到并解决了一个关于时区处理的问题。这个经历让我深刻体会到了在软件开发中正确处理时间和时区的重要性。</p><h3 id="问题的发现">问题的发现</h3><p>最初，是在 SourceGit 的 GitHub <a href="https://github.com/sourcegit-scm/sourcegit/issues/229">Issue #229</a> 中，用户 <code>ghiboz</code> 报告了一个关于 commit 时间未使用正确时区的问题。他发现在使用自定义服务器时（该服务器位于中欧夏令时，CEST），SourceGit 列出的 commit 时间与预期的时区不符。</p><h3 id="问题的分析">问题的分析</h3><p>在查看问题详情后，我与仓库维护者 <code>love-linger</code> 一起分析了可能的原因。我们注意到，commit 的日期时间是从 <code>git log --pretty=format:&quot;%ct&quot;</code> 命令获取的，这是一个 Unix 时间戳，表示自 1970 年 1 月 1 日（UTC）以来的秒数。由于 Unix 时间戳不包含时区信息，如果在转换过程中处理不当，就可能导致时区错误。</p><p>经过进一步的讨论，我提出一个假设：问题可能出在初始化时将 UTC 时间戳转换为了本地时间，而不是在需要时（如显示给用户时）才进行转换。这样的做法可能不会反映夏令时等时区变化，从而导致时区问题。</p><p>出现问题的代码：</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> CommitterTimeStr <span class="token operator">=></span> _utcStart<span class="token punctuation">.</span><span class="token function">AddSeconds</span><span class="token punctuation">(</span>CommitterTime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"yyyy/MM/dd HH:mm:ss"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> CommitterTimeShortStr <span class="token operator">=></span> _utcStart<span class="token punctuation">.</span><span class="token function">AddSeconds</span><span class="token punctuation">(</span>CommitterTime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"yyyy/MM/dd"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">DateTime</span> _utcStart <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">1970</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> DateTimeKind<span class="token punctuation">.</span>Utc<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLocalTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="解决方案的实施">解决方案的实施</h3><p>为了解决这个问题，我与仓库的维护者 <code>love-linger</code> 进行了沟通，并提出了修改方案。我们决定在显示 commit 时间时，将 UTC 时间戳正确地转换为用户的本地时间，而不是在初始化时转换。</p><p><code>love-linger</code> 迅速响应了我的建议，并在不久后提交了一个修复此问题的 <a href="https://github.com/sourcegit-scm/sourcegit/commit/57a2144777f6362d75212d6b3d160715a5e0c28b">commit（57a2144）</a>。这个修复确保了 commit 时间在显示给用户时能够正确反映用户的本地时区。</p><p>修复后的代码：</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">DateTime<span class="token punctuation">.</span>UnixEpoch<span class="token punctuation">.</span><span class="token function">AddSeconds</span><span class="token punctuation">(</span>CommitterTime<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLocalTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"yyyy/MM/dd HH:mm:ss"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="总结与反思">总结与反思</h3><p>在软件开发中，时区、时间戳和时间格式化是紧密相关的概念。特别是当涉及到全球范围内的用户时，正确地处理这些概念变得至关重要。其中，一个经常被忽略的陷阱是将 UTC 时间戳（例如 Unix 时间戳）直接转换为本地时间，并在转换后的基础上进行计算。这种做法在某些情况下，特别是在夏令时调整时，可能会导致问题。</p><p>夏令时是一种调整时间的制度，用于更有效地利用夏季的日光。它通常在每年的固定日期进行更改，使得时钟向前或向后调整一小时。然而，这种调整对于依赖固定 UTC 时间戳转换为本地时间的系统来说，可能会造成混乱。</p><p>当我们将 UTC 时间戳转换为本地时间时，我们实际上是在假设一个固定的偏移量，该偏移量代表本地时间与 UTC 时间之间的差异。但是，当夏令时调整发生时，这个偏移量会突然改变。如果我们事先将 UTC 时间戳转换为本地时间，并在这个基础上进行计算或显示，那么计算结果或显示的时间将会是错误的，因为它没有考虑到夏令时的变化。</p><p>正确的做法应该是在需要显示或计算时间时，再根据当前的夏令时状态将 UTC 时间戳转换为本地时间。这样可以确保无论是否发生夏令时调整，我们都能得到准确的时间和日期。</p><p>通过上面的分析，我们可以看到，正确处理时区、时间戳和时间格式化的关系对于确保软件在全球范围内提供准确、一致的时间显示至关重要。在涉及全球用户的软件开发中，我们应该始终关注这些细节，以避免出现因时区问题而导致的错误和混淆。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2024/07/date-time-format-to-local-with-timezone/</id>
    <link href="https://blog.gadfly.vip/2024/07/date-time-format-to-local-with-timezone/"/>
    <published>2024-07-02T16:26:33.000Z</published>
    <summary>
      <![CDATA[<p>时区、时间戳与时间格式化：解决 SourceGit 中的时区问题。本文由文心一言撰写，本人润色。</p>]]>
    </summary>
    <title>时区、时间戳与时间格式化：解决 SourceGit 中的时区问题</title>
    <updated>2024-07-02T16:57:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="GIS" scheme="https://blog.gadfly.vip/tags/GIS/"/>
    <content>
      <![CDATA[<p>近期有个需求是打卡功能，需要判断用户位置是否在指定打卡范围内。判断打卡范围有两种思路，一种是以目标点为中心画一个圆，判断点距离圆心的距离，小于圆的半径即可；另一种则是画出电子围栏（以多个有顺序的点构筑一个多边形）判断点是否在多边形内。</p><h1 id="点之间的距离">点之间的距离</h1><h2 id="Haversine">Haversine</h2><p>由于地球是椭球体，且定位所获得的经纬度是角度而不是传统意义上能表示距离的坐标，一般有两种思路，由于地球的各地半径差距不大，因此可以近似看作球体，以 Haversine 公式计算距离。注意美团的文章中的 haversine 公式有错误，本文中已修复。</p><p>参考： <a href="https://tech.meituan.com/2014/09/05/lucene-distance.html">https://tech.meituan.com/2014/09/05/lucene-distance.html</a></p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">GeographicUtils</span> <span class="token punctuation">&#123;</span>    <span class="token comment">/** * 地球半径. */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">double</span> <span class="token constant">EARTH_RADIUS</span> <span class="token operator">=</span> <span class="token number">6378137.0</span><span class="token punctuation">;</span><span class="token comment">/** * 计算两点之间的距离(Haversine公式). * * @param lng1 经度1 * @param lat1 纬度1 * @param lng2 经度2 * @param lat2 纬度2 * @return 距离(米) */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">double</span> <span class="token function">distHaversineRAD</span><span class="token punctuation">(</span><span class="token keyword">double</span> lng1<span class="token punctuation">,</span> <span class="token keyword">double</span> lat1<span class="token punctuation">,</span> <span class="token keyword">double</span> lng2<span class="token punctuation">,</span> <span class="token keyword">double</span> lat2<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">double</span> latDistance <span class="token operator">=</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lat2 <span class="token operator">-</span> lat1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> lngDistance <span class="token operator">=</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lng2 <span class="token operator">-</span> lng1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> sinLat <span class="token operator">=</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">sin</span><span class="token punctuation">(</span>latDistance <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> sinLng <span class="token operator">=</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">sin</span><span class="token punctuation">(</span>lngDistance <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> a <span class="token operator">=</span> sinLat <span class="token operator">*</span> sinLat <span class="token operator">+</span><span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">cos</span><span class="token punctuation">(</span><span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lat1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">cos</span><span class="token punctuation">(</span><span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lat2<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">*</span> sinLng <span class="token operator">*</span> sinLng<span class="token punctuation">;</span><span class="token keyword">double</span> c <span class="token operator">=</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">atan2</span><span class="token punctuation">(</span><span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">sqrt</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token constant">EARTH_RADIUS</span> <span class="token operator">*</span> c<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="简化：多项式拟合">简化：多项式拟合</h2><p>由于这个算法中有大量三角函数的计算，且我们计算距离的范围一般不会超过一个省，因此可以使用多项式拟合，精度并不会下降很多。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">GeographicUtils</span> <span class="token punctuation">&#123;</span><span class="token comment">/** * 地球半径. */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">double</span> <span class="token constant">EARTH_RADIUS</span> <span class="token operator">=</span> <span class="token number">6378137.0</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token constant">POLYNOMIAL_FITTING_RESULTS</span> <span class="token operator">=</span> <span class="token function">trainPolyFit</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">/** * 多项式曲线拟合结果. * * @param degree 拟合次数 * @param length 分段粒度，100以上即可获得较好的拟合效果 * @return 拟合结果 */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">trainPolyFit</span><span class="token punctuation">(</span><span class="token keyword">int</span> degree<span class="token punctuation">,</span> <span class="token keyword">int</span> length<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">PolynomialCurveFitter</span> polynomialCurveFitter <span class="token operator">=</span> <span class="token class-name">PolynomialCurveFitter</span><span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>degree<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> minLat <span class="token operator">=</span> <span class="token number">10.0</span><span class="token punctuation">;</span> <span class="token comment">// 中国大陆范围（不含南海诸岛）最低纬度</span><span class="token keyword">double</span> maxLat <span class="token operator">=</span> <span class="token number">60.0</span><span class="token punctuation">;</span> <span class="token comment">// 中国大陆范围最高纬度</span><span class="token keyword">double</span> intern <span class="token operator">=</span> <span class="token punctuation">(</span>maxLat <span class="token operator">-</span> minLat<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">)</span> length<span class="token punctuation">;</span><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">WeightedObservedPoint</span><span class="token punctuation">></span></span> weightedObservedPoints <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">double</span> x <span class="token operator">=</span> minLat <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">)</span> i <span class="token operator">*</span> intern<span class="token punctuation">;</span><span class="token class-name">WeightedObservedPoint</span> weightedObservedPoint <span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">WeightedObservedPoint</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> <span class="token class-name">StrictMath</span><span class="token punctuation">.</span><span class="token function">cos</span><span class="token punctuation">(</span><span class="token class-name">StrictMath</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>weightedObservedPoints<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>weightedObservedPoint<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> polynomialCurveFitter<span class="token punctuation">.</span><span class="token function">fit</span><span class="token punctuation">(</span>weightedObservedPoints<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * 计算两点之间的距离. * * @param lng1 经度1 * @param lat1 纬度1 * @param lng2 经度2 * @param lat2 纬度2 * @return 距离(米) */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">double</span> <span class="token function">distanceSimplifyMore</span><span class="token punctuation">(</span><span class="token keyword">double</span> lng1<span class="token punctuation">,</span> <span class="token keyword">double</span> lat1<span class="token punctuation">,</span> <span class="token keyword">double</span> lng2<span class="token punctuation">,</span> <span class="token keyword">double</span> lat2<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// 1) 计算三个参数</span><span class="token keyword">double</span> dx <span class="token operator">=</span> lng1 <span class="token operator">-</span> lng2<span class="token punctuation">;</span> <span class="token comment">// 经度差值</span><span class="token keyword">double</span> dy <span class="token operator">=</span> lat1 <span class="token operator">-</span> lat2<span class="token punctuation">;</span> <span class="token comment">// 纬度差值</span><span class="token keyword">double</span> b <span class="token operator">=</span> <span class="token punctuation">(</span>lat1 <span class="token operator">+</span> lat2<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2.0</span><span class="token punctuation">;</span> <span class="token comment">// 平均纬度</span><span class="token comment">// 2) 计算东西方向距离和南北方向距离(单位：米)，东西距离采用三阶多项式</span><span class="token keyword">double</span> <span class="token class-name">Lx</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token constant">POLYNOMIAL_FITTING_RESULTS</span><span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">*</span> b <span class="token operator">*</span> b <span class="token operator">*</span> b<span class="token operator">+</span> <span class="token constant">POLYNOMIAL_FITTING_RESULTS</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">*</span> b <span class="token operator">*</span> b<span class="token operator">+</span> <span class="token constant">POLYNOMIAL_FITTING_RESULTS</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">*</span> b <span class="token operator">+</span> <span class="token constant">POLYNOMIAL_FITTING_RESULTS</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">*</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>dx<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token constant">EARTH_RADIUS</span><span class="token punctuation">;</span> <span class="token comment">// 东西距离</span><span class="token keyword">double</span> <span class="token class-name">Ly</span> <span class="token operator">=</span> <span class="token constant">EARTH_RADIUS</span> <span class="token operator">*</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">toRadians</span><span class="token punctuation">(</span>dy<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 南北距离</span><span class="token comment">// 3) 用平面的矩形对角距离公式计算总距离</span><span class="token keyword">return</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token class-name">Lx</span> <span class="token operator">*</span> <span class="token class-name">Lx</span> <span class="token operator">+</span> <span class="token class-name">Ly</span> <span class="token operator">*</span> <span class="token class-name">Ly</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="椭球体计算：Vincenty">椭球体计算：Vincenty</h2><p>使用椭球体计算的 Vincenty 方法可以参考： <a href="https://www.movable-type.co.uk/scripts/latlong-vincenty.html">https://www.movable-type.co.uk/scripts/latlong-vincenty.html</a></p><h1 id="电子围栏">电子围栏</h1><h2 id="射线法">射线法</h2><p>电子围栏的本质是将若干个点按顺序画出多边形，判断点是否在多边形内。这也有两种算法，一种是射线法，即将点向一个方向射出一条射线（一般是右边），判断与多边形相交的点数，为奇数则说明在多边形内。算法参考：<a href="http://alienryderflex.com/polygon/">http://alienryderflex.com/polygon/</a></p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">GeographicUtils</span> <span class="token punctuation">&#123;</span><span class="token comment">/** * 判断点是否在多边形内部(射线法). * &lt;p> * 该方法计算速度快，但是不精确，如果点在多边形边上，则其结果不固定. * * @param x       x坐标 * @param y       y坐标 * @param polygon 多边形顶点坐标 * @return true:在多边形内部; false:不在多边形内部 */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">isPointInPolygonRayCasting</span><span class="token punctuation">(</span><span class="token keyword">double</span> x<span class="token punctuation">,</span> <span class="token keyword">double</span> y<span class="token punctuation">,</span> <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">int</span> i<span class="token punctuation">;</span><span class="token keyword">int</span> j <span class="token operator">=</span> polygon<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> oddNodes <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> polygon<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polyI <span class="token operator">=</span> polygon<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polyJ <span class="token operator">=</span> polygon<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span> polyXi <span class="token operator">=</span> polyI<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span> polyYi <span class="token operator">=</span> polyI<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span> polyXj <span class="token operator">=</span> polyJ<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span> polyYj <span class="token operator">=</span> polyJ<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>polyYi <span class="token operator">&lt;</span> y <span class="token operator">&amp;&amp;</span> polyYj <span class="token operator">>=</span> y<span class="token operator">||</span> polyYj <span class="token operator">&lt;</span> y <span class="token operator">&amp;&amp;</span> polyYi <span class="token operator">>=</span> y<span class="token punctuation">)</span><span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>polyXi <span class="token operator">&lt;=</span> x <span class="token operator">||</span> polyXj <span class="token operator">&lt;=</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>oddNodes <span class="token operator">^=</span> <span class="token punctuation">(</span>polyXi <span class="token operator">+</span> <span class="token punctuation">(</span>y <span class="token operator">-</span> polyYi<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>polyYj <span class="token operator">-</span> polyYi<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>polyXj <span class="token operator">-</span> polyXi<span class="token punctuation">)</span> <span class="token operator">&lt;</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>j <span class="token operator">=</span> i<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> oddNodes<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>该算法很快但不精确，也不能判断点是否在多边形上，对于可能有负数的经纬度判断、围绕南北极点的电子围栏可能计算错误，需要处理数据，也不适合过于复杂的多边形判断。此时我们可以使用 jdk 的 Path2D 工具类实现</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">GeographicUtils</span> <span class="token punctuation">&#123;</span><span class="token comment">/** * 使用Path2D判断点是否在多边形内，该方法很精确，但是很慢. * * @param x              x * @param y              y * @param polygon        多边形顶点坐标数组 * @param containsBorder 是否包含边界 * @return 是否在多边形内 */</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">isPointInPolygonPath2D</span><span class="token punctuation">(</span><span class="token keyword">double</span> x<span class="token punctuation">,</span> <span class="token keyword">double</span> y<span class="token punctuation">,</span> <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon<span class="token punctuation">,</span> <span class="token keyword">boolean</span> containsBorder<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">int</span> numberOfVertices <span class="token operator">=</span> polygon<span class="token punctuation">.</span>length<span class="token punctuation">;</span><span class="token class-name">Path2D<span class="token punctuation">.</span>Double</span> path <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Path2D<span class="token punctuation">.</span>Double</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 构建多边形路径</span>path<span class="token punctuation">.</span><span class="token function">moveTo</span><span class="token punctuation">(</span>polygon<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> polygon<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> numberOfVertices<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>path<span class="token punctuation">.</span><span class="token function">lineTo</span><span class="token punctuation">(</span>polygon<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> polygon<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>path<span class="token punctuation">.</span><span class="token function">closePath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 创建待判断的点对象</span><span class="token class-name">Point2D<span class="token punctuation">.</span>Double</span> point <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Point2D<span class="token punctuation">.</span>Double</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 判断点是否在多边形内部</span><span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>point<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>containsBorder<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// 排除点在多边形边上的情况</span><span class="token keyword">double</span> distanceThreshold <span class="token operator">=</span> <span class="token number">1e-6</span><span class="token punctuation">;</span> <span class="token comment">// 设置一个距离阈值作为判定边上的依据</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> numberOfVertices<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polyI <span class="token operator">=</span> polygon<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polyJ <span class="token operator">=</span> polygon<span class="token punctuation">[</span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">%</span> numberOfVertices<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token class-name">Line2D<span class="token punctuation">.</span>Double</span> edge <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Line2D<span class="token punctuation">.</span>Double</span><span class="token punctuation">(</span>polyI<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> polyI<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> polyJ<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> polyJ<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">double</span> distance <span class="token operator">=</span> edge<span class="token punctuation">.</span><span class="token function">ptSegDist</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>distance <span class="token operator">&lt;=</span> distanceThreshold<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// 如果点到某条边的距离小于等于阈值，即判定为在边上</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>            <span class="token comment">// 该if内的判断基本等价于以下方法，但是该方法经测试会慢一些</span>            <span class="token comment">// 这是判断以该点构筑一个边长为1e-6的矩形是否与边界相交的方法</span>            <span class="token comment">// return path.intersects(x, y, distanceThreshold, distanceThreshold);</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>电子围栏的算法以如下测试用例测试时，有约 120 倍的性能差距，因此在大陆地区的简单电子围栏使用射线法计算即可：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 1061306</span><span class="token comment">// 点（3.0，4.0）是否在多边形内部？ false</span><span class="token comment">// 点（2.0，2.0）是否在多边形内部？ true</span><span class="token comment">// 点（-1.0，-1.0）是否在多边形内部？ true</span><span class="token comment">// 点（10.0，10.0）是否在多边形内部？ false</span><span class="token comment">// 8814</span><span class="token comment">// 点（3.0，4.0）是否在多边形内部？ false</span><span class="token comment">// 点（2.0，2.0）是否在多边形内部？ true</span><span class="token comment">// 点（-1.0，-1.0）是否在多边形内部？ true</span><span class="token comment">// 点（10.0，10.0）是否在多边形内部？ false</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// 示例：判断点（3，4）是否在多边形内部</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon1 <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">double</span> x1 <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span><span class="token keyword">double</span> y1 <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon2 <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">double</span> x2 <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token keyword">double</span> y2 <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon3 <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">double</span> x3 <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">double</span> y3 <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> polygon4 <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">double</span> x4 <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token keyword">double</span> y4 <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token keyword">long</span> start <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> isInside1 <span class="token operator">=</span> <span class="token function">isPointInPolygonPath2D</span><span class="token punctuation">(</span>x1<span class="token punctuation">,</span> y1<span class="token punctuation">,</span> polygon1<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> isInside2 <span class="token operator">=</span> <span class="token function">isPointInPolygonPath2D</span><span class="token punctuation">(</span>x2<span class="token punctuation">,</span> y2<span class="token punctuation">,</span> polygon2<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> isInside3 <span class="token operator">=</span> <span class="token function">isPointInPolygonPath2D</span><span class="token punctuation">(</span>x3<span class="token punctuation">,</span> y3<span class="token punctuation">,</span> polygon3<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> isInside4 <span class="token operator">=</span> <span class="token function">isPointInPolygonPath2D</span><span class="token punctuation">(</span>x4<span class="token punctuation">,</span> y4<span class="token punctuation">,</span> polygon4<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> start<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x1 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y1 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x2 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y2 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x3 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y3 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside3<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x4 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y4 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside4<span class="token punctuation">)</span><span class="token punctuation">;</span>start <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>isInside1 <span class="token operator">=</span> <span class="token function">isPointInPolygonRayCasting</span><span class="token punctuation">(</span>x1<span class="token punctuation">,</span> y1<span class="token punctuation">,</span> polygon1<span class="token punctuation">)</span><span class="token punctuation">;</span>isInside2 <span class="token operator">=</span> <span class="token function">isPointInPolygonRayCasting</span><span class="token punctuation">(</span>x2<span class="token punctuation">,</span> y2<span class="token punctuation">,</span> polygon2<span class="token punctuation">)</span><span class="token punctuation">;</span>isInside3 <span class="token operator">=</span> <span class="token function">isPointInPolygonRayCasting</span><span class="token punctuation">(</span>x3<span class="token punctuation">,</span> y3<span class="token punctuation">,</span> polygon3<span class="token punctuation">)</span><span class="token punctuation">;</span>isInside4 <span class="token operator">=</span> <span class="token function">isPointInPolygonRayCasting</span><span class="token punctuation">(</span>x4<span class="token punctuation">,</span> y4<span class="token punctuation">,</span> polygon4<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> start<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x1 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y1 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x2 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y2 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x3 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y3 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside3<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"点（"</span> <span class="token operator">+</span> x4 <span class="token operator">+</span> <span class="token string">"，"</span> <span class="token operator">+</span> y4 <span class="token operator">+</span> <span class="token string">"）是否在多边形内部？ "</span> <span class="token operator">+</span> isInside4<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]>
    </content>
    <id>https://blog.gadfly.vip/2023/07/gis-util-java/</id>
    <link href="https://blog.gadfly.vip/2023/07/gis-util-java/"/>
    <published>2023-07-14T16:11:46.000Z</published>
    <summary>
      <![CDATA[<p>近期有个需求是打卡功能，需要判断用户位置是否在指定打卡范围内。判断打卡范围有两种思路，一种是以目标点为中心画一个圆，判断点距离圆心的距离，小于圆的半径即可；另一种则是画出电子围栏（以多个有顺序的点构筑一个多边形）判断点是否在多边形内。</p>
<h1 id="点之间的距离"]]>
    </summary>
    <title>地理信息（GIS）工具</title>
    <updated>2023-07-14T16:22:42.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <content>
      <![CDATA[<p>idea 2022.3 版本更新后，将新版 UI 的开关放在了设置中，可以直接尝试了。</p><span id="more"></span><p><img src="/images/227387.gif" data-original="../../../images/posts/2023/01/NewUIPreview.png" alt="NewUIPreview.png"></p><p>可以看到这界面有在尽力模仿 vscode 了，也把大部分平时根本不会用到的按钮都收起来了，界面看起来简洁了很多。但是编辑器的区域却没有变大，因为每个 tab、按钮等都变得更大了。。。</p><p>IDEA 里自带的设置是不能调整 tab 大小、列表项高度等等的设置的，只能借助第三方插件，其中最推荐的就是 <code>Material Theme UI</code> 。不过这个插件需要收费，免费版不能完成特殊设置。</p><p><img src="/images/227387.gif" data-original="/images/posts/2023/01/2023-01-28_16-53.png" alt="2023-01-28_16-53.png"></p><p><img src="/images/227387.gif" data-original="/images/posts/2023/01/2023-01-28_16-54.png" alt="2023-01-28_16-54.png"></p><p>为了配置列表项的高度，还需要 <code>Material Theme UI Extras</code> 插件，这个插件又需要额外收费。。。而且必须买了本体才可以用这个。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2023/01/idea-2022-3-new-ui/</id>
    <link href="https://blog.gadfly.vip/2023/01/idea-2022-3-new-ui/"/>
    <published>2023-01-28T08:01:59.000Z</published>
    <summary>
      <![CDATA[<p>idea 2022.3 版本更新后，将新版 UI 的开关放在了设置中，可以直接尝试了。</p>]]>
    </summary>
    <title>IDEA 2022.3 新UI体验及配置</title>
    <updated>2023-07-14T16:22:42.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <content>
      <![CDATA[<p>一些在 vue3 里舒服使用 ts 需要做的体操</p><span id="more"></span><h2 id="Vuex-4">Vuex 4</h2><p>vuex 对于 ts 的支持目前来说还是不怎么够用的，有许多 any 的地方，还没法简单修改。参考：<a href="https://dev.to/3vilarthas/vuex-typescript-m4j">https://dev.to/3vilarthas/vuex-typescript-m4j</a></p>]]>
    </content>
    <id>https://blog.gadfly.vip/2022/03/vue3-ts-type-challenges/</id>
    <link href="https://blog.gadfly.vip/2022/03/vue3-ts-type-challenges/"/>
    <published>2022-03-11T02:48:12.000Z</published>
    <summary>
      <![CDATA[<p>一些在 vue3 里舒服使用 ts 需要做的体操</p>]]>
    </summary>
    <title>vue3 中的 ts 类型体操</title>
    <updated>2022-03-11T03:18:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <content>
      <![CDATA[<p>解决类型被作为值导入导出引发的警告</p><span id="more"></span><p>如果在 ts 文件中导出一个类型（type），那么在另一个 ts 文件中导入时，webpack 会报如下警告：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">WARNING Compiled with <span class="token number">1</span> warningwarning <span class="token keyword">in</span> ./src/component/vue-tabs-chrome/index.ts<span class="token builtin class-name">export</span> <span class="token string">'Tab'</span> <span class="token punctuation">(</span>reexported as <span class="token string">'Tab'</span><span class="token punctuation">)</span> was not found <span class="token keyword">in</span> <span class="token string">'./vue-tabs-chrome.vue'</span> <span class="token punctuation">(</span>possible exports: default<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>为了解决这样的问题，TypeScript 提供了一个特性，叫做类型导入（type import），它可以让你在一个文件中导出一个类型，并且可以在另一个文件中导入这个类型。</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// type</span><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Tab</span> <span class="token punctuation">&#123;</span>  <span class="token comment">/** 显示名称 */</span>  label<span class="token operator">:</span> <span class="token builtin">string</span>  <span class="token comment">/** 唯一 key */</span>  key<span class="token operator">:</span> <span class="token builtin">string</span>  favico<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span>  <span class="token comment">/**   * 是否可关闭   */</span>  closable<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span>  <span class="token comment">/**   * 是否可被交换   */</span>  swappable<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span>  <span class="token comment">/**   * 是否可拖拽   */</span>  dragable<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span>  $el<span class="token operator">?</span><span class="token operator">:</span> HTMLElement  <span class="token comment">// eslint-disable-next-line</span>  _instance<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span>  _x<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">&#125;</span><span class="token comment">// origin</span><span class="token keyword">import</span> VueTabsChrome<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> Tab <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'./vue-tabs-chrome.vue'</span><span class="token keyword">export</span> <span class="token punctuation">&#123;</span> VueTabsChrome<span class="token punctuation">,</span> Tab <span class="token punctuation">&#125;</span><span class="token comment">// changed</span><span class="token keyword">import</span> VueTabsChrome <span class="token keyword">from</span> <span class="token string">'./vue-tabs-chrome.vue'</span><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">&#123;</span> Tab <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'./vue-tabs-chrome.vue'</span><span class="token keyword">export</span> <span class="token punctuation">&#123;</span> VueTabsChrome<span class="token punctuation">,</span> Tab <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>references: <a href="https://javascript.plainenglish.io/leveraging-type-only-imports-and-exports-with-typescript-3-8-5c1be8bd17fb">Leveraging Type-Only imports and exports with TypeScript 3.8</a></p>]]>
    </content>
    <id>https://blog.gadfly.vip/2022/03/leveraging-type-only-imports-and-exports-with-typescript/</id>
    <link href="https://blog.gadfly.vip/2022/03/leveraging-type-only-imports-and-exports-with-typescript/"/>
    <published>2022-03-03T01:37:40.000Z</published>
    <summary>
      <![CDATA[<p>解决类型被作为值导入导出引发的警告</p>]]>
    </summary>
    <title>使用 TypeScript 的仅类型导入和导出</title>
    <updated>2023-02-18T20:35:36.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="消息队列" scheme="https://blog.gadfly.vip/categories/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
    <category term="Docker" scheme="https://blog.gadfly.vip/tags/Docker/"/>
    <category term="RabbitMQ" scheme="https://blog.gadfly.vip/tags/RabbitMQ/"/>
    <category term="Docker Compose" scheme="https://blog.gadfly.vip/tags/Docker-Compose/"/>
    <category term="部署" scheme="https://blog.gadfly.vip/tags/%E9%83%A8%E7%BD%B2/"/>
    <content>
      <![CDATA[<h1 id="安装">安装</h1><p>rabbitmq 官方文档有详细的指导，但是没有提供 docker-compose 的安装，这里就给出一个简单的安装方法，比较简单，但是不够完整。</p><p>docker-compose.yml:</p><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.9"</span><span class="token key atrule">services</span><span class="token punctuation">:</span>    <span class="token key atrule">rabbitmq</span><span class="token punctuation">:</span>        <span class="token key atrule">image</span><span class="token punctuation">:</span> rabbitmq<span class="token punctuation">:</span>3.9.13<span class="token punctuation">-</span>management<span class="token punctuation">-</span>alpine        <span class="token key atrule">container_name</span><span class="token punctuation">:</span> <span class="token string">'rabbitmq'</span>        <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped        <span class="token key atrule">ports</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> 5672<span class="token punctuation">:</span><span class="token number">5672</span>            <span class="token punctuation">-</span> 15672<span class="token punctuation">:</span><span class="token number">15672</span>        <span class="token key atrule">volumes</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> /data/rabbitmq/data/<span class="token punctuation">:</span>/var/lib/rabbitmq/            <span class="token punctuation">-</span> /data/rabbitmq/log/<span class="token punctuation">:</span>/var/log/rabbitmq        <span class="token key atrule">networks</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> rabbitmq_go_net<span class="token key atrule">networks</span><span class="token punctuation">:</span>    <span class="token key atrule">rabbitmq_go_net</span><span class="token punctuation">:</span>        <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>rabbitmq 有内置 SSL 支持，不过一般都是内网使用，倒也不必打开。</p><h1 id="管理">管理</h1><h2 id="用户管理">用户管理</h2><p>RabbitMQ 安装完成后，会有一个默认用户(guest guest)</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 查看用户</span>rabbitmqctl list_users<span class="token comment"># 新增用户</span>rabbitmqctl add_user developer <span class="token number">123456</span><span class="token comment"># 删除用户</span>rabbitmqctl delete_user developer<span class="token comment"># 修改密码</span>rabbitmqctl change_password developer <span class="token number">654321</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="角色设置">角色设置</h2><p>RabbitMQ 中主要有 administrator，monitoring，policymaker，management，impersonator，none 几种角色。<br>默认的用户 guest 是 administrator 角色，新建的 developer 用户没有设置角色，即为 none。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 可以设置多个角色</span>rabbitmqctl set_user_tags developer administrator monitoring<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="权限（vhost）">权限（vhost）</h2><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 设置权限</span>rabbitmqctl set_permissions <span class="token parameter variable">-p</span> / developer <span class="token string">".*"</span> <span class="token string">".*"</span> <span class="token string">".*"</span><span class="token comment"># 查看所有用户的权限</span>rabbitmqctl list_permissions<span class="token comment"># 查看virtual host为/的所有用户权限</span>rabbitmqctl list_permissions <span class="token parameter variable">-p</span> /<span class="token comment"># 查看指定用户的权限</span>rabbitmqctl  list_user_permissions developer<span class="token comment"># 清除用户权限</span>rabbitmqctl  clear_permissions developer<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="使用">使用</h1><h2 id="路由设置">路由设置</h2><p>rabbitmq 中有交换机（exchange）、路由（routing）、队列（queue）的概念。消息需要被发送到交换机，交换机根据路由将消息转发到队列。<br>所以创建完交换机和队列后，需要再进入交换机/队列的配置，为其指定路由。在其中一个地方配置后，另一端的配置中就能看到了。然后才可以正常将消息存入队列。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2022/02/rabbitmq-docker-compose-deploy/</id>
    <link href="https://blog.gadfly.vip/2022/02/rabbitmq-docker-compose-deploy/"/>
    <published>2022-02-24T07:35:07.000Z</published>
    <summary>
      <![CDATA[<h1 id="安装">安装</h1>
<p>rabbitmq 官方文档有详细的指导，但是没有提供 docker-compose 的安装，这里就给出一个简单的安装方法，比较简单，但是不够完整。</p>
<p>docker-compose.yml:</p>
<pre class="]]>
    </summary>
    <title>RabbitMQ Docker Compose Deploy</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="Electron" scheme="https://blog.gadfly.vip/categories/Electron/"/>
    <category term="前端" scheme="https://blog.gadfly.vip/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="Electron" scheme="https://blog.gadfly.vip/tags/Electron/"/>
    <category term="Vue3" scheme="https://blog.gadfly.vip/tags/Vue3/"/>
    <category term="客户端" scheme="https://blog.gadfly.vip/tags/%E5%AE%A2%E6%88%B7%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>记录一下 Electron 踩坑</p><span id="more"></span><h2 id="空项目-SSL-握手异常">空项目 SSL 握手异常</h2><pre class="line-numbers language-log" data-language="log"><code class="language-log"><span class="token punctuation">[</span><span class="token number">6104</span><span class="token operator">:</span><span class="token number">0223</span><span class="token operator">/</span><span class="token number">170435.000</span><span class="token operator">:</span><span class="token level error important">ERROR</span><span class="token operator">:</span>ssl_client_socket_impl<span class="token punctuation">.</span>cc<span class="token operator">(</span><span class="token number">983</span><span class="token operator">)</span><span class="token punctuation">]</span> handshake failed<span class="token operator">;</span> returned <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> SSL error code <span class="token number">1</span><span class="token punctuation">,</span> net_error <span class="token operator">-</span><span class="token number">101</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>原因是 Electron 默认开启 DoH，使用的是<code>https://chrome.cloudflare-dns.com/</code>。然而这东西在国内当然是会随时抽风的。Electron 文档内提到可以配置<code>app.configureHostResolver</code>，<s>但是一旦配置就会起不来，只能等 Electron 更新了。</s><code>app.configureHostResolver</code>必须在 ready 事件出发后设置，否则会报错。</p><h2 id="单实例">单实例</h2><p>正常情况下 Electron 是可以多实例启动的，现在需要保证单实例启动。在主进程里进行如下配置</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">&#123;</span> app<span class="token punctuation">,</span> protocol<span class="token punctuation">,</span> BrowserWindow <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'electron'</span><span class="token keyword">import</span> <span class="token punctuation">&#123;</span> createProtocol <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'vue-cli-plugin-electron-builder/lib'</span><span class="token keyword">const</span> isDevelopment <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span><span class="token comment">// Scheme must be registered before the app is ready</span>protocol<span class="token punctuation">.</span><span class="token function">registerSchemesAsPrivileged</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">&#123;</span> scheme<span class="token operator">:</span> <span class="token string">'app'</span><span class="token punctuation">,</span> privileges<span class="token operator">:</span> <span class="token punctuation">&#123;</span> secure<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> standard<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span> <span class="token punctuation">&#125;</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">// 此方法的返回值表示你的应用程序实例是否成功取得了锁。 如果它取得锁失败，你可以假设另一个应用实例已经取得了锁并且仍旧在运行，并立即退出。</span><span class="token keyword">const</span> gotTheLock <span class="token operator">=</span> app<span class="token punctuation">.</span><span class="token function">requestSingleInstanceLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">let</span> mainWindow<span class="token operator">:</span> BrowserWindow<span class="token comment">// 如果我的应用已经存在，那么就退出当前的进程</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>gotTheLock<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  app<span class="token punctuation">.</span><span class="token function">quit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  app<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'second-instance'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 当运行第二个实例时,将会聚焦到 mainWindow 这个窗口</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>mainWindow<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// 先从托盘呼出</span>      mainWindow<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      <span class="token comment">// 然后放大</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>mainWindow<span class="token punctuation">.</span><span class="token function">isMinimized</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> mainWindow<span class="token punctuation">.</span><span class="token function">restore</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      <span class="token comment">// 最后聚焦</span>      mainWindow<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token comment">// Quit when all windows are closed.</span>  app<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'window-all-closed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// On macOS it is common for applications and their menu bar</span>    <span class="token comment">// to stay active until the user quits explicitly with Cmd + Q</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>platform <span class="token operator">!==</span> <span class="token string">'darwin'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      app<span class="token punctuation">.</span><span class="token function">quit</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  app<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'activate'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// On macOS it's common to re-create a window in the app when the</span>    <span class="token comment">// dock icon is clicked and there are no other windows open.</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>BrowserWindow<span class="token punctuation">.</span><span class="token function">getAllWindows</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token function">createMainWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token comment">// This method will be called when Electron has finished</span>  <span class="token comment">// initialization and is ready to create browser windows.</span>  <span class="token comment">// Some APIs can only be used after this event occurs.</span>  app<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'ready'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 正常启动</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="NSIS-安装、卸载程序执行时关闭正在运行的应用">NSIS 安装、卸载程序执行时关闭正在运行的应用</h2><p>NSIS 默认情况下不会在执行时关闭正在运行的应用，并且不会报错。。。好在 electron-builder 提供了自行修改 nsh 的功能，可以自行编写以下脚本，通过 electron-builder 的 include 配置注入。</p><pre class="line-numbers language-nsh" data-language="nsh"><code class="language-nsh">; http:&#x2F;&#x2F;bcoder.com&#x2F;others&#x2F;kill-process-when-install-or-uninstall-programs-by-the-package-made-by-nsis; https:&#x2F;&#x2F;www.electron.build&#x2F;configuration&#x2F;nsis#custom-nsis-script; This callback will be called when the installer is nearly finished initializing. If the &#39;.onInit&#39; function calls Abort, the installer will quit instantly!macro customInit  ; File &#x2F;oname&#x3D;$PLUGINSDIR\KillProcDLL.dll &quot;$&#123;BUILD_RESOURCES_DIR&#125;\KillProcDLL.dll&quot;  $&#123;nsProcess::KillProcess&#125; &quot;$&#123;PRODUCT_FILENAME&#125;.exe&quot; $R0!macroend; This callback will be called when the uninstaller is nearly finished initializing. If the &#39;un.onInit&#39; function calls Abort, the uninstaller will quit instantly. Note that this function can verify and&#x2F;or modify $INSTDIR if necessary.!macro customUnInit  ; File &#x2F;oname&#x3D;$PLUGINSDIR\KillProcDLL.dll &quot;$&#123;BUILD_RESOURCES_DIR&#125;\KillProcDLL.dll&quot;  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 &quot;是否确认卸载 $&#123;SHORTCUT_NAME&#125; 及其所有组件?&quot; IDYES NoAbort IDYES +2  Abort  NoAbort:    $&#123;nsProcess::KillProcess&#125; &quot;$&#123;PRODUCT_FILENAME&#125;.exe&quot; $R0!macroend<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="启动时最大化窗口">启动时最大化窗口</h2><p>需要创建窗口时设置<code>show: false</code>，然后捕获<code>ready-to-show</code>事件，进行最大化和显示的处理。直接最大化会出现窗口不在最上面的问题，因此需要在最大化之前设置窗口在顶部，最后再取消该设置。</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript">mainWindow <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BrowserWindow</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  show<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>  <span class="token comment">// ...</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token comment">// 初始最大化</span>mainWindow<span class="token punctuation">.</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token string">'ready-to-show'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> win <span class="token operator">=</span> mainWindow <span class="token keyword">as</span> BrowserWindow  win<span class="token punctuation">.</span><span class="token function">setAlwaysOnTop</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>  win<span class="token punctuation">.</span><span class="token function">maximize</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  win<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  win<span class="token punctuation">.</span><span class="token function">setAlwaysOnTop</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="关闭窗口时二次确认">关闭窗口时二次确认</h2><p>为避免用户误操作关闭窗口丢失数据，需要在关闭窗口时二次确认。这是调用 electron 默认 dialog 的方式，如果需要结合渲染层，需要另外进行 IPC 调用。</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript">mainWindow<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> e <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 阻止默认行为，一定要有</span>  e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> response <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">await</span> dialog<span class="token punctuation">.</span><span class="token function">showMessageBox</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    type<span class="token operator">:</span> <span class="token string">'info'</span><span class="token punctuation">,</span>    title<span class="token operator">:</span> <span class="token string">'关闭确认'</span><span class="token punctuation">,</span>    defaultId<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>    message<span class="token operator">:</span> <span class="token string">'确定要关闭应用吗？'</span><span class="token punctuation">,</span>    buttons<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'确定'</span><span class="token punctuation">,</span> <span class="token string">'取消'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>response <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    mainWindow <span class="token operator">=</span> <span class="token keyword">null</span>    <span class="token comment">// 不要用quit() 会弹两次</span>    <span class="token comment">// exit()直接关闭客户端，不会执行quit();</span>    app<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="自定义主进程与渲染进程文件位置">自定义主进程与渲染进程文件位置</h2><p>默认情况下，vue-cli-plugin-electron-builder 在初始化时会将主进程和渲染进程的文件都放在 <code>src</code> 目录下，分别为 <code>background.ts</code> 和 <code>main.ts</code> 但是如果需要自定义主进程和渲染进程的文件位置，可以通过在 <code>vue.config.js</code> 中配置。</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// @ts-check</span><span class="token comment">/** @type &#123;import('@vue/cli-service/types/ProjectOptions').ProjectOptions&#125; */</span>module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  pages<span class="token operator">:</span> <span class="token punctuation">&#123;</span>    index<span class="token operator">:</span> <span class="token punctuation">&#123;</span>      entry<span class="token operator">:</span> <span class="token string">'src/renderer/main.ts'</span><span class="token punctuation">,</span>      template<span class="token operator">:</span> <span class="token string">'public/index.html'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  pluginOptions<span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token comment">/** @type &#123;import('vue-cli-plugin-electron-builder').PluginOptions&#125; */</span>    electronBuilder<span class="token operator">:</span> <span class="token punctuation">&#123;</span>      mainProcessFile<span class="token operator">:</span> <span class="token string">'src/main/background.ts'</span><span class="token punctuation">,</span>      mainProcessWatch<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'src/main'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>      preload<span class="token operator">:</span> <span class="token string">'src/main/preload/index.ts'</span><span class="token punctuation">,</span>      <span class="token comment">// 不要用这个配置项，会导致渲染进程没有被挂载到html上</span>      <span class="token comment">// rendererProcessFile: 'src/renderer/main.ts',</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]>
    </content>
    <id>https://blog.gadfly.vip/2022/02/electron-vue3-pits/</id>
    <link href="https://blog.gadfly.vip/2022/02/electron-vue3-pits/"/>
    <published>2022-02-23T09:01:49.000Z</published>
    <summary>
      <![CDATA[<p>记录一下 Electron 踩坑</p>]]>
    </summary>
    <title>Electron Vue3 踩坑笔记</title>
    <updated>2022-03-09T09:04:18.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <content>
      <![CDATA[<p>问卷模块是我的毕业设计的一部分。毕设论文里要求代码不能超过三页，导致我写的时候很难受，在这里写上完整的设计实现思路。</p><span id="more"></span><p>项目地址：</p><ul><li>前端：<a href="https://github.com/gadfly3173/chakki-vue">https://github.com/gadfly3173/chakki-vue/</a></li><li>后端：<a href="https://github.com/gadfly3173/chakki-spring">https://github.com/gadfly3173/chakki-spring/</a></li></ul><p>要开发一个问卷模块，首先我们需要将问卷进行抽象，来理解一个问卷系统需要哪些数据。一个问卷中可能包含多个题目，他们可能是简答题或选择题等。一个选择题可能包含多个选项，问题可以限制选项被选择的数量。如果是矩阵选择题，那么选项将分为横轴与纵轴两种，两种选项可以两两组合变成一个唯一的选项。同时，所有的选项与问题都可以被排序。将这些条件抽象成树状的数据模型就可以是下图的形式。按照图示就可以很清晰地设计出数据库的表结构了。</p><p><img src="/images/227387.gif" data-original="/images/posts/2021/04/image-20210428182720196.png" alt="image-20210428182720196"></p><p>下面是数据库表结构：</p><table><thead><tr><th>列名</th><th>数据类型</th><th>字段类型</th><th>是否为空</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td></td></tr><tr><td>title</td><td>varchar(60)</td><td>varchar</td><td>NO</td><td></td><td>问卷标题</td></tr><tr><td>info</td><td>varchar(255)</td><td>varchar</td><td>YES</td><td></td><td>问卷简介</td></tr><tr><td>class_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>对应班级</td></tr><tr><td>end_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr><tr><td>create_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>update_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>delete_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr></tbody></table><p>questionnaire 表是问卷信息表，记录了问卷的标题、简介、所属的班级 id、问卷的结束时间等信息。</p><table><thead><tr><th>列名</th><th>数据类型</th><th>字段类型</th><th>是否为空</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td></td></tr><tr><td>title</td><td>varchar(60)</td><td>varchar</td><td>NO</td><td></td><td>问题标题</td></tr><tr><td>questionnaire_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>对应问卷</td></tr><tr><td>order</td><td>tinyint(2)  unsigned</td><td>tinyint</td><td>NO</td><td></td><td>顺序编号</td></tr><tr><td>type</td><td>tinyint(2)  unsigned</td><td>tinyint</td><td>NO</td><td></td><td>问题类型：1-简答 2-选择</td></tr><tr><td>limit_max</td><td>tinyint(2)  unsigned</td><td>tinyint</td><td>YES</td><td></td><td>多选限制数量</td></tr><tr><td>create_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>update_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>delete_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr></tbody></table><p>questionnaire_question 表是问题信息表，记录了问题的标题、对应的问卷 id、排序、问题类型、上限等信息。</p><table><thead><tr><th>列名</th><th>数据类型</th><th>字段类型</th><th>是否为空</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td></td></tr><tr><td>title</td><td>varchar(60)</td><td>varchar</td><td>NO</td><td></td><td>选项内容</td></tr><tr><td>question_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>对应问卷</td></tr><tr><td>order</td><td>tinyint(2)  unsigned</td><td>tinyint</td><td>NO</td><td></td><td>顺序编号</td></tr><tr><td>create_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>update_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>delete_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr></tbody></table><p>questionnaire_question_option 表是问卷的选择题的选项表，记录了选项名、对应的问题 id、选项的排序等信息</p><table><thead><tr><th>列名</th><th>数据类型</th><th>字段类型</th><th>是否为空</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td></td></tr><tr><td>user_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>学生 id</td></tr><tr><td>questionnaire_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>问卷 id</td></tr><tr><td>ip</td><td>varchar(39)</td><td>varchar</td><td>YES</td><td></td><td></td></tr><tr><td>create_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>update_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>delete_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr></tbody></table><p>student_questionnaire 表是学生与问卷的关系表，即学生的问卷提交记录，记录了用户 id、问卷 id、IP 地址等信息。</p><table><thead><tr><th>列名</th><th>数据类型</th><th>字段类型</th><th>是否为空</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td></td></tr><tr><td>student_questionnaire_id</td><td>int(10)</td><td>int</td><td>NO</td><td></td><td>对应的学生提交信息 id</td></tr><tr><td>question_id</td><td>int(10) unsigned</td><td>int</td><td>NO</td><td></td><td>问题 id</td></tr><tr><td>answer</td><td>varchar(255)</td><td>varchar</td><td>YES</td><td></td><td></td></tr><tr><td>option_id</td><td>int(10) unsigned</td><td>int</td><td>YES</td><td></td><td></td></tr><tr><td>create_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>update_time</td><td>datetime(3)</td><td>datetime</td><td>NO</td><td>CURRENT_TIMESTAMP(3)</td><td></td></tr><tr><td>delete_time</td><td>datetime(3)</td><td>datetime</td><td>YES</td><td></td><td></td></tr></tbody></table><p>student_questionnaire_question_answer 表是学生提交的每个问题的具体回答的记录表，记录了对应 student_questionnaire 的 id、对应的问题 id、简答题的回答内容、选择题的选项等信息。</p><p>sql 建表语句如下：</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token comment">-- ----------------------------</span><span class="token comment">-- 问卷表</span><span class="token comment">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire<span class="token punctuation">`</span></span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire<span class="token punctuation">`</span></span><span class="token punctuation">(</span>    <span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span>          <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>title<span class="token punctuation">`</span></span>       <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">60</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问卷标题'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>info<span class="token punctuation">`</span></span>        <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问卷简介'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>class_id<span class="token punctuation">`</span></span>    <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'对应班级'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>end_time<span class="token punctuation">`</span></span>    <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>create_time<span class="token punctuation">`</span></span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>update_time<span class="token punctuation">`</span></span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">,</span>    <span class="token keyword">INDEX</span> <span class="token identifier"><span class="token punctuation">`</span>title_del<span class="token punctuation">`</span></span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>title<span class="token punctuation">`</span></span><span class="token punctuation">,</span> <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span>  <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> <span class="token operator">=</span> utf8mb4  <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_general_ci  ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment">-- ----------------------------</span><span class="token comment">-- 问题表</span><span class="token comment">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire_question<span class="token punctuation">`</span></span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire_question<span class="token punctuation">`</span></span><span class="token punctuation">(</span>    <span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span>               <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>title<span class="token punctuation">`</span></span>            <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">60</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问题标题'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>questionnaire_id<span class="token punctuation">`</span></span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'对应问卷'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>order<span class="token punctuation">`</span></span>            <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                          <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'顺序编号'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>type<span class="token punctuation">`</span></span>             <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                          <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问题类型：1-简答 2-选择'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>limit_max<span class="token punctuation">`</span></span>        <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                          <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'多选限制数量'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>create_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>update_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span>  <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> <span class="token operator">=</span> utf8mb4  <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_general_ci  ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment">-- ----------------------------</span><span class="token comment">-- 选项表</span><span class="token comment">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire_question_option<span class="token punctuation">`</span></span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token identifier"><span class="token punctuation">`</span>questionnaire_question_option<span class="token punctuation">`</span></span><span class="token punctuation">(</span>    <span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span>          <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>title<span class="token punctuation">`</span></span>       <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">60</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'选项内容'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>question_id<span class="token punctuation">`</span></span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'对应问卷'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>order<span class="token punctuation">`</span></span>       <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                          <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'顺序编号'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>update_time<span class="token punctuation">`</span></span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span>  <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> <span class="token operator">=</span> utf8mb4  <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_general_ci  ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment">-- ----------------------------</span><span class="token comment">-- 学生-问卷 关系表</span><span class="token comment">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token identifier"><span class="token punctuation">`</span>student_questionnaire<span class="token punctuation">`</span></span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token identifier"><span class="token punctuation">`</span>student_questionnaire<span class="token punctuation">`</span></span><span class="token punctuation">(</span>    <span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span>               <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>user_id<span class="token punctuation">`</span></span>          <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'学生id'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>questionnaire_id<span class="token punctuation">`</span></span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                             <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问卷id'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>ip<span class="token punctuation">`</span></span>               <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">39</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>create_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>update_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span>      <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                  <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span>  <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> <span class="token operator">=</span> utf8mb4  <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_general_ci  ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment">-- ----------------------------</span><span class="token comment">-- 问题-回答 关系表</span><span class="token comment">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token identifier"><span class="token punctuation">`</span>student_questionnaire_question_answer<span class="token punctuation">`</span></span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token identifier"><span class="token punctuation">`</span>student_questionnaire_question_answer<span class="token punctuation">`</span></span><span class="token punctuation">(</span>    <span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span>                       <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>student_questionnaire_id<span class="token punctuation">`</span></span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'对应的学生提交信息id'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>question_id<span class="token punctuation">`</span></span>              <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'问题id'</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>answer<span class="token punctuation">`</span></span>                   <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_general_ci <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>option_id<span class="token punctuation">`</span></span>                <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">UNSIGNED</span>                                              <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>create_time<span class="token punctuation">`</span></span>              <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>update_time<span class="token punctuation">`</span></span>              <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token identifier"><span class="token punctuation">`</span>delete_time<span class="token punctuation">`</span></span>              <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>                                                   <span class="token boolean">NULL</span>     <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token identifier"><span class="token punctuation">`</span>id<span class="token punctuation">`</span></span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span>  <span class="token keyword">CHARACTER</span> <span class="token keyword">SET</span> <span class="token operator">=</span> utf8mb4  <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_general_ci  ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>数据模型中我并没有写上序号一项，因为前端提交时是一个数组的形式，本身就带有数据索引，后端接收到数据后，只要将这个索引作为序号填入数据库即可。对于排序的工作，由前端实现即可。因此，我们可以给出发布问卷使用的 json 示例：</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>  <span class="token property">"class_id"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>  <span class="token property">"end_time"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>  <span class="token property">"info"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>  <span class="token property">"questions"</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token punctuation">&#123;</span>      <span class="token property">"limit_max"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>      <span class="token property">"options"</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token punctuation">&#123;</span>          <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">""</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">]</span><span class="token punctuation">,</span>      <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>      <span class="token property">"type"</span><span class="token operator">:</span> <span class="token number">0</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>对应的实体类便是：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">/** * NewQuestionnaireDTO */</span><span class="token annotation punctuation">@Setter</span><span class="token annotation punctuation">@Getter</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@ApiModel</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"新建问卷DTO"</span><span class="token punctuation">,</span> description <span class="token operator">=</span> <span class="token string">"问卷"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NewQuestionnaireDTO</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"标题"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Length</span><span class="token punctuation">(</span>min <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> max <span class="token operator">=</span> <span class="token number">60</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.title.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">String</span> title<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"简介"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Length</span><span class="token punctuation">(</span>max <span class="token operator">=</span> <span class="token number">255</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.info.length&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">String</span> info<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"班级id"</span><span class="token punctuation">,</span> name <span class="token operator">=</span> <span class="token string">"class_id"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@NotNull</span><span class="token punctuation">(</span>message <span class="token operator">=</span> <span class="token string">"&#123;class.id.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Min</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;class.id.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">Integer</span> classId<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"问题列表"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Valid</span>    <span class="token annotation punctuation">@NotNull</span><span class="token punctuation">(</span>message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.length&#125;"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Size</span><span class="token punctuation">(</span>min <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> max <span class="token operator">=</span> <span class="token number">99</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.length&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">NewQuestionDTO</span><span class="token punctuation">></span></span> questions<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"结束时间"</span><span class="token punctuation">,</span> name <span class="token operator">=</span> <span class="token string">"end_time"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">Date</span> endTime<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * NewQuestionDTO */</span><span class="token annotation punctuation">@Setter</span><span class="token annotation punctuation">@Getter</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@ApiModel</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"新建问题DTO"</span><span class="token punctuation">,</span> description <span class="token operator">=</span> <span class="token string">"问题"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NewQuestionDTO</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"标题"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Length</span><span class="token punctuation">(</span>min <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> max <span class="token operator">=</span> <span class="token number">60</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.title.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">String</span> title<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"类型"</span><span class="token punctuation">,</span> allowableValues <span class="token operator">=</span> <span class="token string">"1,2"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@NotNull</span><span class="token punctuation">(</span>message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.type.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Min</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">QuestionTypeConstant</span><span class="token punctuation">.</span><span class="token constant">TEXT</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.type.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Max</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">QuestionTypeConstant</span><span class="token punctuation">.</span><span class="token constant">SELECT</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.type.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">Integer</span> type<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"上限，目前用于选择题"</span><span class="token punctuation">,</span> name <span class="token operator">=</span> <span class="token string">"limit_max"</span><span class="token punctuation">,</span> allowableValues <span class="token operator">=</span> <span class="token string">"range[1,10]"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Min</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.limit.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Max</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.limit.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">Integer</span> limitMax<span class="token punctuation">;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"选项列表"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Valid</span>    <span class="token annotation punctuation">@Size</span><span class="token punctuation">(</span>max <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.option.limit&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">NewOptionDTO</span><span class="token punctuation">></span></span> options<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * NewOptionDTO */</span><span class="token annotation punctuation">@Setter</span><span class="token annotation punctuation">@Getter</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@ApiModel</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"新建选项DTO"</span><span class="token punctuation">,</span> description <span class="token operator">=</span> <span class="token string">"选项"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NewOptionDTO</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@ApiModelProperty</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"标题"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Length</span><span class="token punctuation">(</span>min <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> max <span class="token operator">=</span> <span class="token number">60</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"&#123;lesson.questionnaire.question.option.title.not-null&#125;"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token class-name">String</span> title<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接下来给出 service 类的实现，controller 就省略了。由于数据库表结构上文也已经给出，对应的 model 类 DO 也不再给出。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">/** * 问题类型常量 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">QuestionTypeConstant</span> <span class="token punctuation">&#123;</span>    <span class="token comment">/**     * 简答     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">TEXT</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token comment">/**     * 选择     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">SELECT</span> <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span>    <span class="token annotation punctuation">@Transactional</span><span class="token punctuation">(</span>rollbackFor <span class="token operator">=</span> <span class="token class-name">Exception</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">createQuestionnaire</span><span class="token punctuation">(</span><span class="token class-name">NewQuestionnaireDTO</span> dto<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// 插入问卷获取id</span>        <span class="token class-name">QuestionnaireDO</span> questionnaireDO <span class="token operator">=</span> <span class="token class-name">QuestionnaireDO</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span><span class="token function">getInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">classId</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span><span class="token function">getClassId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">endTime</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        questionnaireMapper<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>questionnaireDO<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 插入题目</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> dto<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">NewQuestionDTO</span> questionDTO <span class="token operator">=</span> dto<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">QuestionnaireQuestionDO</span> questionnaireQuestionDO <span class="token operator">=</span> <span class="token class-name">QuestionnaireQuestionDO</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">questionnaireId</span><span class="token punctuation">(</span>questionnaireDO<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span>questionDTO<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">type</span><span class="token punctuation">(</span>questionDTO<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">limitMax</span><span class="token punctuation">(</span>questionDTO<span class="token punctuation">.</span><span class="token function">getLimitMax</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            questionnaireQuestionMapper<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>questionnaireQuestionDO<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">// 如果是选择题，且选项列表不为空则插入选项</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>questionDTO<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">QuestionTypeConstant</span><span class="token punctuation">.</span><span class="token constant">SELECT</span>                    <span class="token operator">&amp;&amp;</span> questionDTO<span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span>                    <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>questionDTO<span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> k <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> k <span class="token operator">&lt;</span> questionDTO<span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> k<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                    <span class="token class-name">NewOptionDTO</span> optionDTO <span class="token operator">=</span> questionDTO<span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token class-name">QuestionnaireQuestionOptionDO</span> questionnaireQuestionOptionDO <span class="token operator">=</span>                         <span class="token class-name">QuestionnaireQuestionOptionDO</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                            <span class="token punctuation">.</span><span class="token function">questionId</span><span class="token punctuation">(</span>questionnaireQuestionDO<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                            <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span>optionDTO<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                            <span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span>                            <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    questionnaireQuestionOptionMapper<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>questionnaireQuestionOptionDO<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到代码逻辑很简单，直接循环 DTO 中接收到的所有参数，一一对应插入数据库即可。因为 DTO 中没有给到排序编号，因此使用的是普通 fori 循环来得到数组每项的下标作为编号。</p><p>然后来看看前端部分，新建问卷页面关键代码如下：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span> <span class="token attr-name">v-loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>&#123;&#123; $route.params.id === '0' ? '新建' : '编辑' &#125;&#125;问卷<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>deploy-button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span> <span class="token attr-name">@click.stop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>deploy<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>发布<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title-input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span>问卷标题：<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>medium<span class="token punctuation">"</span></span> <span class="token attr-name">clearable</span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span> <span class="token attr-name">:maxlength</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>60<span class="token punctuation">"</span></span> <span class="token attr-name">show-word-limit</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-input</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>info-input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span>问卷简介：<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span>          <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>medium<span class="token punctuation">"</span></span>          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>textarea<span class="token punctuation">"</span></span>          <span class="token attr-name">:autosize</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123; minRows: 2 &#125;<span class="token punctuation">"</span></span>          <span class="token attr-name">clearable</span>          <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>info<span class="token punctuation">"</span></span>          <span class="token attr-name">:maxlength</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>255<span class="token punctuation">"</span></span>          <span class="token attr-name">show-word-limit</span>        <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-input</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>questions<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-row</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-col</span> <span class="token attr-name">:span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>6<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toolbar<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>add-button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name">icon</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>el-icon-circle-plus-outline<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>addTextQuestion<span class="token punctuation">"</span></span>                <span class="token punctuation">></span></span>新建简答题<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-button</span>              <span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>add-button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name">icon</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>el-icon-circle-plus-outline<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>addSelectQuestion<span class="token punctuation">"</span></span>                <span class="token punctuation">></span></span>新建选择题<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-button</span>              <span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-col</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-col</span> <span class="token attr-name">:span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>18<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>right-col<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-scrollbar</span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scrollbar<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scrollbar<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>mask top-mask<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>end-time-input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                结束时间：                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-date-picker</span>                  <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>endTime<span class="token punctuation">"</span></span>                  <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>datetime<span class="token punctuation">"</span></span>                  <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>选择日期时间<span class="token punctuation">"</span></span>                  <span class="token attr-name">align</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>right<span class="token punctuation">"</span></span>                  <span class="token attr-name">:editable</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>false<span class="token punctuation">"</span></span>                  <span class="token attr-name">:picker-options</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pickerOptions<span class="token punctuation">"</span></span>                <span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-date-picker</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>!list || list.length === 0<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hint-box<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                点击左侧按钮新建题目              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>draggable</span>                <span class="token attr-name">v-else</span>                <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>list-group<span class="token punctuation">"</span></span>                <span class="token attr-name">tag</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ul<span class="token punctuation">"</span></span>                <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>list<span class="token punctuation">"</span></span>                <span class="token attr-name">v-bind</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dragOptions<span class="token punctuation">"</span></span>                <span class="token attr-name">handle</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>.handle<span class="token punctuation">"</span></span>                <span class="token attr-name">@start</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>drag = true<span class="token punctuation">"</span></span>                <span class="token attr-name">@end</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>drag = false<span class="token punctuation">"</span></span>              <span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition-group</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>transition<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flip-list<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>list-group-item<span class="token punctuation">"</span></span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(element, index) in list<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>`question-$&#123;index&#125;`<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>anticon icon-bars handle<span class="token punctuation">"</span></span>                      <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>order<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>&#123;&#123; index + 1 &#125;&#125;.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span>                    <span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>question<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>question-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>label<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                          问题：                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span>                          <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title-input<span class="token punctuation">"</span></span>                          <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>请输入标题<span class="token punctuation">"</span></span>                          <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>medium<span class="token punctuation">"</span></span>                          <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element.title<span class="token punctuation">"</span></span>                          <span class="token attr-name">:maxlength</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>60<span class="token punctuation">"</span></span>                          <span class="token attr-name">clearable</span>                          <span class="token attr-name">show-word-limit</span>                        <span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-input</span><span class="token punctuation">></span></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>question-type<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                        类型：&#123;&#123; element.type | questionTypeFilter &#125;&#125;                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element.type === 2<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>limit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                          选项可选数量上限：                          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input-number</span>                            <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>limit-max-input<span class="token punctuation">"</span></span>                            <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element.limit_max<span class="token punctuation">"</span></span>                            <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>mini<span class="token punctuation">"</span></span>                            <span class="token attr-name">controls-position</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>right<span class="token punctuation">"</span></span>                            <span class="token attr-name">:step-strictly</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>                            <span class="token attr-name">:min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span>                            <span class="token attr-name">:max</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>10<span class="token punctuation">"</span></span>                          <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-input-number</span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                      <span class="token comment">&lt;!-- 选择题选项 --></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>question-options<span class="token punctuation">"</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element.type === 2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iconfont icon-jia plus<span class="token punctuation">"</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>!element.options.length<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>addOption(index)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-row</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>option-row<span class="token punctuation">"</span></span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(item, key) in element.options<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>key<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>option-hint<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>选项&#123;&#123; key + 1 &#125;&#125;<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span>                            <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item.title<span class="token punctuation">"</span></span>                            <span class="token attr-name">:maxlength</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>60<span class="token punctuation">"</span></span>                            <span class="token attr-name">show-word-limit</span>                            <span class="token attr-name">clearable</span>                            <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>请输入选项<span class="token punctuation">"</span></span>                            <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>medium<span class="token punctuation">"</span></span>                            <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>option-input<span class="token punctuation">"</span></span>                          <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-input</span><span class="token punctuation">></span></span>                          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>function<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>                            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iconfont icon-jian1 minus<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>removeOption(index, key)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">></span></span>                            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span>                              <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iconfont icon-jia plus<span class="token punctuation">"</span></span>                              <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>key === element.options.length - 1 &amp;&amp; element.options.length &lt; 10<span class="token punctuation">"</span></span>                              <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>addOption(index)<span class="token punctuation">"</span></span>                            <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">></span></span>                          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-row</span><span class="token punctuation">></span></span>                      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>el-icon-close<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>removeQuestion(index)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">></span></span>                  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition-group</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>draggable</span><span class="token punctuation">></span></span>              <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>mask bottom-mask<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-scrollbar</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-col</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-row</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><span class="token keyword">import</span> Class <span class="token keyword">from</span> <span class="token string">'@/model/class'</span><span class="token keyword">import</span> draggable <span class="token keyword">from</span> <span class="token string">'vuedraggable'</span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">&#123;</span>  <span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>      <span class="token literal-property property">info</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>      <span class="token literal-property property">endTime</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>      <span class="token literal-property property">loading</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>      <span class="token literal-property property">drag</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>      <span class="token literal-property property">dragOptions</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">animation</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>        <span class="token literal-property property">group</span><span class="token operator">:</span> <span class="token string">'description'</span><span class="token punctuation">,</span>        <span class="token literal-property property">disabled</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>        <span class="token literal-property property">ghostClass</span><span class="token operator">:</span> <span class="token string">'ghost'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      <span class="token literal-property property">pickerOptions</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">shortcuts</span><span class="token operator">:</span> <span class="token punctuation">[</span>          <span class="token punctuation">&#123;</span>            <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">'五分钟后'</span><span class="token punctuation">,</span>            <span class="token function">onClick</span><span class="token punctuation">(</span><span class="token parameter">picker</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>              <span class="token keyword">const</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span>              date<span class="token punctuation">.</span><span class="token function">setTime</span><span class="token punctuation">(</span>date<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">5</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>              picker<span class="token punctuation">.</span><span class="token function">$emit</span><span class="token punctuation">(</span><span class="token string">'pick'</span><span class="token punctuation">,</span> date<span class="token punctuation">)</span>            <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>          <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>          <span class="token comment">// 其他的自行撰写</span>        <span class="token punctuation">]</span><span class="token punctuation">,</span>        <span class="token function">disabledDate</span><span class="token punctuation">(</span><span class="token parameter">date</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token keyword">return</span> date<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">3600</span> <span class="token operator">*</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">24</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      <span class="token literal-property property">list</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">components</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    draggable<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">computed</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token function">currentClassId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>$store<span class="token punctuation">.</span>state<span class="token punctuation">.</span>currentClassId    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token function">addTextQuestion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">addSelectQuestion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>        <span class="token literal-property property">limit_max</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>        <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">addOption</span><span class="token punctuation">(</span><span class="token parameter">index</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">removeOption</span><span class="token punctuation">(</span><span class="token parameter">index<span class="token punctuation">,</span> key</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">removeQuestion</span><span class="token punctuation">(</span><span class="token parameter">index</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token keyword">async</span> <span class="token function">getQuestionnaireVO</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// TODO 编辑问卷</span>      <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> Class<span class="token punctuation">.</span><span class="token function">getQuestionnaireVO</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>$route<span class="token punctuation">.</span>params<span class="token punctuation">.</span>id<span class="token punctuation">)</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>title <span class="token operator">=</span> res<span class="token punctuation">.</span>title    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token keyword">async</span> <span class="token function">deploy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">true</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>$route<span class="token punctuation">.</span>params<span class="token punctuation">.</span>id <span class="token operator">===</span> <span class="token string">'0'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token comment">// 发布问卷</span>          <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> Class<span class="token punctuation">.</span><span class="token function">createQuestionnaire</span><span class="token punctuation">(</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>title<span class="token punctuation">,</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>info<span class="token punctuation">,</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>currentClassId<span class="token punctuation">,</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>end_time<span class="token punctuation">,</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">,</span>          <span class="token punctuation">)</span>          <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>code <span class="token operator">&lt;</span> window<span class="token punctuation">.</span><span class="token constant">MAX_SUCCESS_CODE</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">false</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">'问卷发布成功'</span><span class="token punctuation">)</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>          <span class="token comment">// 更新问卷</span>          <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> Class<span class="token punctuation">.</span><span class="token function">updateQuestionnaire</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>$route<span class="token punctuation">.</span>params<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>title<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>content<span class="token punctuation">)</span>          <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>code <span class="token operator">&lt;</span> window<span class="token punctuation">.</span><span class="token constant">MAX_SUCCESS_CODE</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">false</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">'问卷修改成功'</span><span class="token punctuation">)</span>          <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>message<span class="token punctuation">)</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">false</span>      <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">false</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token function">created</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>$route<span class="token punctuation">.</span>params<span class="token punctuation">.</span>id <span class="token operator">!==</span> <span class="token string">'0'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getQuestionnaireVO</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scss<span class="token punctuation">"</span></span> <span class="token attr-name">scoped</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><span class="token selector">.flip-list-move</span> <span class="token punctuation">&#123;</span>  <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.5s<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token selector">.flip-list-enter-active</span> <span class="token punctuation">&#123;</span>  <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.5s<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token selector">.flip-list-enter</span> <span class="token punctuation">&#123;</span>  <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token selector">.no-move</span> <span class="token punctuation">&#123;</span>  <span class="token property">transition</span><span class="token punctuation">:</span> transform 0s<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token selector">.ghost</span> <span class="token punctuation">&#123;</span>  <span class="token property">opacity</span><span class="token punctuation">:</span> 0.5<span class="token punctuation">;</span>  <span class="token property">background</span><span class="token punctuation">:</span> #c8ebfb<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token selector">.container</span> <span class="token punctuation">&#123;</span>  <span class="token property">padding</span><span class="token punctuation">:</span> 0 30px<span class="token punctuation">;</span>  <span class="token property">color</span><span class="token punctuation">:</span> #596c8e<span class="token punctuation">;</span>  <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>  <span class="token selector">.header</span> <span class="token punctuation">&#123;</span>    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>    <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>    <span class="token property">border-bottom</span><span class="token punctuation">:</span> 1px solid #dae1ec<span class="token punctuation">;</span>    <span class="token property">height</span><span class="token punctuation">:</span> 59px<span class="token punctuation">;</span>    <span class="token selector">.title</span> <span class="token punctuation">&#123;</span>      <span class="token property">height</span><span class="token punctuation">:</span> 59px<span class="token punctuation">;</span>      <span class="token property">line-height</span><span class="token punctuation">:</span> 59px<span class="token punctuation">;</span>      <span class="token property">color</span><span class="token punctuation">:</span> $parent-title-color<span class="token punctuation">;</span>      <span class="token property">font-size</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span>      <span class="token property">font-weight</span><span class="token punctuation">:</span> 500<span class="token punctuation">;</span>      <span class="token property">text-indent</span><span class="token punctuation">:</span> 40px<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token selector">.deploy-button</span> <span class="token punctuation">&#123;</span>      <span class="token property">margin</span><span class="token punctuation">:</span> 0 40px<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token selector">.wrapper</span> <span class="token punctuation">&#123;</span>    <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% - 80px<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token selector">.title-input</span> <span class="token punctuation">&#123;</span>      <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>      <span class="token property">padding</span><span class="token punctuation">:</span> 20px 20px 0<span class="token punctuation">;</span>      <span class="token selector">label</span> <span class="token punctuation">&#123;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 120px<span class="token punctuation">;</span>        <span class="token property">line-height</span><span class="token punctuation">:</span> 36px<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token selector">.info-input</span> <span class="token punctuation">&#123;</span>      <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>      <span class="token property">padding</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>      <span class="token property">border-bottom</span><span class="token punctuation">:</span> 1px dashed #dae1ec<span class="token punctuation">;</span>      <span class="token selector">label</span> <span class="token punctuation">&#123;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 120px<span class="token punctuation">;</span>        <span class="token property">line-height</span><span class="token punctuation">:</span> 36px<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token selector">.questions</span> <span class="token punctuation">&#123;</span>      <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% - 150px<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token selector">/deep/ .el-row</span> <span class="token punctuation">&#123;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.toolbar</span> <span class="token punctuation">&#123;</span>        <span class="token property">border-right</span><span class="token punctuation">:</span> 1px solid #dae1ec<span class="token punctuation">;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>        <span class="token property">text-align</span><span class="token punctuation">:</span> right<span class="token punctuation">;</span>        <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>        <span class="token property">align-items</span><span class="token punctuation">:</span> flex-end<span class="token punctuation">;</span>        <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>        <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>        <span class="token selector">.add-button</span> <span class="token punctuation">&#123;</span>          <span class="token property">margin</span><span class="token punctuation">:</span> 10px 20px<span class="token punctuation">;</span>          <span class="token selector">.button</span> <span class="token punctuation">&#123;</span>            <span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.scrollbar</span> <span class="token punctuation">&#123;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>        <span class="token selector">/deep/ .el-scrollbar__wrap</span> <span class="token punctuation">&#123;</span>          <span class="token property">overflow-x</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.right-col</span> <span class="token punctuation">&#123;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.end-time-input</span> <span class="token punctuation">&#123;</span>        <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>        <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>        <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>        <span class="token property">margin-top</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>        <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>        <span class="token property">border-bottom</span><span class="token punctuation">:</span> 1px solid #dae1ec<span class="token punctuation">;</span>        <span class="token selector">/deep/ .el-input__inner</span> <span class="token punctuation">&#123;</span>          <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.hint-box</span> <span class="token punctuation">&#123;</span>        <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>        <span class="token property">padding</span><span class="token punctuation">:</span> 20vh 0<span class="token punctuation">;</span>        <span class="token property">color</span><span class="token punctuation">:</span> #dcdfe6<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.mask</span> <span class="token punctuation">&#123;</span>        <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>        <span class="token property">z-index</span><span class="token punctuation">:</span> 100<span class="token punctuation">;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span>        <span class="token property">pointer-events</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>        <span class="token selector">&amp;.top-mask</span> <span class="token punctuation">&#123;</span>          <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>          <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">rgb</span><span class="token punctuation">(</span>249<span class="token punctuation">,</span> 250<span class="token punctuation">,</span> 251<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>249<span class="token punctuation">,</span> 250<span class="token punctuation">,</span> 251<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token selector">&amp;.bottom-mask</span> <span class="token punctuation">&#123;</span>          <span class="token property">bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>          <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">rgba</span><span class="token punctuation">(</span>249<span class="token punctuation">,</span> 250<span class="token punctuation">,</span> 251<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">rgb</span><span class="token punctuation">(</span>249<span class="token punctuation">,</span> 250<span class="token punctuation">,</span> 251<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.list-group</span> <span class="token punctuation">&#123;</span>        <span class="token property">min-height</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 90%<span class="token punctuation">;</span>        <span class="token property">padding</span><span class="token punctuation">:</span> 0 20px<span class="token punctuation">;</span>        <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span>        <span class="token selector">.list-group-item</span> <span class="token punctuation">&#123;</span>          <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>          <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>          <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>          <span class="token property">margin</span><span class="token punctuation">:</span> 15px 0<span class="token punctuation">;</span>          <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>          <span class="token property">border-bottom</span><span class="token punctuation">:</span> 1px dashed #d5eae6<span class="token punctuation">;</span>          <span class="token selector">.handle</span> <span class="token punctuation">&#123;</span>            <span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>            <span class="token property">font-weight</span><span class="token punctuation">:</span> 700<span class="token punctuation">;</span>            <span class="token property">cursor</span><span class="token punctuation">:</span> move<span class="token punctuation">;</span>            <span class="token property">padding</span><span class="token punctuation">:</span> 20px 0<span class="token punctuation">;</span>            <span class="token selector">&amp;:hover</span> <span class="token punctuation">&#123;</span>              <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.2<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">&#125;</span>          <span class="token selector">.order</span> <span class="token punctuation">&#123;</span>            <span class="token property">margin</span><span class="token punctuation">:</span> 0 10px<span class="token punctuation">;</span>          <span class="token punctuation">&#125;</span>          <span class="token selector">.el-icon-close</span> <span class="token punctuation">&#123;</span>            <span class="token property">font-weight</span><span class="token punctuation">:</span> 700<span class="token punctuation">;</span>            <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>            <span class="token property">padding</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span>            <span class="token property">color</span><span class="token punctuation">:</span> #c7485b<span class="token punctuation">;</span>            <span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>            <span class="token selector">&amp;:hover</span> <span class="token punctuation">&#123;</span>              <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.2<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">&#125;</span>          <span class="token selector">.question-title</span> <span class="token punctuation">&#123;</span>            <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>            <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>            <span class="token property">margin</span><span class="token punctuation">:</span> 10px 0<span class="token punctuation">;</span>            <span class="token selector">.label</span> <span class="token punctuation">&#123;</span>              <span class="token property">width</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token selector">.title-input</span> <span class="token punctuation">&#123;</span>              <span class="token property">width</span><span class="token punctuation">:</span> 350px<span class="token punctuation">;</span>              <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">&#125;</span>          <span class="token selector">.question-type</span> <span class="token punctuation">&#123;</span>            <span class="token property">font-size</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span>            <span class="token property">margin</span><span class="token punctuation">:</span> 10px 0<span class="token punctuation">;</span>            <span class="token selector">.limit</span> <span class="token punctuation">&#123;</span>              <span class="token property">padding-left</span><span class="token punctuation">:</span> 30px<span class="token punctuation">;</span>              <span class="token selector">.limit-max-input</span> <span class="token punctuation">&#123;</span>                <span class="token property">width</span><span class="token punctuation">:</span> 80px<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">&#125;</span>          <span class="token selector">.question-options</span> <span class="token punctuation">&#123;</span>            <span class="token property">padding-top</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>            <span class="token selector">.iconfont</span> <span class="token punctuation">&#123;</span>              <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>              <span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>              <span class="token property">font-weight</span><span class="token punctuation">:</span> 700<span class="token punctuation">;</span>              <span class="token selector">&amp;.plus</span> <span class="token punctuation">&#123;</span>                <span class="token property">color</span><span class="token punctuation">:</span> #3765b6<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>              <span class="token selector">&amp;.minus</span> <span class="token punctuation">&#123;</span>                <span class="token property">font-size</span><span class="token punctuation">:</span> 22px<span class="token punctuation">;</span>                <span class="token property">color</span><span class="token punctuation">:</span> #c7485b<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>            <span class="token selector">.option-row</span> <span class="token punctuation">&#123;</span>              <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>              <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>              <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>              <span class="token property">width</span><span class="token punctuation">:</span> 400px<span class="token punctuation">;</span>              <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>              <span class="token selector">.option-hint</span> <span class="token punctuation">&#123;</span>                <span class="token property">width</span><span class="token punctuation">:</span> 60px<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>              <span class="token selector">.option-input</span> <span class="token punctuation">&#123;</span>                <span class="token property">width</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span>                <span class="token property">margin-right</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>              <span class="token selector">.function</span> <span class="token punctuation">&#123;</span>                <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>                <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>                <span class="token property">width</span><span class="token punctuation">:</span> 60px<span class="token punctuation">;</span>                <span class="token property">height</span><span class="token punctuation">:</span> 36px<span class="token punctuation">;</span>                <span class="token property">line-height</span><span class="token punctuation">:</span> 36px<span class="token punctuation">;</span>              <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>拖拽排序使用的是 vuedraggable，使用方法请自行查阅官方文档。</p><p>学生提交问卷时，则需要上传问卷 id、自己的回答列表即可。回答本身可能有简答与选择的选项两种情况，由于两者类型不同，因此使用两个不同的字段 answer 和 option_id 存储。对于学生提交问卷回答部分就不再展示代码，</p><p>教师查询学生的提交记录时则是下载一个包含所有提交记录的 Excel 文件。Excel 的写入可以使用 Apache POI 来完成。Apache POI 对 Excel 类型的文件有三种创建方式，HSSF、XSSF、SXSSF。<br>HSSF 是用于生成 Office 97-2003 的旧版本 xls 文件，XSSF 用于生成新版本的 xlsx 文件，而 SXSSF 则是在 XSSF 的基础上进行扩展，可以避免过大的数据导致内存溢出。<br>由于问卷是针对班级为单位发布的，数据量不会超过 100 行，因此使用 XSSF 形式进行生成即可。POI 对 Excel 的操作是以行为单位的，每行每列都是从 0 开始。因此需要通过 createRow 方法先创建标题行，然后遍历学生提交的答案来填充数据，最后写入临时文件并返回。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token class-name">FileExportBO</span> <span class="token function">getQuestionnaireReportFile</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 查询问卷本体</span>    <span class="token class-name">QuestionnaireVO</span> questionnaireVO <span class="token operator">=</span> questionnaireMapper<span class="token punctuation">.</span><span class="token function">getQuestionnaireVO</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 查询学生提交记录</span>    <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">StudentQuestionnaireAnswerVO</span><span class="token punctuation">></span></span> answerVOList <span class="token operator">=</span> studentQuestionnaireMapper<span class="token punctuation">.</span><span class="token function">selectStudentQuestionnaireAnswerVO</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// Apache POI 创建 Excel</span>    <span class="token class-name">XSSFWorkbook</span> wb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XSSFWorkbook</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">XSSFSheet</span> sheet <span class="token operator">=</span> wb<span class="token punctuation">.</span><span class="token function">createSheet</span><span class="token punctuation">(</span><span class="token string">"Sheet 1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 设置时间格式</span>    <span class="token class-name">XSSFCreationHelper</span> creationHelper <span class="token operator">=</span> wb<span class="token punctuation">.</span><span class="token function">getCreationHelper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">CellStyle</span> cellStyle <span class="token operator">=</span> wb<span class="token punctuation">.</span><span class="token function">createCellStyle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    cellStyle<span class="token punctuation">.</span><span class="token function">setDataFormat</span><span class="token punctuation">(</span>creationHelper<span class="token punctuation">.</span><span class="token function">createDataFormat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFormat</span><span class="token punctuation">(</span><span class="token string">"yyyy-MM-dd HH:mm:ss.SSS"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 标题行</span>    <span class="token class-name">XSSFRow</span> titleRow <span class="token operator">=</span> sheet<span class="token punctuation">.</span><span class="token function">createRow</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    titleRow<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token string">"学号"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    titleRow<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token string">"姓名"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 问题题目作为标题</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        titleRow<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"第%d题：%s"</span><span class="token punctuation">,</span>                i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>                questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    titleRow<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span>questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token string">"提交时间"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 学生回答内容写入</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> answerVOList<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">XSSFRow</span> row <span class="token operator">=</span> sheet<span class="token punctuation">.</span><span class="token function">createRow</span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span>answerVOList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span>answerVOList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getNickname</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 遍历问题</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token comment">// 简答题写入answer</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token class-name">QuestionTypeConstant</span><span class="token punctuation">.</span><span class="token constant">TEXT</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span>j <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span>answerVOList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAnswers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAnswer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">continue</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token comment">// 选择题查询选项原文写入</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token class-name">QuestionTypeConstant</span><span class="token punctuation">.</span><span class="token constant">SELECT</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token comment">// 初始化StringJoiner用来存放原文</span>                <span class="token class-name">StringJoiner</span> optionTitles <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringJoiner</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Integer</span> optionId <span class="token operator">:</span> answerVOList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAnswers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getOptionId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">OptionVO</span> optionVO <span class="token operator">:</span> questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                        <span class="token comment">// 如果id相同就加一条</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span>optionVO<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>optionId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                            optionTitles<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>optionVO<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token punctuation">&#125;</span>                    <span class="token punctuation">&#125;</span>                <span class="token punctuation">&#125;</span>                row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span>j <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span>optionTitles<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>        <span class="token comment">// 写入创建时间</span>        <span class="token class-name">XSSFCell</span> datetimeCell <span class="token operator">=</span> row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span>questionnaireVO<span class="token punctuation">.</span><span class="token function">getQuestions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        datetimeCell<span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span>answerVOList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getCreateTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        datetimeCell<span class="token punctuation">.</span><span class="token function">setCellStyle</span><span class="token punctuation">(</span>cellStyle<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 写入临时文件</span>    <span class="token class-name">File</span> excelFile <span class="token operator">=</span> <span class="token class-name">File</span><span class="token punctuation">.</span><span class="token function">createTempFile</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">".xlsx"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">FileOutputStream</span> outputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span>excelFile<span class="token punctuation">)</span><span class="token punctuation">;</span>    wb<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>outputStream<span class="token punctuation">)</span><span class="token punctuation">;</span>    outputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 格式化文件名</span>    <span class="token class-name">String</span> filename <span class="token operator">=</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"问卷调查结果_%s.xlsx"</span><span class="token punctuation">,</span> questionnaireVO<span class="token punctuation">.</span><span class="token function">getTitle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token class-name">FileExportBO</span><span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">file</span><span class="token punctuation">(</span>excelFile<span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">filename</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]>
    </content>
    <id>https://blog.gadfly.vip/2021/04/make-a-questionnaire-module-step-by-step/</id>
    <link href="https://blog.gadfly.vip/2021/04/make-a-questionnaire-module-step-by-step/"/>
    <published>2021-04-28T10:10:36.000Z</published>
    <summary>
      <![CDATA[<p>问卷模块是我的毕业设计的一部分。毕设论文里要求代码不能超过三页，导致我写的时候很难受，在这里写上完整的设计实现思路。</p>]]>
    </summary>
    <title>Re:从零开始的问卷模块</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="spring" scheme="https://blog.gadfly.vip/tags/spring/"/>
    <category term="后端" scheme="https://blog.gadfly.vip/tags/%E5%90%8E%E7%AB%AF/"/>
    <category term="javascript" scheme="https://blog.gadfly.vip/tags/javascript/"/>
    <category term="axios" scheme="https://blog.gadfly.vip/tags/axios/"/>
    <category term="前端" scheme="https://blog.gadfly.vip/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="HTTP" scheme="https://blog.gadfly.vip/tags/HTTP/"/>
    <content>
      <![CDATA[<p>前文提到过，Axios 的核心是 <code>XMLHttpRequest</code>。<code>XMLHttpRequest</code> 对象在默认情况下并不会发送 <code>Cookies</code>，需要设置 <code>withCredentials: true</code>才行。<br>但是只有前端设置也是没有用的，涉及跨域的情况下，还需要后端配合。我用的是 Spring，就用 Spring 的配置类演示了。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ajax 封装插件, 使用 axios</span><span class="token keyword">import</span> Vue <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">'axios'</span><span class="token keyword">import</span> Config <span class="token keyword">from</span> <span class="token string">'@/config'</span><span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">baseURL</span><span class="token operator">:</span> Config<span class="token punctuation">.</span>baseURL <span class="token operator">||</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>apiUrl <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>  <span class="token literal-property property">timeout</span><span class="token operator">:</span> <span class="token number">5</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token comment">// 请求超时时间设置</span>  <span class="token literal-property property">crossDomain</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>  <span class="token literal-property property">withCredentials</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// Check cross-site Access-Control</span>  <span class="token comment">// 定义可获得的http响应状态码</span>  <span class="token comment">// return true、设置为null或者undefined，promise将resolved,否则将rejected</span>  <span class="token function">validateStatus</span><span class="token punctuation">(</span><span class="token parameter">status</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> status <span class="token operator">>=</span> <span class="token number">200</span> <span class="token operator">&amp;&amp;</span> status <span class="token operator">&lt;</span> <span class="token number">510</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token comment">// 创建请求实例</span><span class="token keyword">const</span> _axios <span class="token operator">=</span> axios<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token comment">// 其他代码</span><span class="token comment">// ...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token punctuation">(</span>proxyBeanMethods <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WebConfiguration</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addCorsMappings</span><span class="token punctuation">(</span><span class="token class-name">CorsRegistry</span> registry<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        registry<span class="token punctuation">.</span><span class="token function">addMapping</span><span class="token punctuation">(</span><span class="token string">"/**"</span><span class="token punctuation">)</span>                <span class="token comment">// 跨域域名绝对不能配置为通配符，必须指定具体的域名，且http和https是算两个域名的，如果两个都要支持就都要写</span>                <span class="token punctuation">.</span><span class="token function">allowedOriginPatterns</span><span class="token punctuation">(</span><span class="token string">"http://localhost:18080"</span><span class="token punctuation">,</span> <span class="token string">"你的域名"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">allowedMethods</span><span class="token punctuation">(</span><span class="token string">"GET"</span><span class="token punctuation">,</span> <span class="token string">"HEAD"</span><span class="token punctuation">,</span> <span class="token string">"POST"</span><span class="token punctuation">,</span> <span class="token string">"PUT"</span><span class="token punctuation">,</span> <span class="token string">"DELETE"</span><span class="token punctuation">,</span> <span class="token string">"OPTIONS"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">allowCredentials</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">maxAge</span><span class="token punctuation">(</span><span class="token number">3600</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">allowedHeaders</span><span class="token punctuation">(</span><span class="token string">"*"</span><span class="token punctuation">)</span>                <span class="token comment">// 允许跨域接受Cookie</span>                <span class="token punctuation">.</span><span class="token function">allowCredentials</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">exposedHeaders</span><span class="token punctuation">(</span><span class="token class-name">HttpHeaders</span><span class="token punctuation">.</span><span class="token constant">CONTENT_DISPOSITION</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>参考：<a href="https://github.com/chinesedfan/You-Dont-Know-Axios">You Don't Know Axios</a></p>]]>
    </content>
    <id>https://blog.gadfly.vip/2021/02/axios-xhr-with-cookie/</id>
    <link href="https://blog.gadfly.vip/2021/02/axios-xhr-with-cookie/"/>
    <published>2021-02-26T09:58:13.000Z</published>
    <summary>
      <![CDATA[<p>前文提到过，Axios 的核心是 <code>XMLHttpRequest</code>。<code>XMLHttpRequest</code> 对象在默认情况下并不会发送 <code>Cookies</code>，需要设置 <code>withCredentials: t]]>
    </summary>
    <title>Axios 发送请求时携带 Cookies</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="javascript" scheme="https://blog.gadfly.vip/tags/javascript/"/>
    <category term="axios" scheme="https://blog.gadfly.vip/tags/axios/"/>
    <category term="前端" scheme="https://blog.gadfly.vip/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="HTTP" scheme="https://blog.gadfly.vip/tags/HTTP/"/>
    <content>
      <![CDATA[<p>现在有一个接口提供下载服务，在发生异常时，这个接口会返回 JSON 数据，来告诉前端是什么样的异常。默认情况下，Axios 只能处理 JSON 数据，如何让二者兼容是本文讨论的核心。</p><p>Axios 的核心是 <code>XMLHttpRequest</code>。可以设置 <code>XMLHttpRequest</code> 对象的 <code>responseType</code> 属性以改变从服务器端获取的预期响应。可接受的值为空字符串（默认）、<code>arraybuffer</code>、<code>blob</code>、<code>json</code>、<code>text</code>、<code>document</code>。<br>而 Axios 默认配置为 <code>json</code>，如果将其配置为 <code>blob</code>，虽然可以处理下载文件了，但是接口返回的 JSON 数据很难<strong>同步</strong>转换为 JSON 进行处理（因为 Blob 转 JSON 需要使用 <code>FileReader().readAsText()</code>，这是一个异步的操作）。<br>因此我们可以设置 <code>responseType</code> 为 <code>arraybuffer</code>，<code>ArrayBuffer</code> 对象用来表示通用的、固定长度的原始二进制数据缓冲区。它是一个字节数组，通常在其他语言中称为“byte array”，<code>ArrayBuffer</code>可以简单的转换为 JSON 或 Blob。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ajax 封装插件, 使用 axios</span><span class="token keyword">import</span> Vue <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">'axios'</span><span class="token keyword">import</span> Config <span class="token keyword">from</span> <span class="token string">'@/config'</span><span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">baseURL</span><span class="token operator">:</span> Config<span class="token punctuation">.</span>baseURL <span class="token operator">||</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>apiUrl <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>  <span class="token literal-property property">timeout</span><span class="token operator">:</span> <span class="token number">5</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token comment">// 请求超时时间设置</span>  <span class="token literal-property property">crossDomain</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>  <span class="token literal-property property">withCredentials</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// Check cross-site Access-Control</span>  <span class="token comment">// 定义可获得的http响应状态码</span>  <span class="token comment">// return true、设置为null或者undefined，promise将resolved,否则将rejected</span>  <span class="token function">validateStatus</span><span class="token punctuation">(</span><span class="token parameter">status</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> status <span class="token operator">>=</span> <span class="token number">200</span> <span class="token operator">&amp;&amp;</span> status <span class="token operator">&lt;</span> <span class="token number">510</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token comment">// 创建请求实例</span><span class="token keyword">const</span> _axios <span class="token operator">=</span> axios<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span>_axios<span class="token punctuation">.</span>interceptors<span class="token punctuation">.</span>request<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>  <span class="token parameter">originConfig</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> reqConfig <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token operator">...</span>originConfig <span class="token punctuation">&#125;</span>    <span class="token comment">// step1: 容错处理</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>reqConfig<span class="token punctuation">.</span>url<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">/* eslint-disable-next-line */</span>      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'request need url'</span><span class="token punctuation">)</span>      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">source</span><span class="token operator">:</span> <span class="token string">'axiosInterceptors'</span><span class="token punctuation">,</span>        <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">'request need url'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>reqConfig<span class="token punctuation">.</span>method<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// 默认使用 get 请求</span>      reqConfig<span class="token punctuation">.</span>method <span class="token operator">=</span> <span class="token string">'get'</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 大小写容错</span>    reqConfig<span class="token punctuation">.</span>method <span class="token operator">=</span> reqConfig<span class="token punctuation">.</span>method<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token comment">// 参数容错</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>reqConfig<span class="token punctuation">.</span>method <span class="token operator">===</span> <span class="token string">'get'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>reqConfig<span class="token punctuation">.</span>params<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// 防止字段用错</span>        reqConfig<span class="token punctuation">.</span>params <span class="token operator">=</span> reqConfig<span class="token punctuation">.</span>data <span class="token operator">||</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>reqConfig<span class="token punctuation">.</span>method <span class="token operator">===</span> <span class="token string">'post'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// 防止字段用错</span>        reqConfig<span class="token punctuation">.</span>data <span class="token operator">=</span> reqConfig<span class="token punctuation">.</span>params <span class="token operator">||</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token comment">// 检测是否包含文件类型, 若包含则进行 formData 封装</span>      <span class="token keyword">let</span> hasFile <span class="token operator">=</span> <span class="token boolean">false</span>      Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token keyword">const</span> item <span class="token operator">=</span> reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">[</span>key<span class="token punctuation">]</span>          <span class="token keyword">if</span> <span class="token punctuation">(</span>item <span class="token keyword">instanceof</span> <span class="token class-name">FileList</span> <span class="token operator">||</span> item <span class="token keyword">instanceof</span> <span class="token class-name">File</span> <span class="token operator">||</span> item <span class="token keyword">instanceof</span> <span class="token class-name">Blob</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            hasFile <span class="token operator">=</span> <span class="token boolean">true</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>      <span class="token comment">// 检测到存在文件使用 FormData 提交数据</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>hasFile<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">const</span> formData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FormData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>          formData<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> reqConfig<span class="token punctuation">.</span>data<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>        reqConfig<span class="token punctuation">.</span>data <span class="token operator">=</span> formData      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// TODO: 其他类型请求数据格式处理</span>      <span class="token comment">/* eslint-disable-next-line */</span>      console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">其他请求类型: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>reqConfig<span class="token punctuation">.</span>method<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, 暂无自动处理</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 你自己的请求头处理，比如Authorization之类的</span>    <span class="token comment">// ...</span>    <span class="token keyword">return</span> reqConfig  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token parameter">error</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token comment">// Add a response interceptor</span>_axios<span class="token punctuation">.</span>interceptors<span class="token punctuation">.</span>response<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>  <span class="token keyword">async</span> <span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 返回的内容是文件</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-type'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'application/octet-stream'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> res    <span class="token punctuation">&#125;</span>    <span class="token comment">// 返回的数据是 arraybuffer，内容是 json</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>request<span class="token punctuation">.</span>responseType <span class="token operator">===</span> <span class="token string">'arraybuffer'</span> <span class="token operator">&amp;&amp;</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">'[object ArrayBuffer]'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> text <span class="token operator">=</span> Buffer<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token string">'utf8'</span><span class="token punctuation">)</span>      res<span class="token punctuation">.</span>data <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>status<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">'2'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span>data    <span class="token punctuation">&#125;</span>    <span class="token keyword">let</span> <span class="token punctuation">&#123;</span> code<span class="token punctuation">,</span> message <span class="token punctuation">&#125;</span> <span class="token operator">=</span> res<span class="token punctuation">.</span>data <span class="token comment">// eslint-disable-line</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token comment">// 你自己的异常处理</span>      <span class="token comment">// ...</span>      <span class="token class-name">Vue</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">$message</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        message<span class="token punctuation">,</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'error'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>      <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token parameter">error</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>error<span class="token punctuation">.</span>response<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token class-name">Vue</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">$notify</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Network Error'</span><span class="token punctuation">,</span>        <span class="token literal-property property">dangerouslyUseHTMLString</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>        <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">'&lt;strong class="my-notify">请检查 API 是否异常&lt;/strong>'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'error'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 判断请求超时</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>code <span class="token operator">===</span> <span class="token string">'ECONNABORTED'</span> <span class="token operator">&amp;&amp;</span> error<span class="token punctuation">.</span>message<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'timeout'</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token class-name">Vue</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">$message</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'warning'</span><span class="token punctuation">,</span>        <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">'请求超时'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token comment">// eslint-disable-next-line</span>Plugin<span class="token punctuation">.</span><span class="token function-variable function">install</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">Vue<span class="token punctuation">,</span> options</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// eslint-disable-next-line</span>  Vue<span class="token punctuation">.</span>axios <span class="token operator">=</span> _axios  window<span class="token punctuation">.</span>axios <span class="token operator">=</span> _axios  Object<span class="token punctuation">.</span><span class="token function">defineProperties</span><span class="token punctuation">(</span><span class="token class-name">Vue</span><span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">axios</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> _axios      <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token literal-property property">$axios</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> _axios      <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Vue<span class="token punctuation">.</span>axios<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  Vue<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>Plugin<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">// 导出常用函数</span><span class="token comment">/** * @param &#123;string&#125; url * @param &#123;object&#125; data * @param &#123;object&#125; params */</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">post</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">_axios</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'post'</span><span class="token punctuation">,</span>    url<span class="token punctuation">,</span>    data<span class="token punctuation">,</span>    params<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @param &#123;string&#125; url * @param &#123;object&#125; params */</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">_axios</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'get'</span><span class="token punctuation">,</span>    url<span class="token punctuation">,</span>    params<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">/** * 下载用的实例配置 * @param &#123;string&#125; url * @param &#123;object&#125; params */</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">download</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">_axios</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'get'</span><span class="token punctuation">,</span>    url<span class="token punctuation">,</span>    params<span class="token punctuation">,</span>    <span class="token comment">// 指定无超时、接收对象为arraybuffer</span>    <span class="token literal-property property">timeout</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>    <span class="token literal-property property">responseType</span><span class="token operator">:</span> <span class="token string">'arraybuffer'</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @param &#123;string&#125; url * @param &#123;object&#125; data * @param &#123;object&#125; params */</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">put</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">_axios</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'put'</span><span class="token punctuation">,</span>    url<span class="token punctuation">,</span>    params<span class="token punctuation">,</span>    data<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @param &#123;string&#125; url * @param &#123;object&#125; params */</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">_delete</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">_axios</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'delete'</span><span class="token punctuation">,</span>    url<span class="token punctuation">,</span>    params<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">export</span> <span class="token keyword">default</span> _axio<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>参考：<a href="http://blog.tubumu.com/2017/12/27/axios-extension-01/">Axios 同时支持下载和 JSON 数据格式</a></p>]]>
    </content>
    <id>https://blog.gadfly.vip/2021/02/axios-download-file/</id>
    <link href="https://blog.gadfly.vip/2021/02/axios-download-file/"/>
    <published>2021-02-26T09:21:15.000Z</published>
    <summary>
      <![CDATA[<p>现在有一个接口提供下载服务，在发生异常时，这个接口会返回 JSON 数据，来告诉前端是什么样的异常。默认情况下，Axios 只能处理 JSON 数据，如何让二者兼容是本文讨论的核心。</p>
<p>Axios 的核心是 <code>XMLHttpRequest</code>]]>
    </summary>
    <title>Axios 同一实例配置下载文件与 JSON 处理</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="建站" scheme="https://blog.gadfly.vip/categories/%E5%BB%BA%E7%AB%99/"/>
    <category term="纪实" scheme="https://blog.gadfly.vip/tags/%E7%BA%AA%E5%AE%9E/"/>
    <category term="建站" scheme="https://blog.gadfly.vip/tags/%E5%BB%BA%E7%AB%99/"/>
    <category term="Hexo" scheme="https://blog.gadfly.vip/tags/Hexo/"/>
    <content>
      <![CDATA[<h2 id="介绍">介绍</h2><p>Github Actions 可以很方便实现 CI/CD 工作流，类似 Travis 的用法，来帮我们完成一些工作，比如实现自动化测试、打包、部署等操作。当我们运行<code>Jobs</code>时，它会创建一个容器 (runner)，容器支持：<code>Ubuntu</code>、<code>Windows</code>和<code>MacOS</code>等系统，在容器中我们可以安装软件，利用安装的软件帮我们处理一些数据，然后把处理好的数据推送到某个地方。</p><p>本文将介绍利用 Github Actions 实现自动部署<code>hexo</code>到 Github Pages，在之前我们需要写完文章执行<code>hexo d -g</code>来部署，当你文章比较多的时候，可能还需要等待很久，而且还可能会遇到本地安装的<code>Node.js</code>版本与<code>Hexo</code>不兼容的问题。<br>目前我就是因为电脑的<code>Node.js</code>版本升到<code>v14</code>版本导致与<code>Hexo</code>不兼容部署不了，才来捣腾 Github Actions 功能的。利用 Github Actions 你将会没有这些烦恼。</p><h2 id="前提">前提</h2><h3 id="生成部署密钥">生成部署密钥</h3><p>一路按回车直到生成成功</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">ssh-keygen <span class="token parameter variable">-f</span> github-deploy-key<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>当前目录下会有<code>github-deploy-key</code>和<code>github-deploy-key.pub</code>两个文件。</p><h3 id="配置部署密钥">配置部署密钥</h3><p>复制<code>github-deploy-key</code>文件内容，在博客源码仓库的 <code>Settings</code> -&gt; <code>Secrets</code> -&gt; <code>Add a new secret</code> 页面上添加。</p><p>在<code>Name</code>输入框填写<code>HEXO_DEPLOY_PRI</code>。<br>在<code>Value</code>输入框填写<code>github-deploy-key</code>文件内容。</p><p>复制<code>github-deploy-key.pub</code>文件内容，在<code>your.github.io</code>仓库 <code>Settings</code> -&gt; <code>Deploy keys</code> -&gt; <code>Add deploy key</code> 页面上添加。</p><p>在<code>Title</code>输入框填写<code>HEXO_DEPLOY_PUB</code>。<br>在<code>Key</code>输入框填写<code>github-deploy-key.pub</code>文件内容。<br>勾选<code>Allow write access</code>选项。</p><h2 id="编写-Github-Actions">编写 Github Actions</h2><h3 id="Workflow-模版">Workflow 模版</h3><p>在博客源码仓库下创建<code>.github/workflows/deploy.yml</code>文件，我这里是博客仓库的<code>source</code>分支，目录结构如下。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">blog <span class="token punctuation">(</span>repository<span class="token punctuation">)</span>└── .github    └── workflows        └── deploy.yml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>在 <code>deploy.yml</code> 文件中粘贴以下内容。</p><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Hexo CI<span class="token key atrule">on</span><span class="token punctuation">:</span>  <span class="token key atrule">push</span><span class="token punctuation">:</span>    <span class="token key atrule">branches</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> source<span class="token key atrule">env</span><span class="token punctuation">:</span>  <span class="token key atrule">GIT_USER</span><span class="token punctuation">:</span> Gadfly  <span class="token key atrule">GIT_EMAIL</span><span class="token punctuation">:</span> gadfly@gadfly.vip  <span class="token key atrule">DEPLOY_REPO</span><span class="token punctuation">:</span> gadfly3173/gadfly3173.github.io  <span class="token key atrule">DEPLOY_BRANCH</span><span class="token punctuation">:</span> master  <span class="token key atrule">TZ</span><span class="token punctuation">:</span> Asia/Shanghai<span class="token key atrule">jobs</span><span class="token punctuation">:</span>  <span class="token key atrule">build</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> Build on node $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.node_version <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span> and $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.os <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest    <span class="token key atrule">strategy</span><span class="token punctuation">:</span>      <span class="token key atrule">matrix</span><span class="token punctuation">:</span>        <span class="token key atrule">os</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>ubuntu<span class="token punctuation">-</span>latest<span class="token punctuation">]</span>        <span class="token key atrule">node-version</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>10.x<span class="token punctuation">]</span>    <span class="token key atrule">steps</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout deploy repo        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2        <span class="token key atrule">with</span><span class="token punctuation">:</span>          <span class="token key atrule">repository</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> env.DEPLOY_REPO <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>          <span class="token key atrule">ref</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> env.DEPLOY_BRANCH <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>          <span class="token key atrule">path</span><span class="token punctuation">:</span> .deploy_git      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Use Node.js $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>node@v1        <span class="token key atrule">with</span><span class="token punctuation">:</span>          <span class="token key atrule">node-version</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Configuration environment        <span class="token key atrule">env</span><span class="token punctuation">:</span>          <span class="token key atrule">HEXO_DEPLOY_PRI</span><span class="token punctuation">:</span> $<span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span>secrets.HEXO_DEPLOY_PRI<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">          mkdir -p ~/.ssh/          echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa          chmod 600 ~/.ssh/id_rsa          ssh-keyscan github.com >> ~/.ssh/known_hosts          git config --global user.name $GIT_USER          git config --global user.email $GIT_EMAIL</span>      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install dependencies        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">          yarn</span>      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Deploy hexo        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">          yarn release</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]>
    </content>
    <id>https://blog.gadfly.vip/2021/02/github-actions-hexo-ci/</id>
    <link href="https://blog.gadfly.vip/2021/02/github-actions-hexo-ci/"/>
    <published>2021-02-23T11:39:07.000Z</published>
    <summary>
      <![CDATA[<h2 id="介绍">介绍</h2>
<p>Github Actions 可以很方便实现 CI/CD 工作流，类似 Travis 的用法，来帮我们完成一些工作，比如实现自动化测试、打包、部署等操作。当我们运行<code>Jobs</code>时，它会创建一个容器 (runner]]>
    </summary>
    <title>利用 Github Actions 自动部署 Hexo 博客</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="spring" scheme="https://blog.gadfly.vip/tags/spring/"/>
    <category term="后端" scheme="https://blog.gadfly.vip/tags/%E5%90%8E%E7%AB%AF/"/>
    <category term="HTTP" scheme="https://blog.gadfly.vip/tags/HTTP/"/>
    <content>
      <![CDATA[<p>关于 HTTP Header，网上找到的大部分教程，设置 header 都是非常简单粗暴的 new 一个<code>HttpHeaders()</code>，然后直接 add 的形式（甚至连 StackOverflow 上也有很多回答是这么做的），如：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">ResponseEntity</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">FileSystemResource</span><span class="token punctuation">></span></span> <span class="token function">export</span><span class="token punctuation">(</span><span class="token class-name">File</span> file<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>file <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token class-name">HttpHeaders</span> headers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpHeaders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"Cache-Control"</span><span class="token punctuation">,</span> <span class="token string">"no-cache, no-store, must-revalidate"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"Content-Disposition"</span><span class="token punctuation">,</span> <span class="token string">"attachment; filename="</span> <span class="token operator">+</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".xls"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"Pragma"</span><span class="token punctuation">,</span> <span class="token string">"no-cache"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"Expires"</span><span class="token punctuation">,</span> <span class="token string">"0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"Last-Modified"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"ETag"</span><span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token class-name">ResponseEntity</span>            <span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">headers</span><span class="token punctuation">(</span>headers<span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">contentLength</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">contentType</span><span class="token punctuation">(</span><span class="token class-name">MediaType</span><span class="token punctuation">.</span><span class="token function">parseMediaType</span><span class="token punctuation">(</span><span class="token string">"application/octet-stream"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileSystemResource</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是这样代码里会有很多魔法值，而对于<code>Content-Disposition</code>这样的 header 中，filename 如果直接把中文填进去还会导致乱码，对它进行转码就又要多写几行。其实 Spring 本身对于 header 就提供了很多简单的设置方法，可以有效提高代码的可读性。</p><p>将上面的代码用 Spring 中提供了方法改写的话，可以写成：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token class-name">ResponseEntity</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">FileSystemResource</span><span class="token punctuation">></span></span> <span class="token function">export</span><span class="token punctuation">(</span><span class="token class-name">File</span> file<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>file <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token class-name">HttpHeaders</span> headers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpHeaders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setCacheControl</span><span class="token punctuation">(</span><span class="token class-name">CacheControl</span><span class="token punctuation">.</span><span class="token function">noStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mustRevalidate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setContentDisposition</span><span class="token punctuation">(</span><span class="token class-name">ContentDisposition</span><span class="token punctuation">.</span><span class="token function">attachment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filename</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">StandardCharsets</span><span class="token punctuation">.</span><span class="token constant">UTF_8</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setPragma</span><span class="token punctuation">(</span><span class="token string">"no-cache"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setExpires</span><span class="token punctuation">(</span><span class="token number">0L</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setLastModified</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    headers<span class="token punctuation">.</span><span class="token function">setETag</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token class-name">ResponseEntity</span>            <span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">headers</span><span class="token punctuation">(</span>headers<span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">contentLength</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">contentType</span><span class="token punctuation">(</span><span class="token class-name">MediaType</span><span class="token punctuation">.</span><span class="token constant">APPLICATION_OCTET_STREAM</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileSystemResource</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这样编码的转换也不需要自己写了，也可以很简单设置 header，不会有打错字的困扰（雾</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2021/02/sth-about-http-header/</id>
    <link href="https://blog.gadfly.vip/2021/02/sth-about-http-header/"/>
    <published>2021-02-20T19:15:35.000Z</published>
    <summary>
      <![CDATA[<p>关于 HTTP Header，网上找到的大部分教程，设置 header 都是非常简单粗暴的 new 一个<code>HttpHeaders()</code>，然后直接 add 的形式（甚至连 StackOverflow 上也有很多回答是这么做的），如：</p>
<pre c]]>
    </summary>
    <title>关于 HTTP Header 的碎碎念</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="spring" scheme="https://blog.gadfly.vip/tags/spring/"/>
    <category term="后端" scheme="https://blog.gadfly.vip/tags/%E5%90%8E%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>Spring Boot 中对方法开启事务管理非常轻松，只需要 <code>@Transactional</code> 注解就可以让方法开启事务，然而不了解其机制时使用可能会导致其失效。</p><span id="more"></span><p><code>@Transactional</code> 本质是一个 AOP 动态代理，不需要开发者干预。下面来一个代码例子：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">&#123;</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> <span class="token class-name">UserMapper</span> userMapper<span class="token punctuation">;</span> <span class="token annotation punctuation">@Transactional</span><span class="token punctuation">(</span>propagation<span class="token operator">=</span><span class="token class-name">Propagation</span><span class="token punctuation">.</span><span class="token constant">REQUIRED</span><span class="token punctuation">,</span>isolation<span class="token operator">=</span><span class="token class-name">Isolation</span><span class="token punctuation">.</span><span class="token constant">DEFAULT</span><span class="token punctuation">,</span>readOnly<span class="token operator">=</span><span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">insert01</span><span class="token punctuation">(</span><span class="token class-name">User</span> u<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>userMapper<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"测试插入事务"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">insert02</span><span class="token punctuation">(</span><span class="token class-name">User</span> u<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">insert01</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"测试插入事务"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个例子中，调用 <code>insert01</code> 时，事务的执行不会有任何问题，但是调用 <code>insert02</code> 时，异常被正常抛出，但是数据依然被插入了数据库，说明事务没有被正常执行。<br>在外部调用 <code>insert01</code> 时，调用的就是被动态代理的 <code>insert01</code>，但是如果在一个类里自调用时，这样是无法调用到代理对象的，所以 <code>insert02</code> 中调用的不是代理对象 <code>insert01</code>，而是原本的方法。当然，原本的对象中是没有切片做事务增强的，自然也不能进行事务回滚。</p><p>一般的事务代理机制：<br><img src="/images/227387.gif" data-original="/images/posts/2020/04/20170608105830399.jpg" alt=""></p><p>自调用时，通过 <code>this</code> 只能取到原本的方法：<br><img src="/images/227387.gif" data-original="/images/posts/2020/04/20170608110005451.jpg" alt=""></p><p>那么如何解决自调用失效的问题呢？</p><ul><li><p>最佳实践自然是不要产生自调用。</p></li><li><p>如果无法避免的话，那么可以只在 <code>insert02</code> 上注解事务。（如果两个方法都要被外部调用，那就两个都写上）</p></li><li><p><code>insert01</code> 开启事务，并且在 <code>insert02</code> 中不使用 <code>this</code> 调用，而是获取代理</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">insert02</span><span class="token punctuation">(</span><span class="token class-name">User</span> u<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">getService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">insert01</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">private</span> <span class="token class-name">UserService</span> <span class="token function">getService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 采取这种方式的话</span>    <span class="token comment">// @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)</span>    <span class="token comment">// 必须设置为true</span>    <span class="token keyword">return</span> <span class="token class-name">AopContext</span><span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token class-name">UserService</span><span class="token punctuation">)</span><span class="token class-name">AopContext</span><span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>从 <code>beanFactory</code> 中获取对象。Controller 中的 <code>UserService</code> 是代理对象，它是从 <code>beanFactory</code> 中得来的，那么 Service 类内调用其他方法时，也先从 <code>beanFacotry</code> 中拿出来就 OK 了。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">insert02</span><span class="token punctuation">(</span><span class="token class-name">User</span> u<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">getService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">insert01</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token class-name">UserService</span> <span class="token function">getService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token class-name">SpringContextUtil</span><span class="token punctuation">.</span><span class="token function">getBean</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>有关 AOP 和代理的问题可以看 <a href="https://juejin.im/post/5b90e648f265da0aea695672">从代理机制到 Spring AOP</a> 和 <a href="https://juejin.im/post/5b06bf2df265da0de2574ee1">Spring AOP 就是这么简单啦</a>。这两篇文章讲得很清楚了。</p><p>除了代理导致的自调用失效，还有一个问题是方法抛出异常时，事务也没有回滚。这个则是因为 <code>@Transactional</code> 中捕获的异常只有 <code>RuntimeException</code>。<br>DefaultTransactionAttribute.java 源码中写的是：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">rollbackOn</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> ex<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>ex <span class="token keyword">instanceof</span> <span class="token class-name">RuntimeException</span> <span class="token operator">||</span> ex <span class="token keyword">instanceof</span> <span class="token class-name">Error</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>所以需要捕获其他异常时，必须把注解写成</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Transactional</span><span class="token punctuation">(</span> rollbackFor <span class="token operator">=</span> <span class="token class-name">Exception</span><span class="token punctuation">.</span><span class="token keyword">class</span> <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>更详细的使用要点可以看来自 IBM Developer 的 <a href="https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html">透彻的掌握 Spring 中@transactional 的使用</a></p>]]>
    </content>
    <id>https://blog.gadfly.vip/2020/04/spring-boot-transactional/</id>
    <link href="https://blog.gadfly.vip/2020/04/spring-boot-transactional/"/>
    <published>2020-04-12T07:08:19.000Z</published>
    <summary>
      <![CDATA[<p>Spring Boot 中对方法开启事务管理非常轻松，只需要 <code>@Transactional</code> 注解就可以让方法开启事务，然而不了解其机制时使用可能会导致其失效。</p>]]>
    </summary>
    <title>Spring Boot 开发笔记 - @Transactional 注解使用浅析</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="spring" scheme="https://blog.gadfly.vip/tags/spring/"/>
    <category term="后端" scheme="https://blog.gadfly.vip/tags/%E5%90%8E%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>Spring Boot 开发笔记系列第二弹，这次来聊聊 JWT 和登录鉴权系统的设计。</p><span id="more"></span><p>阮一峰的 <a href="https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html">JSON Web Token 入门教程</a> 里把 JWT 的概念都讲得很清楚了。既然它被称为 Token，那么自然是作为令牌，用来验证身份的存在。传统的 Session, Cookie 等都承担着类似的功能。JWT 的独特之处在于它可以承载一定的信息，是一种无状态的令牌。验证其是否有效的工作可以不需要经过数据库或者缓存等，如果加密 JWT 的密钥在后端是固定的或者通过其承载的信息可以算出来的，那么验证与下发都可以极大的减少服务器压力。而它本身可以承载的信息中可以加上不敏感的内容，比如用户 id 或者用户名等等，在不经过数据库操作的时候就能判断用户的合法性，完成一定的操作。<br>当然这样的设计也有局限性，比如我的项目中需要保证用户不会在多处同时登录，但是 JWT 验证 Token 只根据它其中包含的过期时间信息，后端不能在用户使用第二台设备登录时作废第一台设备的登录信息，这可能导致一定的安全隐患。因此我在项目中引入了 Redis 缓存，每次下发 Token 时生成随机密钥，这个密钥与用户信息绑定，与 Token 拥有相同的过期时间并存在 Redis 中。Redis 的原理使得这样的操作不会增加太多的开销，又能让 JWT 依然承担传递信息的任务。</p><h3 id="JWT-相关的设计">JWT 相关的设计</h3><p>首先来一个 JWT 的工具类：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JWTUtil</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">RedisUtils</span> nonStaticRedisUtils<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">RedisUtils</span> redisUtils<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token class-name">JWTUtil</span><span class="token punctuation">(</span><span class="token class-name">RedisUtils</span> nonStaticRedisUtils<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>nonStaticRedisUtils <span class="token operator">=</span> nonStaticRedisUtils<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token annotation punctuation">@PostConstruct</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        redisUtils <span class="token operator">=</span> nonStaticRedisUtils<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 过期时间7天</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> <span class="token constant">EXPIRE_TIME</span> <span class="token operator">=</span> <span class="token number">7</span> <span class="token operator">*</span> <span class="token number">24</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">;</span>    <span class="token comment">/**     * 生成签名,7days后过期     *     * @param openid qq_openid     * @return 加密的token     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">sign</span><span class="token punctuation">(</span><span class="token class-name">String</span> openid<span class="token punctuation">,</span> <span class="token class-name">String</span> access_level<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">Date</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token constant">EXPIRE_TIME</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>redisUtils<span class="token punctuation">.</span><span class="token function">hasKey</span><span class="token punctuation">(</span>openid<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            redisUtils<span class="token punctuation">.</span><span class="token function">del</span><span class="token punctuation">(</span>openid<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token class-name">String</span> secret <span class="token operator">=</span> <span class="token constant">UUID</span><span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">Algorithm</span> algorithm <span class="token operator">=</span> <span class="token class-name">Algorithm</span><span class="token punctuation">.</span><span class="token function">HMAC256</span><span class="token punctuation">(</span>secret<span class="token punctuation">)</span><span class="token punctuation">;</span>        redisUtils<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>openid<span class="token punctuation">,</span> secret<span class="token punctuation">,</span> <span class="token constant">EXPIRE_TIME</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span>openid <span class="token operator">+</span> <span class="token string">"&amp;"</span> <span class="token operator">+</span> secret<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 附带openid信息</span>        <span class="token keyword">return</span> <span class="token constant">JWT</span><span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">withClaim</span><span class="token punctuation">(</span><span class="token string">"openid"</span><span class="token punctuation">,</span> openid<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">withClaim</span><span class="token punctuation">(</span><span class="token string">"access_level"</span><span class="token punctuation">,</span> access_level<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">withExpiresAt</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span>algorithm<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 校验token是否正确     *     * @param token  密钥     * @return 是否正确     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">verify</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">,</span> <span class="token class-name">String</span> openid<span class="token punctuation">,</span> <span class="token class-name">String</span> access_level<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">String</span> secret <span class="token operator">=</span> redisUtils<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>openid<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">Algorithm</span> algorithm <span class="token operator">=</span> <span class="token class-name">Algorithm</span><span class="token punctuation">.</span><span class="token function">HMAC256</span><span class="token punctuation">(</span>secret<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">JWTVerifier</span> verifier <span class="token operator">=</span> <span class="token constant">JWT</span><span class="token punctuation">.</span><span class="token function">require</span><span class="token punctuation">(</span>algorithm<span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">withClaim</span><span class="token punctuation">(</span><span class="token string">"openid"</span><span class="token punctuation">,</span> openid<span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">withClaim</span><span class="token punctuation">(</span><span class="token string">"access_level"</span><span class="token punctuation">,</span> access_level<span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">DecodedJWT</span> jwt <span class="token operator">=</span> verifier<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">JWTVerificationException</span> exception<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 获得token中的信息无需secret解密也能获得     *     * @return token中包含的openid     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">getOpenid</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">DecodedJWT</span> jwt <span class="token operator">=</span> <span class="token constant">JWT</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> jwt<span class="token punctuation">.</span><span class="token function">getClaim</span><span class="token punctuation">(</span><span class="token string">"openid"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">asString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">getAccessLevel</span><span class="token punctuation">(</span><span class="token class-name">String</span> token<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">DecodedJWT</span> jwt <span class="token operator">=</span> <span class="token constant">JWT</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> jwt<span class="token punctuation">.</span><span class="token function">getClaim</span><span class="token punctuation">(</span><span class="token string">"access_level"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">asString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里我使用的是 <code>com.auth0.java-jwt</code>，换成 <code>io.jsonwebtoken.jjwt</code> 也不会有太大的区别，都是对 JWT 标准的实现，只是接口上不太相同。我在 JWT 的载荷中存放了用户的 open id 和权限级别，<br>这样在不查询数据库的情况下就可以判断用户是否有权操作部分敏感接口。签名部分的加密方式选择了默认的 SHA-256，换成其他的也可以。至于上面那一串非静态方法注入，<br>则是因为我在 Redis 的工具类中做成了非静态方法，这个 JWT 工具类又做成了静态方法，而把这个静态方法都换成非静态方法又遇到了一堆问题，于是我就直接把非静态方法注入这个类，再去用静态对象调用这个非静态方法。。。<br>下面贴一下 Redis 的工具类：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RedisUtils</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">StringRedisTemplate</span> redisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token class-name">RedisUtils</span><span class="token punctuation">(</span><span class="token class-name">StringRedisTemplate</span> redisTemplate<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate <span class="token operator">=</span> redisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">RedisUtils</span> redisUtils<span class="token punctuation">;</span>    <span class="token comment">/**     * 实现命令：DEL key，删除一个key     *     * @param key     */</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">del</span><span class="token punctuation">(</span><span class="token class-name">String</span> key<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 实现命令：HAS key，返回bool     *     * @param key     */</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">hasKey</span><span class="token punctuation">(</span><span class="token class-name">String</span> key<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> redisTemplate<span class="token punctuation">.</span><span class="token function">hasKey</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 实现命令：SET key value EX seconds，设置key-value和超时时间（秒）     *     * @param key     * @param value     * @param timeout     *            （以ms为单位）     */</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token class-name">String</span> key<span class="token punctuation">,</span> <span class="token class-name">String</span> value<span class="token punctuation">,</span> <span class="token keyword">long</span> timeout<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> timeout<span class="token punctuation">,</span> <span class="token class-name">TimeUnit</span><span class="token punctuation">.</span><span class="token constant">MILLISECONDS</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 实现命令：GET key，返回 key所关联的字符串值。     *     * @param key     * @return value     */</span>    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token class-name">String</span> key<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>本身写的时候还有很多其他方法，这里就只展示用到的了。总的来说比较简单，Redis 也是属于 non-SQL 数据库，只有键值对，操作很方便。需要注意的是 <code>redisTemplate</code> 设置过期时间时，默认是以秒为单位，有需要的话就在后面加上 <code>TimeUnit</code>。</p><h3 id="鉴权系统的设计">鉴权系统的设计</h3><p>显然鉴权处理不应该在每个 Controller 里分别判断，而应该作为一个全局方法，在请求到达 Controller 之前就处理完成。Spring Boot 自带 <code>HandlerInterceptor</code> 接口，因此我们只需要将其实现。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span><span class="token class-name">HttpServletRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">HttpServletResponse</span> response<span class="token punctuation">,</span> <span class="token class-name">Object</span> handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">String</span> token <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">"X-token"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span> <span class="token operator">||</span> token <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token class-name">JWTUtil</span><span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>token<span class="token punctuation">,</span> <span class="token class-name">JWTUtil</span><span class="token punctuation">.</span><span class="token function">getOpenid</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">JWTUtil</span><span class="token punctuation">.</span><span class="token function">getAccessLevel</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>            <span class="token function">returnJson</span><span class="token punctuation">(</span>response<span class="token punctuation">,</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorTokenMsg</span><span class="token punctuation">(</span><span class="token string">"登录信息无效，请重新登录！"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">returnJson</span><span class="token punctuation">(</span><span class="token class-name">HttpServletResponse</span> response<span class="token punctuation">,</span> <span class="token class-name">Object</span> json<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">&#123;</span>        <span class="token class-name">ObjectMapper</span> mapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">String</span> strJson <span class="token operator">=</span> mapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">PrintWriter</span> writer <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>        response<span class="token punctuation">.</span><span class="token function">setCharacterEncoding</span><span class="token punctuation">(</span><span class="token string">"UTF-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        response<span class="token punctuation">.</span><span class="token function">setContentType</span><span class="token punctuation">(</span><span class="token string">"application/json; charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>            writer <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token function">getWriter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            writer<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>strJson<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>writer <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>                writer<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">postHandle</span><span class="token punctuation">(</span><span class="token class-name">HttpServletRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">HttpServletResponse</span> response<span class="token punctuation">,</span> <span class="token class-name">Object</span> handler<span class="token punctuation">,</span> <span class="token class-name">ModelAndView</span> modelAndView<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>    <span class="token punctuation">&#125;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">afterCompletion</span><span class="token punctuation">(</span><span class="token class-name">HttpServletRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">HttpServletResponse</span> response<span class="token punctuation">,</span> <span class="token class-name">Object</span> handler<span class="token punctuation">,</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于我按照 Restful API 的思路来构建的后端，因此登录处理失败时的返回也应该为 JSON。然而正常情况下在这里只能返回文字流。于是我在这里做了一个 <code>returnJson</code> 的方法，将我需要返回的 JSON 转为 String，<br>再在返回的 Header 上加上 ContentType，使得客户端浏览器将其转为 JSON。鉴权则很简单，客户端发起请求时把 Token 放在请求头里，这里从 <code>HttpServletRequest</code> 中获得请求头中的字段进行认证即可。<br>现在完成了登录的鉴权处理，但是后端接收的请求并不会自己跑过来，需要配置一个全局拦截器。Spring Boot 有着 <code>WebMvcConfigurer</code> 的接口，将其实现即可完成我们的想法。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token annotation punctuation">@EnableWebMvc</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomWebConfigurer</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span><span class="token class-name">InterceptorRegistry</span> registry<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LoginInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span><span class="token string">"/login/**"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>把它注册为一个配置类，在其中注册刚才的登录拦截器，添加 <code>/**</code> 路径，把所有请求都转到拦截器中，当然，登录之类的接口不能走拦截器，因此还要添加例外。</p><p>这样，一个基本的未登录用户的拦截功能就做完了。由于我的项目中权限只有两级，敏感权限也没几个，所以只是另外写了个工具类判断是否有高级权限，并没有做进拦截器里。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2020/04/spring-boot-jwt-login/</id>
    <link href="https://blog.gadfly.vip/2020/04/spring-boot-jwt-login/"/>
    <published>2020-04-09T04:49:04.000Z</published>
    <summary>
      <![CDATA[<p>Spring Boot 开发笔记系列第二弹，这次来聊聊 JWT 和登录鉴权系统的设计。</p>]]>
    </summary>
    <title>Spring Boot 开发笔记 - JSON Web Token 与登录鉴权设计</title>
    <updated>2022-03-03T02:24:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="开发" scheme="https://blog.gadfly.vip/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="java" scheme="https://blog.gadfly.vip/tags/java/"/>
    <category term="spring" scheme="https://blog.gadfly.vip/tags/spring/"/>
    <category term="后端" scheme="https://blog.gadfly.vip/tags/%E5%90%8E%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>写这个主要是为了整理开发思路与记录，顺便表示 QQ 的接口真的写的很奇怪。。。</p><span id="more"></span><p>整个项目是一个课程作业，我选择了后端使用 Spring Boot，前端使用 Vue.js 作为项目的技术栈。首先说一下 Auth 2.0。这个在 QQ 的文档里倒也写的很清楚<br><a href="https://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B">OAuth2.0 简介 — QQ 互联 WIKI</a>。它采用第三方应用在客户端根据自己的 AppId 和 Redirect URI（回调地址）来请求 QQ 的授权页面，<br>用户授权后将 Authentication Code 作为 params 跳转到回调地址，回调地址将这个 Code 传给后端，由后端根据 AppId 和 Secret 再向 QQ 申请用户登录的 Access Token，<br>根据这个 Token 后端才能去获取用户授权的相关信息。第三方应用中每个用户都有一个唯一的 OpenId 用于对应唯一的用户，但是不同第三方应用对相同的用户拿到的 OpenId 则是不同的。<br>开发中参考了 <a href="https://segmentfault.com/a/1190000020181967">使用 java 后端的 springboot 环境下实现网站接入 QQ 第三方登录</a>，因此避免了很多坑。</p><h3 id="获取-Access-Token">获取 Access Token</h3><p>QQ 官方文档在这里 <a href="https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0">传送门</a>。这份文档对于没有开发经验的人来说坑实在是太多，不过相比其他平台还是比较友好的。<br>参考文章中是使用了读取配置的方式，我则是写了一个返回 secret 等内容的工具类来获取 QQ 相关配置。</p><p>基本代码如下：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"/login/callback"</span><span class="token punctuation">,</span> consumes<span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token class-name">MediaType</span><span class="token punctuation">.</span><span class="token constant">APPLICATION_JSON_VALUE</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token class-name">GlobalJSONResult</span> <span class="token function">handleCallbackCode</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> <span class="token class-name">LoginCode</span> reqParams<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">JsonProcessingException</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> authorization_code <span class="token operator">=</span> reqParams<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtils</span><span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>authorization_code<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorMsg</span><span class="token punctuation">(</span><span class="token string">"code无效，请重新授权！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">//client端的状态值。用于第三方应用防止CSRF攻击。</span>    <span class="token class-name">String</span> state <span class="token operator">=</span> reqParams<span class="token punctuation">.</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>state<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"login"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorMsg</span><span class="token punctuation">(</span><span class="token string">"state无效，请确认是否为本人操作！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token class-name">String</span> access_token <span class="token operator">=</span> <span class="token function">getAccessToken</span><span class="token punctuation">(</span>authorization_code<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtils</span><span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>access_token<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorMsg</span><span class="token punctuation">(</span><span class="token string">"access_token获取失败，请重新授权！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 下略</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token class-name">String</span> <span class="token function">getAccessToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> authorization_code<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> urlForAccessToken <span class="token operator">=</span> <span class="token function">getUrlForAccessToken</span><span class="token punctuation">(</span>authorization_code<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> firstCallbackInfo <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>urlForAccessToken<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> params <span class="token operator">=</span> firstCallbackInfo<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">"&amp;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> access_token <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">String</span> param <span class="token operator">:</span> params<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> keyvalue <span class="token operator">=</span> param<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">"="</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>keyvalue<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"access_token"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            access_token <span class="token operator">=</span> keyvalue<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> access_token<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">getUrlForAccessToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> authorization_code<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> grant_type <span class="token operator">=</span> <span class="token string">"authorization_code"</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> client_id <span class="token operator">=</span> <span class="token class-name">QQLoginUtil</span><span class="token punctuation">.</span><span class="token function">getQQLoginClientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> client_secret <span class="token operator">=</span> <span class="token class-name">QQLoginUtil</span><span class="token punctuation">.</span><span class="token function">getQQLoginClientSecret</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> redirect_uri <span class="token operator">=</span> <span class="token class-name">QQLoginUtil</span><span class="token punctuation">.</span><span class="token function">getQQLoginRedirectUri</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> url <span class="token operator">=</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"https://graph.qq.com/oauth2.0/token"</span> <span class="token operator">+</span>                    <span class="token string">"?grant_type=%s&amp;client_id=%s&amp;client_secret=%s&amp;code=%s&amp;redirect_uri=%s"</span><span class="token punctuation">,</span>            grant_type<span class="token punctuation">,</span> client_id<span class="token punctuation">,</span> client_secret<span class="token punctuation">,</span> authorization_code<span class="token punctuation">,</span> redirect_uri<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> url<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中 <code>GlobalJSONResult</code> 是我编写的全局 JSON 返回类，有空会在其他文章中说明。QQ 在文档中要求生成 CSRF Token 一类的东西作为 state 参数上传，以避免 CSRF 攻击。<br>不过由于我的登录系统是只有授权 QQ 登录之后才会生成账户，因此基本没有这方面的风险，就把 state 写死了。接收的 <code>reqParams</code> 则是直接前端将路由中的参数返回，Vue.js 中使用 <code>this.$route.query</code> 就可以将参数以 JSON 形式获取。</p><h4 id="解析接口返回的数据">解析接口返回的数据</h4><blockquote><p>之后就是跳转这个 URL 去获取 access_token，这里就是第一个坑了，按照官方文档，搞得好像这次我们跳转到这个获取 access_token 的 URL 后，腾讯那边会跳转我们设定的回调地址并带上我们需要的参数，就像之前获取 authorization code 一样。<br>但完全不是这样的！！！你按照要求向这个获取 access_token 的 URL 发送请求后，对方并不会再跳转，而是直接返回你一个数据，希望你获得这个数据然后处理。这有点像前端 JS 的异步请求后后回调函数处理 data。</p></blockquote><p>所以这里使用了 <code>restTemplate</code> 来发起请求。虽然很多教程里都会说使用 <code>@Autowired</code> 来注入实例，但是你会发现 IDEA 中会提示这种写法不被推荐，使用构造函数注入是更好的选择。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//注入实例</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">UserInfoRepository</span> userInfoRepository<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">RestTemplate</span> restTemplate<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token class-name">LoginController</span><span class="token punctuation">(</span><span class="token class-name">UserInfoRepository</span> userInfoRepository<span class="token punctuation">,</span> <span class="token class-name">RestTemplate</span> restTemplate<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>userInfoRepository <span class="token operator">=</span> userInfoRepository<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>restTemplate <span class="token operator">=</span> restTemplate<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>restTemplate 可以接收所有的返回参数，然而 QQ 这里采用了一个非常神奇的接口返回形式：<code>access_token=FE04**CCE2&amp;expires_in=7776000&amp;refresh_token=88E4**BE14</code>。<br>请<strong>不要</strong>认为这是在回调地址后面加上的参数，这个就是 token 接口返回的一个<strong>字符串</strong>。这接口真是绝了，写的人不觉得别扭吗？？？拿到这个字符串之后只能根据 &amp; 和 = 拆分来获取数据了。</p><p>我这边使用前端将参数上传而不是直接把回调地址设为后端地址的原因主要是考虑到拿到 Code 之后，后端处理需要一定的时间，如果不能在前端展示动画之类的内容，容易让用户不明所以，因此前端拿到参数再提交给后端，同时前端展示等待动画是更好的选择。</p><h3 id="获取-Open-Id">获取 Open Id</h3><p>拿到这个 token 之后就可以去获取用户的 openid 了。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 上略</span>  <span class="token class-name">String</span> url <span class="token operator">=</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"https://graph.qq.com/oauth2.0/me?access_token=%s"</span><span class="token punctuation">,</span> access_token<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//第二次模拟客户端发出请求后得到的是带openid的返回数据,格式如下</span>  <span class="token comment">//callback( &#123;"client_id":"YOUR_APPID","openid":"YOUR_OPENID"&#125; );</span>  <span class="token class-name">String</span> secondCallbackInfo <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//正则表达式处理</span>  <span class="token class-name">String</span> regex <span class="token operator">=</span> <span class="token string">"\\&#123;.*\\&#125;"</span><span class="token punctuation">;</span>  <span class="token class-name">Pattern</span> pattern <span class="token operator">=</span> <span class="token class-name">Pattern</span><span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span>regex<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token class-name">Matcher</span> matcher <span class="token operator">=</span> pattern<span class="token punctuation">.</span><span class="token function">matcher</span><span class="token punctuation">(</span>secondCallbackInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>matcher<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"异常的回调值: "</span> <span class="token operator">+</span> secondCallbackInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorMsg</span><span class="token punctuation">(</span><span class="token string">"异常的回调值: "</span> <span class="token operator">+</span> secondCallbackInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">//调用jackson</span>  <span class="token class-name">ObjectMapper</span> objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">></span></span> hashMap <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">readValue</span><span class="token punctuation">(</span>matcher<span class="token punctuation">.</span><span class="token function">group</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">HashMap</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token class-name">String</span> openid <span class="token operator">=</span> hashMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"openid"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 下略</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里 QQ 又返回了什么呢，返回了一个 JSONP 。。。形如：<code>callback( {&quot;client_id&quot;:&quot;YOUR_APPID&quot;,&quot;openid&quot;:&quot;YOUR_OPENID&quot;} )</code>不是，腾讯你们这玩意是分了几个人写啊，怎么每个接口返回格式都这么奇怪啊喂！！！<br>处理这个则是使用正则，先把 <code>callback</code> 里的对象取出来，用 Spring Boot 自带的 Jackson 解析为 Map。</p><blockquote><p>这里有两点值得一说</p><p>其一，为什么 String regex = &quot;\\{.*\\}&quot;;，正则表达式中有\\这东西呢？这时因为正则表达式中{和}都是有意义的，非字符的，我们希望正则表达式把它们理解成字符，就需要对它们进行转义，所以这里需要一个转义符\，但\自身在 java 字符串中并不是字符，所以我们还要转义\自身，所以会出现\\。</p><p>其二，matcher 如果不经历 matcher.find()，则就算有合适的匹配内容，也仍然不会有任何匹配能得到。所以 matcher.find()是必须的，同时 matcher.find()一次后再来一次，那完了，返回 false。</p></blockquote><h3 id="获取用户信息与登录-Token-下发">获取用户信息与登录 Token 下发</h3><p>教程到这里结束了，而我还需要完成获取用户信息等操作才能完成整个登录接口。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java">  <span class="token comment">// 上略</span>    <span class="token comment">// 获取QQ用户信息</span>    <span class="token class-name">String</span> user_info_url <span class="token operator">=</span> <span class="token function">getUserInfoUrl</span><span class="token punctuation">(</span>access_token<span class="token punctuation">,</span> openid<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> user_result <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>user_info_url<span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> user_info_qq <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">readValue</span><span class="token punctuation">(</span>user_result<span class="token punctuation">,</span> <span class="token class-name">Map</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span> user_info_qq<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"ret"</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">errorMsg</span><span class="token punctuation">(</span><span class="token string">"用户信息获取失败，请重试"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token class-name">String</span> token <span class="token operator">=</span> <span class="token function">getToken</span><span class="token punctuation">(</span>openid<span class="token punctuation">,</span> user_info_qq<span class="token punctuation">)</span><span class="token punctuation">;</span>    user_info_qq<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"token"</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token class-name">GlobalJSONResult</span><span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>user_info_qq<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 下略</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">getUserInfoUrl</span><span class="token punctuation">(</span><span class="token class-name">String</span> access_token<span class="token punctuation">,</span> <span class="token class-name">String</span> openid<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> client_id <span class="token operator">=</span> <span class="token class-name">QQLoginUtil</span><span class="token punctuation">.</span><span class="token function">getQQLoginClientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> url <span class="token operator">=</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"https://graph.qq.com/user/get_user_info"</span> <span class="token operator">+</span>            <span class="token string">"?access_token=%s&amp;oauth_consumer_key=%s&amp;openid=%s"</span><span class="token punctuation">,</span> access_token<span class="token punctuation">,</span> client_id<span class="token punctuation">,</span> openid<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> url<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token class-name">String</span> <span class="token function">getToken</span><span class="token punctuation">(</span><span class="token class-name">String</span> openid<span class="token punctuation">,</span> <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> user_info_qq<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> uid <span class="token operator">=</span> <span class="token constant">UUID</span><span class="token punctuation">.</span><span class="token function">nameUUIDFromBytes</span><span class="token punctuation">(</span>openid<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> access_level<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>userInfoRepository<span class="token punctuation">.</span><span class="token function">existsById</span><span class="token punctuation">(</span>uid<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">UserInfo</span> userInfo <span class="token operator">=</span> userInfoRepository<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>uid<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        userInfo<span class="token punctuation">.</span><span class="token function">setNickname</span><span class="token punctuation">(</span>user_info_qq<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"nickname"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        userInfo<span class="token punctuation">.</span><span class="token function">setAvatarUrl</span><span class="token punctuation">(</span>user_info_qq<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"figureurl_2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        userInfoRepository<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>userInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>        access_level <span class="token operator">=</span> userInfo<span class="token punctuation">.</span><span class="token function">getAccessLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">UserInfo</span> userInfo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">UserInfo</span><span class="token punctuation">(</span>uid<span class="token punctuation">,</span> openid<span class="token punctuation">,</span> user_info_qq<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"nickname"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                user_info_qq<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"figureurl_2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        userInfoRepository<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>userInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>        access_level <span class="token operator">=</span> userInfo<span class="token punctuation">.</span><span class="token function">getAccessLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> <span class="token class-name">JWTUtil</span><span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span>openid<span class="token punctuation">,</span> access_level<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>获取用户信息的接口正常多了，返回的是 JSON。主要可以聊聊的是 JWT，不过这个也放到下次再讲吧。</p>]]>
    </content>
    <id>https://blog.gadfly.vip/2020/04/springboot-auth2-with-QQ/</id>
    <link href="https://blog.gadfly.vip/2020/04/springboot-auth2-with-QQ/"/>
    <published>2020-04-08T09:29:38.000Z</published>
    <summary>
      <![CDATA[<p>写这个主要是为了整理开发思路与记录，顺便表示 QQ 的接口真的写的很奇怪。。。</p>]]>
    </summary>
    <title>Spring Boot 开发笔记 - 使用 Spring Boot 完成 Server-Side 模式的 QQ 第三方登录</title>
    <updated>2024-07-02T16:57:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>猪蹄宝宝</name>
    </author>
    <category term="NFC" scheme="https://blog.gadfly.vip/categories/NFC/"/>
    <category term="单片机" scheme="https://blog.gadfly.vip/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <category term="IC 卡" scheme="https://blog.gadfly.vip/tags/IC-%E5%8D%A1/"/>
    <category term="近场通信" scheme="https://blog.gadfly.vip/tags/%E8%BF%91%E5%9C%BA%E9%80%9A%E4%BF%A1/"/>
    <content>
      <![CDATA[<p><small><em>本文中提到的<code>卡片</code>，若非特别注明，均为非接触式卡片，不再另外声明。</em></small></p><h2 id="概述">概述</h2><p>首先分析 NFC 和我们身边的 IC 卡之前，我们需要先介绍一些基本的概念：</p><blockquote><p>近场通信技术（Near-field communication，NFC）由非接触式射频识别（RFID）演变而来，由飞利浦半导体（现恩智浦半导体）、诺基亚和索尼共同于 2004 年研制开发，其基础是 RFID 及互连技术。近场通信是一种短距高频的无线电技术，在 13.56MHz 频率运行于 20 厘米距离内。其传输速度有 106 Kbit/秒、212 Kbit/秒或者 424 Kbit/秒三种。当前近场通信已通过成为 ISO/IEC IS 18092 国际标准、EMCA-340 标准与 ETSI TS 102 190 标准。NFC 采用主动和被动两种读取模式。</p><p>每一个完整的 NFC 设备可以用三种模式工作：</p><ul><li>卡模拟模式（Card emulation mode）：这个模式其实就是相当于一张采用 RFID 技术的 IC 卡。可以替代现在大量的 IC 卡（包括信用卡）场合商场刷卡、IPASS、门禁管制、车票、门票等等。此种方式下，有一个极大的优点，那就是卡片通过非接触读卡器的 RF 域来供电，即便是寄主设备（如手机）没电也可以工作。NFC 设备若要进行卡片模拟（Card Emulation）相关应用，则必须内置安全组件（Security Element, SE）之 NFC 芯片或通过软件实现主机卡模拟(Host Card Emulation，HCE)。</li><li>读卡器模式（Reader/Writer mode）：作为非接触读卡器使用，比如从海报或者展览信息电子标签上读取相关信息。</li><li>点对点模式（P2P mode）：这个模式和红外线差不多，可用于数据交换，只是传输距离较短，传输创建速度较快，传输速度也快些，功耗低（蓝牙也类似）。将两个具备 NFC 功能的设备链接，能实现数据点对点传输，如下载音乐、交换图片或者同步设备地址薄。因此通过 NFC，多个设备如数字相机、PDA、计算机和手机之间都可以交换资料或者服务。</li></ul><p><small><em>摘自：<a href="https://zh.wikipedia.org/wiki/%E8%BF%91%E5%A0%B4%E9%80%9A%E8%A8%8A">zh.wikipedia.org/wiki/近場通訊</a></em></small></p></blockquote><p>这么大一段话概括一下的话，大致意思是：NFC 是一种使得数据可以在几厘米的范围内进行传输的技术，我们身边常见的大陆二代身份证、电子护照、公交卡等都属于这种技术的具体应用。<br>NFC 在我们身边的具体应用场景有很多，有时我们会将其错误地认为是其他技术的实现。比如大家常说银行卡、公交卡等“消磁了”，但事实上现在这些卡片已经不再是使用磁条等传统的磁性物质记录数据，而是支持非接触式射频识别（RFID）的电子芯片。他们出现失效的情况一般也不是因为某些磁性物质损坏了，而是其中的线圈或者芯片遭受了物理损伤，比如卡片被弯折等，或者是其他使用类似技术的读写卡器等破坏了其中存储的数据导致无法正常读取。</p><p>传统的 RFID 技术我这里就不多做解释，将其理解为某种特殊的无线电通信技术即可。NFC 只是限于 13.56MHz 的频段，而 RFID 的频段有低频（125KHz 到 135KHz），高频（13.56MHz）和超高频（860MHz 到 960MHz）之间。NFC 工作有效距离小于 10cm，所以具有很高的安全性，RFID 工作有效距离从几米到几十米都有。RFID 标准较多，统一较为复杂（估计是没可能统一的了），只能在特殊行业有特殊需求下，采用相应的技术标准。<br>传统 RFID 最常见的应用是图书馆的书籍，不是特别寒酸的图书馆的藏书上都会贴有特殊的标签，这种标签可以在一个比较大的范围内被读取，这样就可以实现简便的自助化的图书借阅管理，读者将书放在特定的区域就可以被读取到借阅了哪些书，而不像以前的图书馆需要管理员将扉页上的藏书条形码进行人工扫描，大大提升了效率。许多物流运输也通过类似的方法来管理运输箱，通过机器就可以远程分拣，也方便管理。NFC 就是在这样的技术上发展而来，更适合一般日常生活的使用。</p><h2 id="生活中的各种可以刷的卡都是-NFC-么">生活中的各种可以刷的卡都是 NFC 么</h2><p>答案显然是否定的。要看一张卡是不是支持 NFC 技术的卡有以下几种方法：</p><ol><li><p>用一张支持 NFC 的安卓手机刷一下<br>虽然手机厂商在宣传 NFC 时有很多会说什么“全功能 NFC”之类的，但是那只是针对之前 wikipedia 上提到的“卡模拟模式”说的，在读取 NFC 卡片这件事情上，除了 iPhone 以外，大家都没有特别大的区别。这边推荐一个名为“MIFARE 经典工具”的 app，在 GitHub 和 Google Play 上都可以下载：</p><ul><li>Play： <a href="https://play.google.com/store/apps/details?id=de.syss.MifareClassicTool">de.syss.MifareClassicTool</a></li><li>GitHub： <a href="https://github.com/ikarus23/MifareClassicTool">ikarus23/MifareClassicTool</a></li><li>F-Droid：<a href="https://f-droid.org/packages/de.syss.MifareClassicTool/">de.syss.MifareClassicTool</a></li><li><em>可用的国内镜像我之后再补充</em><br>使用这个 app 可以读取到 NFC 卡片中的部分信息，不过这个 app 并非支持所有的 NFC 卡片，只支持 MIFARE 系列及相似技术的模拟卡（这个问题稍后再说）。首先这个 app 能识别的肯定是 NFC 卡片，如果 app 没有反应，但是手机中的其他应用，比如 QQ、微信、金融类 app 等对其有反应，那么这张卡片也是 NFC 卡片的一种。</li></ul></li><li><p>如果卡片本身不是特别厚，并且是一般的长方形卡片，则可以用手电筒等能在较小的范围内聚集大量光源的设备照亮这张卡。如果发现里面的小芯片在卡片的某一边，外面有一圈贴近卡片边缘的线圈，形如下图，那么这一般也是 NFC 技术的卡片。<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/ic_coil.png" alt=""></p></li><li><p>如果卡片上没有 10+8 位的数字，也没有 8-10 位的数字，并且没有标注 HID 之类的字样，那么这张卡一般也是一种 NFC 卡片。</p></li></ol><p>那么如果我们平时使用的非接触式卡片不是 NFC 的卡片的话，那么又是什么卡片呢？最常见的就是 ID 卡了。</p><blockquote><p>ID 卡全称为身份识别卡（Identification Card），是一种不可写入的感应卡，含固定的编号。ID 卡与磁卡一样，都仅仅使用了“卡的号码”而已，卡内除了卡号外，无任何加密存储功能，其“卡号”是公开、裸露的。ISO 标准 ID 卡的规格为：85.5x54x0.80±0.04mm（高/宽/厚），市场上也存在一些厚、薄卡或异型卡。</p></blockquote><p>最常见的异型卡就是下图右边这种，统称为钥匙扣：<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/id_card.png" alt=""><br><img src="/images/227387.gif" data-original="/images/posts/2019/09/id_coil.png" alt=""><br>而 NFC 卡片则一般统称为 IC 卡：</p><blockquote><p>智能卡（英语：Smart card 或 IC Card），又称智慧卡、聪明卡、集成电路卡及 IC 卡。是指粘贴或嵌有集成电路芯片的一种便携式卡片塑胶。卡片包含了微处理器、I/O 接口及存储器，提供了数据的运算、访问控制及存储功能，卡片的大小、接点定义当前是由 ISO 规范统一，主要规范在 ISO7810 中。常见的有电话 IC 卡、身份 IC 卡，以及一些交通票证和存储卡。</p><p><small><em>摘自：<a href="https://zh.wikipedia.org/wiki/%E6%99%BA%E6%85%A7%E5%8D%A1">zh.wikipedia.org/wiki/智慧卡</a></em></small></p></blockquote><p>不过 IC 卡不是只有非接触式的，手机使用的 sim 卡、银行卡上那个裸露的金属芯片也是 IC 卡，但是是接触式的。本文中则只讨论非接触式 IC 卡。</p><h2 id="非接触式-IC-卡">非接触式 IC 卡</h2><p>既然技术已经存在，那么就需要一定的标准来将其统一、规范化。<br>非接触式 IC 卡一般有三种国际规范：ISO/IEC 14443 Type A、ISO/IEC 14443 Type B、ISO/IEC 15693。三个规范都规定了工作在 13.56Mhz 下智能标签和读写器的空气接口及数据通信规范，但是类型不同。我们生活中更常见的是 ISO/IEC 14443 Type A 和 ISO/IEC 14443 Type B 规范的设备，15693 则与公众关系不太密切，因此本文作为科普暂不讨论。</p><h3 id="14443-A">14443-A</h3><p>14443-A 最为常见，其中最为知名的是前文提到的 MIFARE 系列，国内的复旦卡等早期均是模仿 MIFARE 系列开发的，MIFARE 的解决方案最为流行，也是大家都兼容的方案。</p><blockquote><p>MIFARE 是恩智浦半导体公司（NXP Semiconductors）拥有的一系列非接触式智能卡和近傍型卡技术的注册商标。<br>MIFARE 包括一系列依循 ISO/IEC 14443-A 规格，利用无线射频识别（频率为 13.56MHz）的多种非接触式智能卡专有解决方案。这项技术是最早是 1994 年由米克朗集团（Mikron Group）开发，在 1998 年转售给飞利浦电子公司（2006 年更名为恩智浦半导体公司）。近年来 MIFARE 已经普遍在日常生活当中使用，如大众运输系统付费、商店小额消费、门禁安全系统、借书证等。</p><p><small><em>摘自：<a href="https://zh.wikipedia.org/wiki/MIFARE">zh.wikipedia.org/wiki/MIFARE</a></em></small></p></blockquote><p><img src="/images/227387.gif" data-original="/images/posts/2019/09/jiagou.png" alt=""><br>上图为 MIFARE 卡片的架构，</p><blockquote><p>UID：唯一标识符（Unique Identifier）， RID：安全随机标识符（Random Security Identifier）</p><ul><li><p>卡片架构：卡片上面有一组唯一标识符、通信接口（包含天线及调制解调器）以及一个 ASIC 里面包含了通信逻辑电路、加密控制逻辑电路与数据存储区（ EEPROM），可以作为电子钱包或其它门禁、差勤考核、借书证等用途。</p></li><li><p>数据存储区块：可分 16 个区段（sector 0-15）， 每个区段由 4 个区块（block 0-3）组成，而每个区块都是独立的单元，每 1 个区块的容量有 16Byte。而每个区段的最后一个区块则用来存放 2 组密钥（KeyA、KeyB），以及密钥对应各自的访问权限（Access bit）。</p></li><li><p>每张卡片第一区段的第一区块（sector 0，block 0）只能读取无法写入数据，称为制造商代码（Manufacturer Code）, 第 1－4byte 为 UID。第 5byte 为比特计数检查码（bit count check)，其余的存放卡片制造商的数据。所以每张卡片实际能使用的只有 15 个区段，即便如此也可用于 15 个不同的应用。</p></li><li><p>读写卡机架构：读卡器包含 CPU、电源模块、读（写）模块、记忆模块、控制模块等，有些还有显示模块、定时模块等其他模块。</p></li><li><p>工作流程：当卡片接近读写卡机进入通信天线的感应范围（约 2.5 公分至 10 公分）之后，读写卡机便会提供微量电力（约达 2 伏特之后）驱动卡片上的电路。此时卡、机各以曼彻斯特编码（MANCHESTER Encoding）及米勒编码（Miller encoding）加密通信内容后再以振幅偏移调制（Amplitude Shift Keying，ASK）透过调制解调器收发无线电波信号互相验证是否为正确卡片，如果验证结果正确读写卡机就会确认要访问的数据存储区块，并对该区块进行密码校验，在卡、 机三重认证无误之后，就可以透过加密进行实际工作通信。这个过程大约只需要 0.1 秒就可以完成。如果同时有多张卡片进入读写卡机感应范围，读写卡机会将卡 片编号并选定 1 张卡片进行验证直到完成所有卡片验证（称为防碰撞机制）或是离开感应范围为止。</p></li><li><p>卡 机三重认证步骤：1.卡片产生一个随机数 RB 发送到读卡器。2.读卡器会将接收到的随机数 RB 依公式加密编码后的 TokenAB 数值并发送回卡片。3.卡片接 收到 TokenAB 后，会把加密部分解译出来然后比对参数 B、随机数 RB。同时并依据收到的随机数 RA，引用公式编码后产生 TokenBA 发送回读卡器。4. 读卡器接收到 TokenBA 后，又把加密过的部分解译，比较随机数 RB，RA 与 TokenBA 中解出之 RB、RA 是否相符，正确的就可以完成指令（扣款、打 开门锁或是登记其他事项）。</p></li></ul><p><small><em>摘自：<a href="https://zh.wikipedia.org/wiki/MIFARE">zh.wikipedia.org/wiki/MIFARE</a></em></small></p></blockquote><p>MIFARE 系列最常用的就是各种 1K 卡，也就是存储容量为 1Kbytes 的卡。简单来说，1K 卡将整张卡片的数据区域分割为 16 个扇区，4 个区块，每个区块长度为 32 位，每一位都是 0-F 的 16 进制数，每 2 位为 1Byte，所以每一块为 16Bytes，总容量<code>16 * 4 * 16 = 1024Bytes = 1K</code>。每个扇区各自独立，正常情况下，0 扇区 0 块：第 1－4byte 为 UID，第 5byte 为比特计数检查码（bit count check)，其余的存放卡片制造商的数据，包括卡片类型等。整个 0 扇区一般无法被更改。剩下 15 个扇区中每个扇区只有三个块可用，每个扇区的最后一个块的结构为：12 位 KeyA+8 位控制字+12 位 KeyB。控制字负责控制整个扇区的读写状态，KeyA 和 B 用于开发者自行根据需要来控制使用不同 Key 的权限。一定程度上算是安全的，但是每个密钥均为固定，所以通过一定的暴力破解和逆向手段，也有可能获得卡片的全部密钥，安全性不足。（下图为数据区块示意图）（没看懂也没事，下文会详细分析的）<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/blocks.png" alt=""></p><h3 id="14443-B">14443-B</h3><p>14443-B 则用于安全性更高的产品，如二代身份证、电子护照等。事实上，针对 14443-B 的破解手段也非常罕见，因此我也难以展开细讲。</p><h2 id="MIFARE-卡片的破解">MIFARE 卡片的破解</h2><p>刚介绍了一些原理什么的，我们就开始对其进行破解，是不是哪里不太对？其实这边介绍破解的技术和思路可以更容易地理解 MIFARE 卡片的原理。之前讲的都是理论概念，这里开始实际操作。</p><p>首先，针对 MIFARE 卡片的研究，我们可以使用由 Jonathan Westhues 在做硕士论文中研究 Mifare Classic 时设计、开发的一款开源硬件<strong>Proxmark3</strong>，可以用于 RFID 中嗅探、读取以及克隆等相关操作。（其他的还有 PN532、ACR122U 等，这里不展开细说）这个名为 Proxmark3 的设备在某宝即可获得，售价几百元不等，一般学习的话选择最便宜的即可。一般卖家会给你提供这样的一个软件以便操作。<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/pm3_easy_gui.png" alt=""><br>具体的操作参考卖家给出的教程即可，这里主要对技术问题进行分析。</p><h3 id="默认密码和-PRNG">默认密码和 PRNG</h3><p>教程中一般会提到：使用默认密码扫描无法被破解的卡片可以用 PRNG 破解功能来进行下一步操作。那么默认密码是什么？<br>由于 MIFARE 的机制，导致卡片中每一个扇区都必须存在密码才能使用，因此需要存在约定俗称的几个普通密码，使用这些常见的密码可以方便在未激活时修改数据，必要时将其更改为更可靠的密码即可。默认密码中一般有：</p><ul><li>FFFFFFFFFFFF</li><li>A0A1A2A3A4A5</li><li>000000000000</li><li>(...)<br>其他还有很多，不同的地方定义不同，但是共同点都是非常简单，很容易猜测。如果卡片中存在这样的密码可以被扫描到，那么这张卡的安全性非常薄弱，克隆甚至修改都不成问题。<br>如果不存在默认密码，那么难道只能穷尽所有的可能，逐一尝试密码吗？事实上 MIFARE 卡片本身的机制也存在一定的问题，这个问题导致了 darkside 攻击的存在：PRNG 破解。<br>卡片密钥破解的关键是让卡片发送加密数据，再通过算法解出密钥，所以需要欺骗卡片发出加密数据。我们考虑首先要把卡片中的密钥相关的数据骗出来，也就是让卡片发送出来一段加密的数据，我们通过这段加密的数据才能把密钥破解出来，如果卡片不发送加密的数据给我们，那就没法破解了。而第一次验证的时候卡片会发送明文的随机数给读卡器，然后验证读卡器发送加密数据给卡片，卡片验证失败就停止，不会发送任何数据了，不过，经过研究人员大量的测试后发现卡片算法中存在漏洞，当读卡器发出的密文中某 8bit 数据正确时，读卡器就会回复一个 4bit 的密文，而这个密文就包含了密钥的信息，再通过解密算法即可解出密钥。Linux 下的 mfcuk（MiFare Classic Universal toolKit）就是这样一个基于 darkside 原理攻击全加密卡的程序。GitHub：<a href="https://github.com/nfc-tools/mfcuk">nfc-tools/mfcuk</a>。因此，通过这个原理，就可以使用读卡器进行 darkside 攻击，也就是一般所说的 PRNG 破解。<br>关于 PRNG 的详细内容可以看以下资料：</li></ul><blockquote><p>伪随机数生成器（pseudo random number generator，PRNG），又被称为确定性随机比特生成器（deterministic random bit generator，DRBG），是一个生成数字序列的算法，其特性近似于随机数序列的特性。PRNG 生成的序列并不是真随机，因此它完全由一个初始值决定，这个初始值被称为 PRNG 的随机种子（seed，但这个种子可能包含真随机数）。尽管接近于真随机的序列可以通过硬件随机数生成器生成，但伪随机数生成器因为其生成速度和可再现的优势，在实践中也很重要。<br>PRNG 是模拟（例如，蒙特卡洛方法）、电子游戏（例如过程生成）以及密码学等应用的核心。加密应用程序要求不能从以前的输出中预测输出，而且更复杂的、不具有简单 PRNGs 线性特性的算法是必要的。<br>良好的统计特性，是 PRNG 的核心。通常，需要严格的数学分析来证明 PRNG 生成的序列足够接近真随机以满足预期用途。John von Neumann（约翰·冯·诺伊曼）警告不要把 PRNG 错误地解释为真随机数生成器。</p><p>PRNG 通过设定随机种子可以从任意初始值开始生成。同样的初始值总是生成同样的序列。PRNG 的周期定义为：所有初始值的最大长度的无重复前缀序列。周期受状态数的限制，通常用比特位数表示。然而，每增加一个比特位，周期长度就可能增加一倍，所以构建周期足够长的 PRNG 对于许多实际应用程序来说是很容易的。<br>如果 PRNG 的内部状态包含 n 位，那么它的周期不会超过 2n，甚至可能非常短。对于大多数 PRNG，周期长度的计算并不需要遍历整个周期。线性反馈移位寄存器（LFSR）的周期通常正好是 2n−1。线性同余方法的周期可以通过因式分解进行计算。 尽管 PRNG 在达到周期之后会出现重复的结果，但重复序列的出现并不意味着到达了一个周期，因为它的内部状态可能比输出要大很多。对于输出为 1 位的 PRNGs，这一点尤其明显。</p><p><small><em>摘自：<a href="https://zh.wikipedia.org/wiki/%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8">zh.wikipedia.org/wiki/伪随机数生成器</a></em></small></p></blockquote><h3 id="nested-authentication-攻击（大家常说的验证漏洞攻击）">nested authentication 攻击（大家常说的验证漏洞攻击）</h3><p>首先我们需要了解卡片本身的验证逻辑：<br>第一次验证时，读卡器首先验证 0 扇区的密码，卡片给读卡器发送一个随机数 n1（明文），然后读卡器通过跟密码相关的加密算法加密 n1，同时自己产生一个密文随机数 n2，发送给卡片。卡片用自己的密码解密之后，如果解密出来的就是自己之前发送的 n1，则认为正确，然后通过自己的密码相关的算法加密读卡器的随机数 n2 成为密文 n3，发送给读卡器。读卡器解密之后，如果跟自己之前发送的随机数 n2 相同，则认为验证通过，之后所有的数据都通过此算法加密传输。<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/nested.png" alt=""><br>首先记住这里面只有第一次的 n1 是明文，之后都是密文，而且 n1 是卡片发送的，也就是验证过程中，卡片是主动先发随机数的。我们破解的时候，读卡器中肯定没有密码（如果有就不用破解了），那么卡片发送一个 n1 给读卡器之后，读卡器用错误的密码加密之后发送给卡片，卡片肯定解密错误，然后验证中断，这个过程中，我们只看到卡片发送的明文随机数，卡片根本没有把自己保存的密码相关的信息发送出来，那怎么破解呢？<br>所以，要已知一个扇区的密码，第一次验证的时候，使用这个扇区验证成功之后，后面所有的数据交互都是密文，读其他扇区数据的时候，也需要验证，也是卡片首先发送随机数 n1，但是这里的 n1 是加密的数据。既然每个扇区的密码是独立的，那么现在的加密实际上就是通过卡片被读取的，相对于第一个读取的扇区的“其他扇区”的密码相关的算法加密的 n1，这个数据中就包含了这个扇区的密码信息，所以我们才能够通过算法漏洞继续分析出扇区的密码是什么。<br>这也是为什么 nested authentication 攻击必须要知道某一个扇区的密码，然后才能破解其他扇区的密码。</p><h3 id="嗅探">嗅探</h3><p>以上两个破解方式组合就可以解决大部分的 M1 卡片了，但是可能出现例外，所以我们还有一种方法是嗅探攻击。由于卡片和读卡器之间的通信是在几厘米之内的无线通信，所以使用 Proxmark3 也可以对其进行监控分析。<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/snoop.png" alt=""><br>如上图，刷一次卡后，拿开等待几秒，电脑会返回嗅探到的数据。注意寻找 60 或者 61 开头的数据，60 含义是使用 A 密码访问，61 是使用 B 密码。开头是 RDR 的是读卡机发出的指令，TAG 则是卡片发出的指令。红圈中表示读卡机访问了第 21 个块。21 是十六进制，转换成十进制是 33 块第一个方框“b2a6de1d”是卡片 UID，第二个方框“f80eee3c”是 tag challenge(卡片挑战数)，第三个方框“4ec88403”是 reader challenge(读卡器挑战数)，第四个方框“d2dd5180”是 reader respones(读卡器响应数)，第五个方框“2bb17b5e”是 tag respones(卡片回应数)，依次填入“crapto1gui.exe”这个软件中。<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/crapto1.png" alt=""><br>点击 crak key 即可计算出密匙，结论是：读卡机使用 KeyA 访问了第 33 块，使用的密码是 FFFFFFFFFFFF。<br>由于 M1 卡在读卡机和卡片交互数据和密码时，使用了 crypto1 算法。即便同一张卡，同样的密码，嗅探得到交互数据也是随机的，但是他存在破解算法 crapto1。只要获得前面提到的四组随机数组，以及 UID，就可以反解出密匙。通过 crapto1 之类的计算工具就可以计算出需要的数据。</p><h2 id="白卡">白卡</h2><p>通过以上的破解分析，我们了解了 M1 卡的运行原理和安全漏洞。根据这些安全漏洞，万能的华强北制作出了许多仿制 M1 的白卡，又被国外称之为 Chinese Magic Card。同时也存在后门指令，被称为 chinese magic backdoor command。</p><h3 id="UID-卡">UID 卡</h3><ul><li>所有区块可被重复读写</li><li>卡片 UID 可改且使用后门指令更改 UID</li><li>UID 可被重复修改</li><li>响应后门指令(意味着可被使用后门指令检测是否为克隆卡的机器发现)</li></ul><h3 id="CUID-卡">CUID 卡</h3><ul><li>所有区块可被重复读写</li><li>卡片 UID 可改且使用普通写指令更改 UID</li><li>不响应后门指令(意味着不容易被反克隆系统发现)</li></ul><h3 id="FUID-卡">FUID 卡</h3><ul><li>0 扇区可写且仅可写入一次</li><li>写入后 0 扇区不可更改</li><li>不响应后门指令</li></ul><p>这些卡某宝售价大约 0.2-1 元一张，非常便宜，异形卡则稍微贵一点。UID 卡只要通过读卡器验证是否存在后门指令即可识别，因此反克隆技术非常成熟（然而我们学校并没有 ORZ）。</p><h2 id="CPU-卡">CPU 卡</h2><p>那么手机中的 NFC 模块有公交卡、银行卡闪付，甚至还有模拟门禁卡的功能，这是使用的以上几种白卡的技术完成的吗？事实上并不是。手机中的 NFC 模块目前多为全功能的 NFC 模组，可以实现 NFC 技术的大部分读写功能，也可以自行模拟为 CPU 卡。CPU 卡是 MIFARE 之后发展出的全新卡种，卡内的集成电路中带有微处理器 CPU、存储单元（包括随机存储器 RAM、程序存储器 ROM（FLASH）、用户数据存储器 EEPROM）以及芯片操作系统 COS。装有 COS 的 CPU 卡相当于一台微型计算机，不仅具有数据存储功能，同时具有命令处理和数据安全保护等功能。一般内部运行一个 Java 虚拟机，可以写入简单的程序，接受与 MIFARE 不同的指令，安全性取决于本身程序的安全程度，更难以被破解。目前最常用的就是 JCOP 系列。</p><blockquote><p>Java Card OpenPlatform (JCOP) is a smart card operating system for the Java Card platform developed by IBM Zürich Research Laboratory. On 31 January 2006 the development and support responsibilities transferred to the IBM Smart Card Technology team in Böblingen, Germany. Since July 2007 support and development activities for the JCOP operating system on NXP / Philips silicon are serviced by NXP Semiconductors.</p><p>The title originates from the standards it complies with:</p><ul><li>Java Card specifications</li><li>GlobalPlatform (formerly known as Visa Inc OpenPlatform) specifications<br>A Java Card JCOP has a Java Card Virtual Machine (JCVM) which allows it to run applications written in Java programming language.</li></ul><p><small><em>摘自：<a href="https://en.wikipedia.org/wiki/Java_Card_OpenPlatform">zh.wikipedia.org/wiki/Java_Card_OpenPlatform</a></em></small></p></blockquote><p>手机 NFC 则一般基于这样的技术开发，CPU 卡本身与 MIFARE 不兼容，为了保证推广，许多厂商在制作时会利用 JCOP 本身的特性去模拟 M1 卡，国内手机中的模拟门禁卡就是这样的技术产生的。由于这种技术属于灰色地带，海外手机出厂一般不自带，国内前两年可以模拟的范围还比较广，现在则大大限制，只能模拟一般密钥为 FFFFFFFFFFFF 的普通卡。</p><p>手机 NFC 中的闪付、公交卡等也不是直接模拟实体卡，而是由发卡方下发密钥，为手机本身发放新卡，因此卡号等也与实体卡不同，公交卡也需要另交押金。由于这样的网络下发的卡属于异形卡，传统上公交卡公司并不愿意提供注销服务（谁愿意把收到手里的押金退掉呢？滑稽( ﹁ ﹁ ) ~→）。不过强如 Apple 倒是有资本和公交卡公司谈，于是 iPhone 用户就可以享受到可退卡的服务了。你问为啥华为小米这样的公司谈不下来？这可就是未解之谜了呢┑(￣Д ￣)┍，技术上是不存在障碍的，那么障碍在哪里呢？</p><h2 id="收尾">收尾</h2><p>好了，本篇关于 NFC 技术的科普就告一段落了。不得不说国内这方面的资料少的可怜，许多知识都是我从论坛或者大佬那里学来的，在这里也非常感谢 UCLA 的 BH4EXD 大佬在我研究这些技术时为我提供的帮助，希望这篇文章能帮助到对于射频技术感兴趣，却没有就读相关专业的你，少走一些弯路。</p><p>最后，引用一句罗老师的名言吧（绝对不是广告）：</p><blockquote><p>生命不息，折腾不止。————罗永浩<br><img src="/images/227387.gif" data-original="/images/posts/2019/09/zheteng.jpg" alt=""></p></blockquote><br><br><br><hr><p>参考资料：</p><ul><li><a href="https://www.freebuf.com/articles/wireless/8792.html">RFID 破解三两事 - FreeBuf 互联网安全新媒体平台</a></li><li><a href="https://zhuanlan.zhihu.com/p/67532665">详谈 Mifare Classic 1K 卡 - 知乎</a></li><li><a href="https://zh.wikipedia.org">维基百科</a></li></ul>]]>
    </content>
    <id>https://blog.gadfly.vip/2019/09/about-ic-cards/</id>
    <link href="https://blog.gadfly.vip/2019/09/about-ic-cards/"/>
    <published>2019-09-29T14:37:35.000Z</published>
    <summary>
      <![CDATA[<p><small><em>本文中提到的<code>卡片</code>，若非特别注明，均为非接触式卡片，不再另外声明。</em></small></p>
<h2 id="概述">概述</h2>
<p>首先分析 NFC 和我们身边的 IC 卡之前，我们需要先介绍一些基本的概念：</]]>
    </summary>
    <title>关于 NFC 和 IC 智能卡的一二三</title>
    <updated>2024-07-02T16:57:50.000Z</updated>
  </entry>
</feed>
