<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://blog.mtfh.cc/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
  <id>https://blog.mtfh.cc/</id>
  <title>MTFH</title>
  <subtitle>做自己喜欢的事儿～</subtitle>
  <rights>Copyright © 2025 - 2026 Happilys &amp;lt;a href=&amp;apos;https://creativecommons.org/licenses/by/4.0/deed.zh-hans&amp;apos; target=&amp;apos;_blank&amp;apos;&amp;gt;CC BY 4.0&amp;lt;/a&amp;gt; Licensed</rights>
  <updated>2026-05-10T08:18:07.850Z</updated>
  <generator>@vuepress/plugin-feed</generator>
  <link rel="self" href="https://blog.mtfh.cc/atom.xml"/>
  <link rel="alternate" href="https://blog.mtfh.cc/"/>
  <category term="前端"/>
  <category term="Vue"/>
  <category term="VBoxes UI"/>
  <entry>
    <title type="text">使用容器执行自动化任务</title>
    <id>https://blog.mtfh.cc/posts/%E4%BD%BF%E7%94%A8%E5%AE%B9%E5%99%A8%E6%89%A7%E8%A1%8C%E8%87%AA%E5%8A%A8%E5%8C%96%E4%BB%BB%E5%8A%A1.html</id>
    <link href="https://blog.mtfh.cc/posts/%E4%BD%BF%E7%94%A8%E5%AE%B9%E5%99%A8%E6%89%A7%E8%A1%8C%E8%87%AA%E5%8A%A8%E5%8C%96%E4%BB%BB%E5%8A%A1.html"/>
    <updated>2025-11-29T06:45:13.000Z</updated>
    <summary type="html"><![CDATA[
<p>九月份博客版本更新时，升级了 Vuepress 版本导致对 NodeJS 版本要求提高了，为此不得不去升级主机的 NodeJS。曾经提到过把博客的自动编译工作放在容器里执行，文档编译相关的依赖从主机转移到了容器。不同的 Node 版本，使用不同容器即可，不需要在主机中安装多个版本的 NodeJS 或被迫限制在某个固定的版本。</p>
<h2>注册并运行 <code>act_runner</code> 容器</h2>
<p><a href="/posts/Vuepress%20%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2%EF%BC%88%E5%9F%BA%E4%BA%8E%20Gitea%20Actions%EF%BC%89.html" target="_blank">这篇</a> 文章里提到过如何在主机环境中注册 <code>act_runner</code> 并使用 Gitea Actions 自动部署博客，现在需要将这些行为搬到 Docker 容器中完成。</p>]]></summary>
    <content type="html"><![CDATA[
<p>九月份博客版本更新时，升级了 Vuepress 版本导致对 NodeJS 版本要求提高了，为此不得不去升级主机的 NodeJS。曾经提到过把博客的自动编译工作放在容器里执行，文档编译相关的依赖从主机转移到了容器。不同的 Node 版本，使用不同容器即可，不需要在主机中安装多个版本的 NodeJS 或被迫限制在某个固定的版本。</p>
<h2>注册并运行 <code>act_runner</code> 容器</h2>
<p><a href="/posts/Vuepress%20%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2%EF%BC%88%E5%9F%BA%E4%BA%8E%20Gitea%20Actions%EF%BC%89.html" target="_blank">这篇</a> 文章里提到过如何在主机环境中注册 <code>act_runner</code> 并使用 Gitea Actions 自动部署博客，现在需要将这些行为搬到 Docker 容器中完成。</p>
<p>官方帮助文档中包含使用容器注册并运行 act_runner 的方式与方法。可以直接使用 docker 命令运行，也可以使用 Docker Compose。</p>
<p>直接使用 docker 运行：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> run</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -v</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> $(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">pwd</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/config.yaml:/config.yaml</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -v</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> $(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">pwd</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/data:/data</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -v</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /var/run/docker.sock:/var/run/docker.sock</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> CONFIG_FILE=/config.yaml</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> GITEA_INSTANCE_URL=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">instance_ur</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">l> </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> GITEA_RUNNER_REGISTRATION_TOKEN=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">registration_toke</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">n> </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> GITEA_RUNNER_NAME=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">runner_nam</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">e> </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> GITEA_RUNNER_LABELS=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">runner_label</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">s> </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    --name</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> my_runner</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    -d</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea/act_runner:nightly</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Docker Compose ：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"3.8"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  runner</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">gitea/act_runner:nightly</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      CONFIG_FILE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/config.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      GITEA_INSTANCE_URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${INSTANCE_URL}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      GITEA_RUNNER_REGISTRATION_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${REGISTRATION_TOKEN}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      GITEA_RUNNER_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${RUNNER_NAME}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      GITEA_RUNNER_LABELS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${RUNNER_LABELS}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./config.yaml:/config.yaml</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./data:/data</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/var/run/docker.sock:/var/run/docker.sock</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> compose</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> up</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>不同于在主机中直接运行 act_runner ，使用容器的方式会将注册与运行动作合并为一步。</p>
<h3>创建 <code>action-runner-helper</code> 仓库</h3>
<p>为了方便后续重复部署，把 Docker Compose 部署方式相关文件放到了 Git 仓库里。将文件版本化之后，方便将来审查与问题追溯。</p>
<h4>添加 config.yaml</h4>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># For more details on configuration options, refer to:</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#  https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  level</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">info</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">runner</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  file</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.runner</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  capacity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  env_file</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.env</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">3h</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  shutdown_timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">0s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  insecure</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  fetch_timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">5s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  fetch_interval</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">2s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  github_mirror</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">''</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  labels</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ubuntu-22.04:docker://ubuntu:22.04"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"node-jod:docker://node:jod-bookworm"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">cache</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  enabled</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  external_server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">container</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  network</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  privileged</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  options</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"--add-host=host.docker.internal:host-gateway"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workdir_parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  valid_volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: []</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  docker_host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  force_pull</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  force_rebuild</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  require_docker</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  docker_timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">0s</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workdir_parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>添加 docker-compose.yml</h4>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    act_runner</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">gitea/act_runner:nightly</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        restart</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">unless-stopped</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            CONFIG_FILE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/config.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            GITEA_INSTANCE_URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${INSTANCE_URL}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            GITEA_RUNNER_REGISTRATION_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${REGISTRATION_TOKEN}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            GITEA_RUNNER_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${RUNNER_NAME}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            GITEA_RUNNER_LABELS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${RUNNER_LABELS}"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./config.yaml:/config.yaml</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./data:/data</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/var/run/docker.sock:/var/run/docker.sock</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>添加 .env</h4>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>INSTANCE_URL=https://gitea.mtfh.cc</span></span>
<span class="line"><span>REGISTRATION_TOKEN=&#x3C;your_registration_token></span></span>
<span class="line"><span>RUNNER_NAME=gitea-runner</span></span>
<span class="line"><span>RUNNER_LABELS=linux-amd64:host</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>启动容器</h4>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span># 启动容器</span></span>
<span class="line"><span>#   修改了配置文件或容器参数等等</span></span>
<span class="line"><span>docker compose up -d</span></span>
<span class="line"><span></span></span>
<span class="line"><span># 结束容器</span></span>
<span class="line"><span>docker compose down</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，完整代码参考 <a href="https://gitea.mtfh.cc/mtfhx/action-runner-helper" target="_blank" rel="noopener noreferrer">action-runner-helper</a>。</p>
<blockquote>
<p>原本让 AI 帮忙写了个脚本，用来辅助用户录入容器注册时所需的参数信息，不过后来发现多余了。</p>
<p>Docker Compose 会默认从 .env 文件中读取变量值并赋给 YAML 中对应的变量，并不会读取 SHELL 临时变量。既然能直接从 .env 里获取参数，那直接改 .env 文件就好了，再整个脚本就有点多此一举了。</p>
</blockquote>
<h2>测试工作流</h2>
<p>添加环境测试工作流，检查工具版本问题。主要用于检测 Vuepress 编译时所需要的工具依赖，包括 NodeJS、Yarn 和 Git。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check Env</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workflow_dispatch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  check-env</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-jod</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Checkout repository</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">https://gitea.com/actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check Node.js version</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node -v</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check Git version</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">git --version</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check Yarn version</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          if ! command -v yarn >/dev/null 2>&#x26;1; then</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            echo "Yarn not found, installing..."</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            npm install -g yarn</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          fi</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          yarn -v</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这时候如果有缺少依赖或版本不匹配问题，可以提前发现。比如说需要激活新版本的 Yarn。</p>
<p>分别执行文档编译工作流与子模块合并工作流</p>
<h2>更新工作流</h2>
<p>添加手动触发工作流的方式；工作流的执行标签使用 <code>node-jod</code> —— 基于 Debian 的 node-22.04 容器；编译文档前激活新版本的 Yarn。</p>
<p>工作流修改后大致内容如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Build Docs</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }}</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    branches</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">main</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workflow_dispatch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  Build-Docs-and-Deploy-It</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-jod</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check out code</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">https://gitea.com/actions/checkout@v4</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          fetch-depth</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.DEPLOYKEY }}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-known-hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{secrets.SSH_KNOWN_HOSTS}}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Setup and build docs</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  # 两次 Build 是为了生成文章分类信息</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          corepack enable</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          yarn install</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          yarn docs:build</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          yarn docs:build</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>自动更新子模块方面，相对于之前版本单独初始化子模块，这里在检出仓库时连带子仓库一并初始化了——能借助 <code>actions/checkout@v4</code> 自动设置认证信息。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Sync Submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workflow_dispatch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  update-submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-jod</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Checkout repo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">https://gitea.com/actions/checkout@v4</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.DEPLOYKEY }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-known-hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.SSH_KNOWN_HOSTS }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">recursive</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Update submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> # 拉取子模块主分支后，再合并  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git -C src/posts fetch origin main  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git -C src/posts checkout main  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Commit update</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span><span style="--shiki-light:white;--shiki-dark:#FFFFFF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git config user.name "${{ vars.USER_NAME }}"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git config user.email "${{ vars.USER_EMAIL }}"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git add .  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          if ! git diff --cached --quiet; then  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            git commit -m "$(git -C src/posts log -1 --pretty=format:"%s")"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            git push origin HEAD  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          else  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            echo "No submodule updates"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          fi</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>容器内的文档（博客）部署问题</h2>
<p>如果不添加文件映射的话，容器内的文件是没法直接拷贝到宿主机的。解决这一问题的方式有很多。可以添加目录映射（逻辑卷）；上传文档，再使用上传的文档创建一个 Web 容器，然后让 Nginx 作反向代理；还可以使用 SCP 发送文件到宿主机。</p>
<blockquote>
<p>当前博客的部署方式是结合 Nginx 一起考虑的。博客项目包含文章的编辑与发布，博客本身的改版，最后统一输出的是一组静态文件——即项目目录 <code>./src/.vuepress/dist</code> 里的内容。</p>
<p>至此「软件」的开发部分完成了，下面讨论部署与分发。因为「博客软件」是网页应用的形式呈现的，部署成功也就完成了应用的分发。</p>
<p>Nginx 把网站发布出去的步骤包括但不限于 Nginx 配置、域名配置和防火墙配置等。</p>
</blockquote>
<h3>使用 SCP 部署文档</h3>
<p>其实 Github 有现成的 Actions 插件，不过由于某些因素下载不了或者特别慢。就只能让 AI 帮我生成一段配置了。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Build Docs</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }}</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    branches</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">main</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workflow_dispatch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  Build-Docs-and-Deploy-It</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-jod</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	  # 省略的步骤</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Install sshpass (with CN mirror)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          apt-get update &#x26;&#x26; apt-get install -y sshpass</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Deploy via scp (password)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          ssh-keyscan -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          sshpass -p "${{ secrets.PASSWORD }}" scp \</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            -o StrictHostKeyChecking=no \</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            -r ./src/.vuepress/dist/* \</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/var/www/html</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>由于 SCP 拷贝文件需要远程主机的地址，所以在运行 act_runner 容器时，配置文件里需要有以下配置，来保证执行工作流的容器能够访问宿主机，而不是 act_runner 容器。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">container</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">	options</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"--add-host=host.docker.internal:host-gateway"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>另外在 Gitea 仓库配置中添加上相应的变量，<code>secrets.HOST</code> 变量值使用 <code>host.docker.internal</code> 即可。</p>
<h2>参考</h2>
<ol>
<li><a href="https://docs.gitea.com/zh-cn/usage/actions/act-runner" target="_blank" rel="noopener noreferrer">Act Runner | Gitea Documentation</a></li>
<li><a href="https://mirrors.ustc.edu.cn/help/debian.html#__tabbed_3_2" target="_blank" rel="noopener noreferrer">Debian - USTC Mirror Help</a></li>
<li><a href="https://github.com/nektos/act/blob/master/IMAGES.md" target="_blank" rel="noopener noreferrer">act/IMAGES.md at master · nektos/act</a></li>
</ol>
]]></content>
    <published>2025-11-29T03:45:36.000Z</published>
  </entry>
  <entry>
    <title type="text">Gitea 自动同步子仓库</title>
    <id>https://blog.mtfh.cc/posts/Gitea%20%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E5%AD%90%E4%BB%93%E5%BA%93.html</id>
    <link href="https://blog.mtfh.cc/posts/Gitea%20%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E5%AD%90%E4%BB%93%E5%BA%93.html"/>
    <updated>2026-01-31T09:20:36.000Z</updated>
    <summary type="html"><![CDATA[
<p>博客代码仓库与文章仓库是两个独立的版本仓库，博客仓库通过子模块的方式来引用文章仓库。把博客的代码（Vuepress）和文章放一起，会更简单——不用折腾 <a href="/posts/%E5%89%8D%E7%AB%AF/Vue/Vupress%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%AE%E9%A2%98.html" target="_blank"><code>@vuepress/plugin-git</code> 问题</a>，也不需要推送子仓库之后再去更新主仓库。</p>
<p>但是个人习惯上，还是倾向于将应用与数据分离开来。更新文章即关注博文的编辑与发布；而功能或 UI 上的改进即为博客版本的迭代。二者之间不具有强关联性——如果想换一个博客框架的话，只要保证新框架具有相似的功能集（Markdown 文件的静态编译功能）就好了，没有的话也可以自己开发；可能的话，网友替换掉子模块后，便能得到一个文章内容不同克隆体。</p>]]></summary>
    <content type="html"><![CDATA[
<p>博客代码仓库与文章仓库是两个独立的版本仓库，博客仓库通过子模块的方式来引用文章仓库。把博客的代码（Vuepress）和文章放一起，会更简单——不用折腾 <a href="/posts/%E5%89%8D%E7%AB%AF/Vue/Vupress%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%AE%E9%A2%98.html" target="_blank"><code>@vuepress/plugin-git</code> 问题</a>，也不需要推送子仓库之后再去更新主仓库。</p>
<p>但是个人习惯上，还是倾向于将应用与数据分离开来。更新文章即关注博文的编辑与发布；而功能或 UI 上的改进即为博客版本的迭代。二者之间不具有强关联性——如果想换一个博客框架的话，只要保证新框架具有相似的功能集（Markdown 文件的静态编译功能）就好了，没有的话也可以自己开发；可能的话，网友替换掉子模块后，便能得到一个文章内容不同克隆体。</p>
<h2>子仓库的自动同步配置</h2>
<p>子仓库的自动同步功能是由 Webhook 结合 Gitea Actions 协作来完成的。子模块推送更新后，触发子仓库 Webhook ，主仓收到 Webhook 请求后执行更新子仓库的工作流——模块的更新、合并和提交等操作，完成主仓库的同步更新。</p>
<p>Gitea 默认具有 Webhook 支持，除了和自身集成，还支持飞书、钉钉和企业微信等应用。要想通过 Webhoook 启动工作流，需要 <code>/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches</code> 这个接口的支持。</p>
<h3>添加自动更新子模块的工作流（Gitea Actions）</h3>
<p>博客仓库（<code>vuepress-starter</code>）切换到 <code>dev</code> 分支，在项目录下的 <code>.gitea</code> 目录里添加  <code>update-submodules.yml</code> 文件， 内容如下：</p>
<blockquote>
<p>默认主分支（<code>main</code>）写保护，主分支不直接用来做提交和推送。最终的提交经由其他分支开发并测试好之后，再合并或者变基过去。</p>
</blockquote>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Sync Submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  workflow_dispatch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  update-submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">nas</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Checkout main repo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">https://gitea.com/actions/checkout@v4</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.DEPLOYKEY }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-known-hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.SSH_KNOWN_HOSTS }}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Update submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span><span style="--shiki-light:white;--shiki-dark:#FFFFFF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git submodule update --init --remote --merge  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Commit updated submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">|</span><span style="--shiki-light:white;--shiki-dark:#FFFFFF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git config user.name "${{ USER_NAME }}"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git config user.email "${{ USER_EMAIL }}"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          git add .  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          if ! git diff --cached --quiet; then  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            git commit -m "$(git -C src/posts log -1 --pretty=format:"%s")"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            git push origin HEAD:dev  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          else  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            echo "No submodule updates"  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          fi</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>工作流功能便是合并子模块的最新提交，提交信息使用的是子模块最新的提交信息。</p>
<p>重点关注工作流的触发方式，这里使用的是 <code>workflow_dispatch</code> 。该方式可以在仓库网页里的工作流标签中手动触发，也可以使用接口来触发，Webhook 采用的便是后者。</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-c4d827f208a3af55b30b5d77e2af25e5.png" alt="03-c4d827f208a3af55b30b5d77e2af25e5" tabindex="0" loading="lazy"><figcaption>03-c4d827f208a3af55b30b5d77e2af25e5</figcaption></figure>
<p>最后添加 <code>update-submodules.yml</code> 文件到 Git 仓库，提交并推送到 Gitea。</p>
<h3>准备 Gitea 接口密钥和部署密钥</h3>
<p>接口密钥用于 Webhook 发送工作流触发请求；部署密钥用于授权工作流对博客和文章仓库的拉取和推送操作。</p>
<h4>创建接口密钥</h4>
<p>依次选择：设置 =&gt; 应用 =&gt; 管理 Access Token 。然后，生成一个 Gitea API 访问令牌。</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-1fa3dd61bd14b955b457f714c62249e9.png" alt="03-1fa3dd61bd14b955b457f714c62249e9" tabindex="0" loading="lazy"><figcaption>03-1fa3dd61bd14b955b457f714c62249e9</figcaption></figure>
<p>生成的密钥字段保存好，后面配置 Webhook 时会用到。</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-c7f67fc9e7fbe22d35bf442535dc3937.png" alt="03-c7f67fc9e7fbe22d35bf442535dc3937" tabindex="0" loading="lazy"><figcaption>03-c7f67fc9e7fbe22d35bf442535dc3937</figcaption></figure>
<h4>配置部署密钥</h4>
<p>使用 <code>ssh-keygen</code> 创建密钥</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># -f 后面是文件名称，如果没有该参数，将默认生成到 $HOME/.ssh</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   添加这个是为了更改默认生成的位置</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># -C 为备注内容，会被添加到公钥末尾</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ssh-keygen</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -t</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ed25519</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -C</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "deploy@mtfh.cc"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -f</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> id_ed25519</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/10/03-4ee81b0e844b60abdf7fb350ce03411c.png" alt="03-4ee81b0e844b60abdf7fb350ce03411c" tabindex="0" loading="lazy"><figcaption>03-4ee81b0e844b60abdf7fb350ce03411c</figcaption></figure>
<p>为 Gitea Actions 添加密钥和为仓库添加部署密钥参考<a href="/posts/Vuepress%20%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2%EF%BC%88%E5%9F%BA%E4%BA%8E%20Gitea%20Actions%EF%BC%89.html#%E5%88%9B%E5%BB%BA-gitea-actions" target="_blank">创建 Gitea Actions ｜Vuepress 自动化部署（基于 Gitea Actions）</a></p>
<h3>配置「文章」仓库的 Webhook</h3>
<p>前往仓库设置 =&gt; Web 钩子 =&gt; 添加 Web 钩子 =&gt; Gitea</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-d09adf079bc850a5ab5294baf64e7ce6.png" alt="03-d09adf079bc850a5ab5294baf64e7ce6" tabindex="0" loading="lazy"><figcaption>03-d09adf079bc850a5ab5294baf64e7ce6</figcaption></figure>
<p>填写接口地址，设置接口访问密钥等一系列参数设置</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-a9f244825455de1d65dc6fdf80db04b1.png" alt="03-a9f244825455de1d65dc6fdf80db04b1" tabindex="0" loading="lazy"><figcaption>03-a9f244825455de1d65dc6fdf80db04b1</figcaption></figure>
<p>模拟发送请求，应该发送失败——前面添加的工作流并不在 <code>main</code> 分支中。</p>
<h2>功能测试与验证</h2>
<p>默认的 Webhook 推送只能触发主分支的工作流，但测试只能使用 dev 分支或其他。这样一来在正式发布前，就无法进行基础的流程测试了。</p>
<p>一个比较笨的方法就是本地镜像一个 Gitea 服务，使用镜像的主分支测试。因为曾经做过 Gitea 备份，想要本地镜像一个很简单，甚至之前做测试时有一个现成的。</p>
<p>另一种方式是，单步验证——分别验证 Action 触发情况和 Webhook 的发送情况。</p>
<blockquote>
<p>单步验证的方式，可以规避人为的粗心大意导致的问题。比如 <code>update-submodules.yml</code> 便是在多次执行验证之后得到的结果。</p>
</blockquote>
<h3>推送文章更新， 验证 Webhook 触发情况</h3>
<p>配置好 Webhook 后，除了测试推送外，随意修改点内容推送到 Gitea 就可以看到，Webhook 发送记录。</p>
<h3>模拟 Webhook 请求，验证 Actions 执行情况</h3>
<p>由于默认的 Webhook 只能触发主分支工作流，因此想要验证工作流正确与否，就需要手动构造并发送 Webhook 请求。</p>
<p>curl 命令发送请求示例：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">curl</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -X</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> POST</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">	 -H</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Content-Type: application/json"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">	 -H</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Authorization: token &#x3C;your api token>"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">     https://</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">your</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> domai</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">n></span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/api/v1/repos/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">usernam</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">e></span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">rep</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">o></span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/actions/workflows/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">workflow</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> i</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">d></span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/dispatches</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">     -d</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '{"ref": "dev"}'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>除了使用 <code>curl</code> 命令外，还可以使用 Postman 、Apiforx 等图形化工具。Apifox 以项目的角度管理接口，除了设计接口，还可以通过多种方式导入接口。将 Gitea 的接口导入进来，测试和查找接口就比较方便了。</p>
<figure><img src="https://images.mtfh.cc/2025/10/03-3f3a45d1117d8d8dc872b1867ba002ee.png" alt="03-3f3a45d1117d8d8dc872b1867ba002ee" tabindex="0" loading="lazy"><figcaption>03-3f3a45d1117d8d8dc872b1867ba002ee</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/10/03-a5004f8bd8b9d8e7ce88f373413630c9.png" alt="03-a5004f8bd8b9d8e7ce88f373413630c9" tabindex="0" loading="lazy"><figcaption>03-a5004f8bd8b9d8e7ce88f373413630c9</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/10/03-894c2fcaaf3076f61d61b60373b00d65.png" alt="03-894c2fcaaf3076f61d61b60373b00d65" tabindex="0" loading="lazy"><figcaption>03-894c2fcaaf3076f61d61b60373b00d65</figcaption></figure>
<h4>获取子模块最新提交信息的方法研究</h4>
<p>观察前面的工作流文件，可以看到，我是通过以下命令获取子仓库的最新提交信息的：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -C</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> src/posts</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> log</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -1</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --pretty=format:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"%s"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>最初的思路并不打算使用命令的方式来获取子仓库的提交信息，因为 Webhook 的推送数据中包含相关信息，请求体样本如下：</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "ref"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"refs/heads/dev"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "before"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"c07cc3900253bb43a682cc4ed354c225ed472867"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "after"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "compare_url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"https://gitea.mtfh.cc/mtfhx/blog-posts/compare/c07cc3900253bb43a682cc4ed354c225ed472867...2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "commits"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "message"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Posts: 2025-09-21 11:11:48</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"https://gitea.mtfh.cc/mtfhx/blog-posts/commit/2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "author"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Happilys"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "email"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"happilys_mtfh@sina.com"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "username"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"mtfhx"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "committer"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Happilys"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "email"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"happilys_mtfh@sina.com"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "username"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"mtfhx"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "verification"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "timestamp"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2025-09-21T11:11:48+08:00"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "added"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "removed"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "modified"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        ".obsidian/core-plugins.json"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        ".obsidian/workspace.json"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "total_commits"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "head_commit"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "message"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Posts: 2025-09-21 11:11:48</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"https://gitea.mtfh.cc/mtfhx/blog-posts/commit/2518d11a6ef532597e4490aec36f1c3570d0aa7d"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "author"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Happilys"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "email"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"happilys_mtfh@sina.com"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "username"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"mtfhx"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "committer"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Happilys"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "email"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"happilys_mtfh@sina.com"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "username"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"mtfhx"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "verification"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "timestamp"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2025-09-21T11:11:48+08:00"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "added"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "removed"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "modified"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      ".obsidian/core-plugins.json"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      ".obsidian/workspace.json"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 以下内容省略</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>经过一番尝试后——使用 <code>toJSON(github.event)</code> 打印出 <code>github.events</code> 对象，发现没有提交信息相关内容，也就宣告这种方式在当前的 Gitea 版本（<code>1.24.5</code>）中不适用。</p>
<p>（<s>问 AI</s>）调研一番后，才决定使用命令来获取提交信息。</p>
<blockquote>
<p>尝试后，给我一种与下面例子类似的感觉。</p>
<p>卖方已经将货物的重量和数量报给对方海关。货物运输抵达后，咨询我方海关货物重量和数量信息，被告知没有相关数据。取货后，只好自行为货物称重和计数。</p>
</blockquote>
<h2>版本上线</h2>
<p>九月底实际上线时，出现了些小问题。更新了 Vuepress 版本之后，NodeJS 的最低版本需要 20 以上。当初作自动化部署时使用的版本是 18 ，部署方式使用的官方预编译的二进制安装的。</p>
<p>使用 Linux 预编译的二进制安装方式如下</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">wget</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://nodejs.org/dist/v22.20.0/node-v22.20.0-linux-x64.tar.xz</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">tar</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -xvzf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> node-v22.20.0-linux-x64.tar.xz</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x26;&#x26; </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> node-v22.20.0-linux-x64</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -m</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 755</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> bin/</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">*</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /usr/local/bin/</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cp</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> include/</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">*</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /usr/local/include/</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cp</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> lib/</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">*</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /usr/local/lib/</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cp</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> share/</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">*</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /usr/local/share/</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>后面有必要把文档编译过程扔到容器里完成。</p>
<p>因为博客仓库的主分支设置了分支保护，白名单设置里，只允许我自己向主分支推送。要么取消白名单保护，要么将部署密钥添加到用户级（尚未验证）。</p>
<p>懒得再去重新添加密钥，就取消了白名单保护——只要密钥拥有写权限，就可以推送提交。</p>
<h2>自动同步子仓库的失败尝试</h2>
<p>Gitea 1.22.2 版本不支持通过接口来触发工作流，但是可以通过评论工单的方式来变相触发工作流 。</p>
<p>基于评论工单的方式触发工作流，工作流文件需要放在 <code>.github</code> 目录下，触发方式为 <code>issue_comments</code> ，并且 <code>.gitea</code> 和 <code>.github</code> 只能存在一个。</p>
<p>为了自定义工单评论请求的请求体，就需要手动构造请求，让 Gitea Actions 执行 <code>curl</code> 命令来发送这份请求。另外，只要有文章更新，这一流程会被反复触发。工单系统会因为工作流的反复触发而导致数据污染，当然如果不介意的话倒也没问题。</p>
<p>实际考量，如果想获得子模块的自动同步的话，更新 Gitea 是最根本方式；如果项目急需上线的话，便只能如此了。但，后续还是应该升级 Gitea ，并改为 Webhook 的方式。</p>
<blockquote>
<p>系统维护除了解决一个个具体问题外，还需要疏通各个通路和问题解决后的收尾工作。</p>
<p>否则在面对紧急问题时哪头都堵，不得已作出各种让步；随之而来的便是系统维护代价越来越高，以至于走向崩溃。</p>
</blockquote>
<h2>待续</h2>
<p>该功能作为博客文章一键发布功能之一，然而本地写作流程还没有具体方案。做这些的主要目标便是搭建一个契合自己的内容输出平台，也是学习闭环中的关键流程。</p>
<p>目前的创作流程还比较粗糙，学习、实践和记录三者同时进行，结束后（成功抑或失败）再编写文章总结。与此同时还需要去对关键步骤进行还原，为文章提供素材。文章草稿完成后，再校验和发布。整个流程走下来还是比较漫长的，除了文章的编辑过程，还有发布流程的改进。</p>
<p>该功能上线之后，只需推送 「文章」仓库的提交便搞定了，不需要手动地合并子仓库的提交了。</p>
<p>而文章的创作流程有待进一步改进……</p>
<blockquote>
<p>先在笔记中罗列大纲，编辑为草稿；移到「文章」仓库，补充素材（图片、流程图）、链接和参考文献等，创建提交并推送到 Gitea，然后去主仓库合并更新并推送提交。</p>
</blockquote>
<h2>参考</h2>
<ol>
<li><a href="https://chatgpt.com/share/68d0afe5-bd9c-800c-9a65-9e305b2cc978" target="_blank" rel="noopener noreferrer">Git子模块合并错误-ChatGPT</a></li>
<li><a href="https://docs.gitea.com/zh-cn/development/api-usage" target="_blank" rel="noopener noreferrer">API 使用指南 | Gitea Documentation</a></li>
<li><a href="https://docs.gitea.com/1.20/usage/webhooks" target="_blank" rel="noopener noreferrer">Webhooks | Gitea Documentation</a></li>
<li><a href="https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onworkflow_dispatch" target="_blank" rel="noopener noreferrer">Workflow syntax for GitHub Actions - GitHub Docs</a></li>
<li><a href="https://docs.github.com/zh/actions/reference/workflows-and-actions/variables" target="_blank" rel="noopener noreferrer">变量参考 - GitHub 文档</a></li>
<li><a href="https://github.com/go-gitea/gitea/issues/23668" target="_blank" rel="noopener noreferrer">Actions - Manually trigger a workflow/action · Issue #23668 · go-gitea/gitea</a></li>
<li><a href="https://github.com/go-gitea/gitea/pull/28163" target="_blank" rel="noopener noreferrer">Actions support workflow dispatch event by pangliang · Pull Request #28163 · go-gitea/gitea</a></li>
<li><a href="https://docs.apifox.com/import-openapi-swagger" target="_blank" rel="noopener noreferrer">导入 OpenAPI/Swagger - Apifox 帮助文档</a></li>
<li><a href="https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent" target="_blank" rel="noopener noreferrer">生成新的 SSH 密钥并将其添加到 ssh-agent - GitHub 文档</a></li>
</ol>
]]></content>
    <published>2025-10-03T08:34:51.000Z</published>
  </entry>
  <entry>
    <title type="text">Gitea 备份</title>
    <id>https://blog.mtfh.cc/posts/Gitea%20%E5%A4%87%E4%BB%BD.html</id>
    <link href="https://blog.mtfh.cc/posts/Gitea%20%E5%A4%87%E4%BB%BD.html"/>
    <updated>2026-03-01T09:27:51.000Z</updated>
    <summary type="html"><![CDATA[
<p>当前网站的文章、博客代码和其他的项目代码都由自己在云端部署的 Gitea 服务管理。如果某一天阿里云「跪」了，那么我所有数据也就全体「升天」。因此 Gitea 服务的备份工作显得格外重要。</p>
<p>Github 很多时候访问不了，有时候想要拉取一些代码，就特别麻烦。需要安装 Git 都还好说，但代码下载不下来就难受了。Gitee 和 GitCode 需要迎合市场需求，掺杂了不需要的东西也不好用（做代理不错）。</p>
<p>所以就目前来看，比较好的方案还是私有化部署 Gitea。GitLab 太大，云服务器配置低容不下，况且它其中的功能对于个人以及小型企业来说非必须的。</p>
]]></summary>
    <content type="html"><![CDATA[
<p>当前网站的文章、博客代码和其他的项目代码都由自己在云端部署的 Gitea 服务管理。如果某一天阿里云「跪」了，那么我所有数据也就全体「升天」。因此 Gitea 服务的备份工作显得格外重要。</p>
<p>Github 很多时候访问不了，有时候想要拉取一些代码，就特别麻烦。需要安装 Git 都还好说，但代码下载不下来就难受了。Gitee 和 GitCode 需要迎合市场需求，掺杂了不需要的东西也不好用（做代理不错）。</p>
<p>所以就目前来看，比较好的方案还是私有化部署 Gitea。GitLab 太大，云服务器配置低容不下，况且它其中的功能对于个人以及小型企业来说非必须的。</p>
<h2>Gitea 维护模式</h2>
<p>Gitea 本身没有维护模式这一说法。 Gitea 服务是由 Nginx 反向代理出去的，除了提供 SSL 支持外，还需要代理博客网页的请求，二者通过域名来区分。为了保证 Gitea 的数据库与本地数据的完整性，因此备份时需要停止服务。而这样 Nginx 接收到来自客户的 Gitea 服务请求时就直接 502 了，这是不希望看到的。</p>
<figure><img src="https://images.mtfh.cc/2025/09/01-f197a3174051656e0d612ce45d5b0584.png" alt="Screenshot 2025-09-01 at 17.12.22" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-09-01 at 17.12.22</figcaption></figure>
<h3>维护页的创建</h3>
<p>维护页最简单的就是一个 HTML 网页了。AI 技术的发展太快了，这种网页甚至不需要自己去写。</p>
<p>使用 <code>create-vue</code> 脚手架，创建好 <code>maintenance-html</code> 工程。然后用 Trae 打开工程，输入提示词，网页就做好了。</p>
<blockquote>
<p>其实工程都不需要自己创建，但是为了保证工程的一致性，就手动创建了。由 Trae 创建的话，还需要提示词来约束。</p>
</blockquote>
<figure><img src="https://images.mtfh.cc/2025/09/01-3ede748eba6be5633676e0d05b535738.png" alt="Screenshot 2025-09-01 at 17.14.53" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-09-01 at 17.14.53</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/09/01-cd6ba67ee408ab80b57efb4bf6db5236.png" alt="Screenshot 2025-09-01 at 17.17.58" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-09-01 at 17.17.58</figcaption></figure>
<h3>维护页的激活与停止</h3>
<p>第一种方式，在 Gitea 服务启/停前后，运行一个同端口的 HTTP 服务，用来响应来自 Nginx 的代理请求。这样不需对 Nginx 做任何操作，缺点在于二者转换存在一定的延迟，可能会出现 502 。</p>
<p>第二种方式，通过切换 Nginx 配置文件来解决的。这样能够无缝切换到维护模式，然后再从维护模式切换回来。但是执行备份脚本的角色就需要提供 Nginx 控制权限和文件修改权限。另外，后续 Gitea 重新部署的话也同样需要这样一份文件。</p>
<p>第三种方式，当上游代理（Gitea 服务）不可达时，由 Nginx 响应维护页。维护页需要以单个 HTML 文件的形式存在，多文件配置起来比较麻烦（未测试成功）。部署 Gitea 的时候也需要针对维护页作配置。</p>
<blockquote>
<p>如果仅仅只是为了达成目的，就不需要考虑这么多了。更多的还是希望规划好每个步骤，并对每一步制定相应的约束。这样下来整体协调统一，机器自动执行时保证不会出问题才是关键。</p>
</blockquote>
<h2>数据备份</h2>
<p>根据官方文档介绍，<code>gitea</code> （二进制）拥有数据备份功能。不过仅支持备份，不能恢复。</p>
<h3>Gitea Dump 备份数据</h3>
<p>添加一份维护页的代理配置 <code>gitea-mainten.conf</code>，将 <code>gitea.conf</code> 改名为 <code>gitea.conf.disable</code>。</p>
<div class="language-nginx line-numbers-mode" data-highlighter="shiki" data-ext="nginx" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-nginx"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      80</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> gitea.mtfh.cc;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 301</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> https://$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">server_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">request_uri</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">443</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ssl;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">gitea.mtfh.cc;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_certificate </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/etc/nginx/certificates/gitea.mtfh.cc.pem;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_certificate_key </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/etc/nginx/certificates/gitea.mtfh.cc.key;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_session_timeout </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 5m</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_ciphers </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_protocols </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">TLSv1 TLSv1.1 TLSv1.2;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_prefer_server_ciphers </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> / {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">			root </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/var/www/maintenance</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>重新加载 Nginx ：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> reload</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nginx.service</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>停止 Gitea 服务：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> stop</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea.service</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>最后使用命令 gitea 命令备份数据：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">./gitea</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dump</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> app.ini</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/09/01-bc299eaa56cfed689d3301a7b8faa59d.png" alt="gitea-dump-output" tabindex="0" loading="lazy"><figcaption>gitea-dump-output</figcaption></figure>
<h3>脚本备份数据</h3>
<p>根据 AI 提供的备份脚本，简单修改后，大致内容如下：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#!/bin/bash</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># gitea-backup.sh</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Gitea 备份/恢复全流程自动化脚本（7z 压缩 + Nginx 维护页 + sudo + 自动清理旧备份）</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">set</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -euo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pipefail</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 省略的配置内容</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ================= 工具函数 =================</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">run_sudo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [[ </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$SUDO_PASS</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ]]; </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">then</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">        echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$SUDO_PASS</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> | </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -S</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$@</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    else</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$@</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    fi</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">switch_to_maintenance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 开启维护页"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> mv</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.disabled"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> cp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_MAINT_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nginx</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -s</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> reload</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">switch_to_normal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 恢复正常代理"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> mv</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_MAINT_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> mv</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.disabled"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_CONF_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NGINX_NORMAL_CONF</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nginx</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -s</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> reload</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stop_gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {  </span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 停止 Gitea 服务"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> stop</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">start_gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {  </span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 启动 Gitea 服务"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    run_sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 省略掉部分辅助函数</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ================= 主流程 =================</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [[ </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">$#</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -lt</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ]]; </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">then</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Usage: </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$0</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> backup|restore [backup_file.7z]"</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    exit</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">fi</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ACTION</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [[ </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$ACTION</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> ==</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "backup"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ]]; </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">then</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    DATE</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">$(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">date</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> +'%Y-%m-%d_%H-%M-%S'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    TMP_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/tmp-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    mkdir</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    switch_to_maintenance</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    stop_gitea</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份 Gitea 数据目录"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    rsync</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --delete</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$GITEA_DATA</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/data/"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    backup_service</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份数据库"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    backup_db</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    start_gitea</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    switch_to_normal</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 压缩为 7z"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    7z</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -mx=9</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    rm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -rf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    cleanup_old_backups</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份完成: </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">elif</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [[ </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$ACTION</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> ==</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "restore"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ]]; </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">then</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	# 节省篇幅，省略掉恢复步骤</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	# 后来也没用上</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "未知操作: </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$ACTION</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    exit</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">fi</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完整内容看<a href="https://gitea.mtfh.cc/mtfhx/maintenance-html/src/commit/43531e021054b83be764c9c639537f71a0fdc284/tools/gitea-backup-7z.sh" target="_blank" rel="noopener noreferrer">这里</a>。经测试，执行后拿到的备份数据，能够成功还原数据。</p>
<p>脚本虽然能成功执行，但其中有大量的 <code>sudo</code> 影子。不止是需要管控 Gitea 服务，还需要控制 Nginx 服务，同时还会修改 Nginx 配置文件。一个简单的备份脚本，哪能允许干这么多事儿！</p>
<p>git 用户本质是为 Git 提供 SSH 协议的，根本就不允许有 sudo 权限。可现在倒好，<code>sudo</code> 有了，登录密码有了，攻击者拿到密钥和密码，可以为所欲为了！</p>
<h2>镜像 Gitea 服务</h2>
<blockquote>
<p>线上服务是正常，所以此处采取镜像的方式进行数据恢复测试。</p>
</blockquote>
<p>将备份后的数据拷贝到另一台设备，部署 Gitea 服务，然后恢复数据。不管是 Gitea Dump 备份，还是脚本备份，恢复流程一致。</p>
<h3>数据库准备</h3>
<p>安装数据库，原来用什么数据库，还原的时候一样。</p>
<blockquote>
<p>线上用的 MySql，镜像的时候装的 Mariadb。后面假设线上用的 Mariadb</p>
</blockquote>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>sudo dnf install -y mariadb-server mariadb</span></span>
<span class="line"><span></span></span>
<span class="line"><span># 执行数据库初始化</span></span>
<span class="line"><span>mysql_secure_installation</span></span>
<span class="line"><span></span></span>
<span class="line"><span># 连接数据库</span></span>
<span class="line"><span>mysql -uroot -p</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建数据库和关联用户</p>
<div class="language-sql line-numbers-mode" data-highlighter="shiki" data-ext="sql" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-sql"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">CREATE</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> DATABASE</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> gitea</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> CHARACTER</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> SET</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'utf8mb4'</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> COLLATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'utf8mb4_bin'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">CREATE</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> '</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">'@</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'%'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> IDENTIFIED </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">BY</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'password'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">GRANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ALL PRIVILEGES </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">ON</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> gitea.* </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">TO</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'gitea'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'%'</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> WITH</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> GRANT</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> OPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">FLUSH PRIVILEGES;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>初次安装的历史比较久远，忘记创建数据库是需要对数据库字符集和排序规则进行设定，导致数据恢复之后，安全检查老提示字符集问题。</p>
<figure><img src="https://images.mtfh.cc/2025/09/01-c97122972e4644df8a33ddff5025f42b.png" alt="Screenshot 2025-09-01 at 19.03.57" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-09-01 at 19.03.57</figcaption></figure>
</blockquote>
<h3>gitea 安装</h3>
<p>创建 git 用户，用于执行 gitea 程序和存放运行数据，同时也便于支持 SSH 协议。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 创建 git 用户，并分配家目录</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">useradd</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -m</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 创建 Gitea 数据目录</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">su</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> -</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mkdir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>获取线上同版本 <code>gitea</code> 程序，放到家目录或 <code>/usr/local/bin</code> 。由于脚本打包，将可执行文件一同打包了进去，所以我就不需要单独下载了。</p>
<blockquote>
<p>systemd 配置，需要根据可执行文件的位置作出相应的变化。</p>
</blockquote>
<p>Gitea 的 systemd 配置示例如下：</p>
<div class="language-systemd line-numbers-mode" data-highlighter="shiki" data-ext="systemd" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-systemd"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[Unit]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Description</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Gitea (Git with a cup of tea)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">After</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">syslog.target</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">After</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">network.target</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[Service]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">RestartSec</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Type</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">simple</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">User</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">git</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Group</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">git</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">WorkingDirectory</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/home/git/gitea</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ExecStart</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/home/git/gitea/gitea web --config /home/git/gitea/app.ini</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Restart</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">always</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Environment</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">USER</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">git </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">HOME</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/home/git </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">GITEA_WORK_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/home/git/gitea</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[Install]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">WantedBy</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">multi-user.target</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Nginx 代理配置如下：</p>
<div class="language-nginx line-numbers-mode" data-highlighter="shiki" data-ext="nginx" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-nginx"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      80</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> gitea.home.local;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> / {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_pass </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">http://127.0.0.1:3000;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Host $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Real-IP $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Forwarded-For $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Forwarded-Proto $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">scheme</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>本地测试环境，省事的话可以不走代理。用代理的话主要为了 HTTPS 代理和虚拟主机功能。</p>
<h3>还原数据</h3>
<p>解压备份数据：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Gitea Dump 备份的数据</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">unzip</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea-dump-1756097579.zip</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 脚本备份的数据</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#7za x gitea-backup-2025-08-28_10-30-01.7z</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">7z</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> x</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea-backup-2025-08-28_10-30-01.7z</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>使用解压出的 SQL 文件，恢复数据库：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Gitea Dump 备份的数据</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mysql</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --default-character-set=utf8mb4</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -ugitea</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x3C; </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">gitea-db.sql</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 脚本备份的数据</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sed</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -i</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's/utf8mb4_0900_as_cs/utf8mb4_bin/g'</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> db.sql</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mysql</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --default-character-set=utf8mb4</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -ugitea</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x3C; </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">db.sql</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>恢复数据时需要重点关注的配置：</p>
<div class="language-ini line-numbers-mode" data-highlighter="shiki" data-ext="ini" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ini"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">WORK_PATH</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> /home/git/gitea</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[repository]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">ROOT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> gitea-repositories</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[server]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">SSH_DOMAIN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> gitea.home.local</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">DOMAIN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> gitea.home.local</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">HTTP_ADDR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> 127.0.0.1</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">HTTP_PORT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> 3000</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">ROOT_URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> http://gitea.home.local</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[lfs]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">PATH</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> lfs</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">[log]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">ROOT_PATH</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379"> log</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>仓库地址位置不对的话，仓库列表有该仓库，但仓库里代码没了；</p>
<p><code>ROOT_URL</code> 配置不正确的话就会出现，登录不了的情况（认证成功，但跳转不到个人主页）。</p>
</blockquote>
<p>最后检查解压出的备份数据位置是否和配置文件中的一致；检查数据库能否登录并访问数据表；检查 systemd 配置文件是否配置正确；检查代理配置是否正确（未使用代理忽略）。</p>
<p>启动服务：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> daemon-reload</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea.service</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><ol>
<li>打开网页，进行登入测试</li>
<li>进入<code>管理后台</code> -&gt; <code>维护</code> -&gt; <code>自我检查</code>
<ol>
<li>查看可能存在的问题；</li>
</ol>
</li>
<li>进入<code>管理后台</code> -&gt; <code>维护</code> -&gt; <code>管理面板</code>
<ol>
<li>执行 <code>健康检查所有仓库</code></li>
<li>执行 <code>重新同步所有仓库的 pre-receive、update 和 post-receive 钩子</code></li>
</ol>
</li>
<li>...（待补充）</li>
</ol>
<blockquote>
<p>使用 Gitea Dump 数据恢复之后，代码仓库不完整。曾认为是 Gitea Dump 备份代码仓库时数据备份不完全，但其实是我配置文件没改。因此不管是 Gitea Dump 还是脚本均能够还原数据。</p>
</blockquote>
<h2>改进脚本并部署测试</h2>
<p>如果顺着 AI 提供的框架拓展的话，也许上个月就不会有结果了。仅仅只是为了自动化备份的话——删除其他逻辑，仅保留备份的顺序逻辑和辅助代码，就会变得简单多了。</p>
<p>为了以最小权限来执行备份脚本，维护页的激活方式改为了「第三种方式」。同时脚本不再对 Nginx 内容做任何操作。我只需要给 git 用户授权 <code>systemctl start gitea.service</code> 和 <code>systemctl stop gitea.service</code> 两条命令的权限即可。由于数据库的备份需要 <code>PROCESS</code> 权限，因此不得不去创建一个管理员权限，用来备份数据库。</p>
<p>关于 systemd 、Nginx 和备份等相关配置暂且只能由笔记记录下来，部署时手动恢复了。</p>
<p>改进后的脚本流程图：
<img src="https://images.mtfh.cc/2025/09/01-ea8a87574a482d00fefe8a570cc77324.gif" alt="Gitea 备份脚本流程图" loading="lazy"></p>
<p>脚本内容如下：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#!/bin/bash</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># gitea-backup.sh</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Gitea 备份流程自动化脚本</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">set</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -euo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pipefail</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ================= 配置区 =================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">BACKUP_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#98C379">${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">PWD</span><span style="--shiki-light:#E45649;--shiki-dark:#98C379">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/backup"</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">      # 备份存放目录</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">GITEA_DATA</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$HOME</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea"</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        # Gitea 数据目录</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DB_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"mysql"</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">                 # mysql / postgres / sqlite3</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DB_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"gitea"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DB_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"happilys"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DB_PASS</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DB_PASS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:-</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">KEEP_DAYS</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">7</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">                         # 自动清理超过 KEEP_DAYS 的旧备份</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ================= 工具函数 =================</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">backup_db</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    local</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> dst</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    case</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $DB_TYPE</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> in</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">        mysql</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            mysqldump</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -u</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_USER</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_PASS</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_NAME</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> > </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$dst</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/db.sql"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            sed</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -i</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's/utf8mb4_0900_as_cs/utf8mb4_bin/g'</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$dst</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/db.sql"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ;;</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">        postgres</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            PGPASSWORD</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_PASS</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> pg_dump</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -U</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_USER</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_NAME</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> > </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$dst</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/db.sql"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ;;</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">        sqlite3</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            cp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$GITEA_DATA</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea.db"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$dst</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/db.sqlite"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ;;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        *)</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">            echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "未知数据库类型：</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DB_TYPE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">            exit</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ;;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    esac</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cleanup_old_backups</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 清理 </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$KEEP_DAYS</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 天前的备份"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    find</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -maxdepth</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -type</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> f</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -name</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "gitea-backup-*.7z"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -mtime</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> +</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$KEEP_DAYS</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -print</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -exec</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> rm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -f</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {}</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}    </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ================= 主流程 =================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">DATE</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">$(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">date</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> +'%Y-%m-%d_%H-%M-%S'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">TMP_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mkdir</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> stop</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea.service</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> Gitea 服务已停止"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份 Gitea 数据目录"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">rsync</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --delete</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$GITEA_DATA</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/data/"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份数据库"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">backup_db</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gitea.service</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> Gitea 服务已启动"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 压缩为 7z"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">7z</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -mx=9</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">rm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -rf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cleanup_old_backups</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份完成: </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>!!! 不得将上述代码应用于您的生产环境，否则造成的数据损失，后果自负！</p>
</blockquote>
<h3>rclone 上传备份数据至云端</h3>
<p>使用安装脚本安装 rclone 客户端，执行 <code>rclone config</code> 根据提示配置好网络存储后就可以使用命令同步或上传数据到云端了。网络存储方式 rclone 支持的列表很多，比如亚马逊 S3、七牛 Kodo，还有阿里云 OSS 等等，选择一个合适的即可。</p>
<p>使用脚本安装 rclone ：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ; </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">curl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://rclone.org/install.sh</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> | </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> bash</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><blockquote>
<p>初次脚本安装还挺流畅，写这篇文章的时候，本地试了下——卡着不动了。实在不行，要不咱们上点「魔法」？</p>
<p>没「魔法」的话，想办法下载可执行文件拷到主机，给到可执行权限也行。</p>
</blockquote>
<p>rclone 配置参考这个<a href="https://developer.qiniu.com/kodo/12285/docking-rclone" target="_blank" rel="noopener noreferrer">内容</a>，阿里云 OSS 同理。上传数据到阿里云 OSS ：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">rclone</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> copy</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> backup/gitea-backup-2025-08-27_18-14-16.7z</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> aliyun:mtfh-code-backup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --v</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --log-file=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">~</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">/.config/rclone/rclone.log</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><blockquote>
<p>使用 rclone 向七牛 Kodo 上传备份数据时，尝试几次始终出现 <code>RequestTimeTooSkewed</code> 错误；换成阿里云 OSS 才上传成功。</p>
<figure><img src="https://images.mtfh.cc/2025/09/01-e24f5f04cb08af05ad016216ef25475f.png" alt="request-time-too-skewed-error" tabindex="0" loading="lazy"><figcaption>request-time-too-skewed-error</figcaption></figure>
</blockquote>
<p>上传没问题后，就可以将这段命令添加进脚本，自动同步备份数据了。修改后，内容大致如下：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 省略的内容</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 压缩为 7z"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">7z</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -mx=9</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">rm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -rf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$TMP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cleanup_old_backups</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 上传文件"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">rclone</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> copy</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> aliyun:mtfh-code-backup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -vv</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --log-file=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">~</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">/.config/rclone/rclone.log</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">echo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "===> 备份完成: </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$BACKUP_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/gitea-backup-</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$DATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.7z"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>crontab 创建定时任务</h3>
<p>脚本有了，云端同步的方式也有了，接下来就是让脚本定期执行了。使用 crontab <a href="https://crontab.guru/#00_3_*_*_6" target="_blank" rel="noopener noreferrer">辅助工具</a>，定时任务也就搞定了，唯一需要注意的就是环境变量。</p>
<p>由于当前代码仓库用户仅有我一个人，数据更新不是很频繁，因此采取一周备份一次就够用了。</p>
<p>crontab 配置如下：</p>
<div class="language-crontab line-numbers-mode" data-highlighter="shiki" data-ext="crontab" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-crontab"><span class="line"><span>HOME=/home/git</span></span>
<span class="line"><span>DB_PASS=&#x3C;password></span></span>
<span class="line"><span></span></span>
<span class="line"><span>00 3 * * 6 $HOME/backup/gitea-backup.sh >> $HOME/backup/gitea-backup.log 2>&#x26;1</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>在此之前我是这么配置的</p>
<p><code>00 3 * * 6 DB_PASS=&lt;password&gt; $HOME/backup/gitea-backup.sh &gt;&gt; $HOME/backup/gitea-backup.log 2&gt;&amp;1</code></p>
<p>检查日志 <code>less /var/log/cron</code> ，发现只执行了变量定义，没有执行脚本。</p>
</blockquote>
<h2>待续</h2>
<p>目前博客与 Gitea 为一体，恢复了 Gitea 博客也便能够恢复（还需要手动干预）。然而 Nginx 配置与定时任务缺少代码关联，只能根据配置笔记来手动恢复。需要寻找方法将这些内容关联起来，达成一条命令一杯茶的功夫，所有内容自动恢复。</p>
<p>距离上面那一步还比较遥远 😂，近在眼前的——想好如何完成自动化部署任务吧！</p>
<h2>参考</h2>
<ol>
<li><a href="https://docs.gitea.com/administration/backup-and-restore" target="_blank" rel="noopener noreferrer">Backup and Restore | Gitea Documentation</a></li>
<li><a href="https://stackoverflow.com/questions/76402694/grant-remote-access-for-mariadb" target="_blank" rel="noopener noreferrer">Grant remote access for MariaDB - Stack Overflow</a></li>
<li><a href="https://chatgpt.com/c/68ac0dde-c790-832a-b790-b1d9115419d2" target="_blank" rel="noopener noreferrer">7z 和 xz 比较 - ChatGPT</a></li>
<li><a href="https://developer.qiniu.com/kodo/12285/docking-rclone" target="_blank" rel="noopener noreferrer">使用 Rclone 命令行工具管理对象存储 Kodo_最佳实践_对象存储 - 七牛开发者中心</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/58719487" target="_blank" rel="noopener noreferrer">一文精通 crontab从入门到出坑 - 知乎</a></li>
<li><a href="https://crontab.guru" target="_blank" rel="noopener noreferrer">Crontab.guru - The cron schedule expression generator</a></li>
<li><a href="https://rclone.org/docs/" target="_blank" rel="noopener noreferrer">Documentation</a></li>
</ol>
]]></content>
    <published>2025-09-01T14:02:17.000Z</published>
  </entry>
  <entry>
    <title type="text">VBoxes 本地部署</title>
    <id>https://blog.mtfh.cc/posts/VBoxes%20%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2.html</id>
    <link href="https://blog.mtfh.cc/posts/VBoxes%20%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2.html"/>
    <updated>2025-08-24T14:33:59.000Z</updated>
    <summary type="html"><![CDATA[
<p>优先基础需求使用起来，然后再根据使用时的反馈，发现问题再解决问题。依此不停地迭代，直到达成自己期望的样子。</p>
<blockquote>
<p>如果搭建好完整的 CI/CD 系统，迭代流程会相当丝滑，只要把重心放在设计和编码上就好。如果再配合上 AI 工作流，那效果或许会非常棒。</p>
</blockquote>
<p>这里的本地部署，指的是部署到家庭的 Nas 主机上，操作系统使用的是 <a href="https://rockylinux.org/news/rocky-linux-8-9-ga-release" target="_blank" rel="noopener noreferrer">Rock Linux 8.9</a> （最新 <a href="https://rockylinux.org/news/rocky-linux-10-0-ga-release" target="_blank" rel="noopener noreferrer">10.0</a> 了，后面找机会迁移到 <a href="https://rockylinux.org/news/rocky-linux-9-6-ga-release" target="_blank" rel="noopener noreferrer">9.6</a> ）。</p>]]></summary>
    <content type="html"><![CDATA[
<p>优先基础需求使用起来，然后再根据使用时的反馈，发现问题再解决问题。依此不停地迭代，直到达成自己期望的样子。</p>
<blockquote>
<p>如果搭建好完整的 CI/CD 系统，迭代流程会相当丝滑，只要把重心放在设计和编码上就好。如果再配合上 AI 工作流，那效果或许会非常棒。</p>
</blockquote>
<p>这里的本地部署，指的是部署到家庭的 Nas 主机上，操作系统使用的是 <a href="https://rockylinux.org/news/rocky-linux-8-9-ga-release" target="_blank" rel="noopener noreferrer">Rock Linux 8.9</a> （最新 <a href="https://rockylinux.org/news/rocky-linux-10-0-ga-release" target="_blank" rel="noopener noreferrer">10.0</a> 了，后面找机会迁移到 <a href="https://rockylinux.org/news/rocky-linux-9-6-ga-release" target="_blank" rel="noopener noreferrer">9.6</a> ）。</p>
<h2>VBoxes 服务部署</h2>
<p>VBoxes 服务由三部分组成，vboxes 接口服务、网页和 Nginx 代理。最初希望只有一个 vboxes 程序搞定一切，现在则是由 Nginx 处理网页静态资源请求。因此想要自动化部署就比较麻烦了，不然这篇就叫《VBoxes 服务的自动部署》。</p>
<p>vboxes 程序目前仅作为接口服务，即使具备静态服务器的功能（尚未将网页资源打包进可执行文件当中）。当前的 VBoxes 服务架构如图：</p>
<figure><img src="https://images.mtfh.cc/2025/08/24-dbc3a55dc20aff89a40cb6f71a3edaaa.jpg" alt="VBoxes-1" tabindex="0" loading="lazy"><figcaption>VBoxes-1</figcaption></figure>
<h3>创建 vboxes 服务</h3>
<p>这里使用 <a href="https://wiki.archlinuxcn.org/wiki/Systemd" target="_blank" rel="noopener noreferrer">systemd</a> 来管理 vboxes 服务。</p>
<p>创建并编辑配置文件 <code>/etc/systemd/system/vboxes.service</code> ，内容如下：</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>[Unit]</span></span>
<span class="line"><span>Description=VBoxes (Vagrant box share server)</span></span>
<span class="line"><span>After=syslog.target</span></span>
<span class="line"><span>After=network.target</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Service]</span></span>
<span class="line"><span>RestartSec=2s</span></span>
<span class="line"><span>Type=simple</span></span>
<span class="line"><span>User=happilys</span></span>
<span class="line"><span>Group=happilys</span></span>
<span class="line"><span>WorkingDirectory=/srv/nas/VagrantBoxes</span></span>
<span class="line"><span>ExecStart=/usr/local/bin/vboxes --address 0.0.0.0 --port 9321 --host http://192.168.5.114 --dir ./Boxes</span></span>
<span class="line"><span>Restart=always</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Install]</span></span>
<span class="line"><span>WantedBy=multi-user.target</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>加载服务配置并启动</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> daemon-reload</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 启动</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> vboxes</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 开机自启</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#systemctl enable vboxes</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/08/24-4d41a1b069315b1e279e969a303f44c9.png" alt="vboxes-status" tabindex="0" loading="lazy"><figcaption>vboxes-status</figcaption></figure>
<h3>打包并部署网页</h3>
<p>配置好 Vue 的开发环境后，拉取代码，打包。把生成的网页文件放到指定目录，编辑 Nginx 配置</p>
<blockquote>
<p>实际情况是在开发机打包好，上传到 Nas 主机上的。因为前端不像 C/C++ 程序，跨 CPU 或系统都需要重新编译。C++ 语言是跨平台的，但编译后的二进制不能跨平台。</p>
</blockquote>
<div class="language-nginx line-numbers-mode" data-highlighter="shiki" data-ext="nginx" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-nginx"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">user </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">nginx;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">worker_processes </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">auto;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">error_log </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/var/log/nginx/error.log;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">pid </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/run/nginx.pid;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">events</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    worker_connections </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1024</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">http</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    log_format </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> main</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  '$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">remote_addr</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> - $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">remote_user</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> [$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">time_local</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">] "$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">request</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" '</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                      '$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">status</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">body_bytes_sent</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">http_referer</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" '</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                      '"$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">http_user_agent</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" "$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">http_x_forwarded_for</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    access_log </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /var/log/nginx/access.log  </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    sendfile </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">           on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    tcp_nopush </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    tcp_nodelay </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    keepalive_timeout </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  65</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    types_hash_max_size </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2048</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    include </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            /etc/nginx/mime.types;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default_type </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">       application/octet-stream;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      80</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> vboxes.home.local;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">		# 网页的静态资源处理</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> / {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            root </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">   /srv/data/vboxes-ui;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">		# 其他配置</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>修改网页文件的权限或用户/组</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">chown</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -R</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nginx:nginx</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /srv/data/vboxes-ui</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">chmod</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -R</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> u=rwX,go=rX</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /srv/data/vboxes-ui</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>家庭内网环境一般可以直接关停 SELinux 和防火墙 。</p>
<p>关闭防火墙</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>systemctl disable firewalld</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>编辑 <code>/etc/selinux/config</code> 来停用 SELinux</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span># This file controls the state of SELinux on the system.</span></span>
<span class="line"><span># SELINUX= can take one of these three values:</span></span>
<span class="line"><span>#     enforcing - SELinux security policy is enforced.</span></span>
<span class="line"><span>#     permissive - SELinux prints warnings instead of enforcing.</span></span>
<span class="line"><span>#     disabled - No SELinux policy is loaded.</span></span>
<span class="line"><span>SELINUX=disabled</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 获取 SELinux 状态</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getenfoce</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 临时设置 SELinux</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">## 停止（Permissive: 仅发出警告，不作实际控制）</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setenfoce</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">## 启动（Enforcing: 完全激活状态）</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setenfoce</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>Nginx 代理配置</h3>
<p>这里客户端浏览器可以直接访问接口请求，不过因为浏览器同源策略问题，因此需要使用 Nginx 来代理接口请求（<a href="/posts/%E5%89%8D%E7%AB%AF/VBoxes%20UI/%E5%9F%BA%E4%BA%8E%20Vue3%20%E7%9A%84%20VBoxes-UI%20%E6%90%AD%E5%BB%BA.html#vmboxlist-%E7%BB%84%E4%BB%B6" target="_blank">这里</a>提到过）。</p>
<blockquote>
<p>生产环境不会将公开接口暴露出来，这里 vboxes 监听所有接口（<code>vboxes -a 0.0.0.0</code>），只是为了方便网页开发时的调试工作。</p>
</blockquote>
<div class="language-nginx line-numbers-mode" data-highlighter="shiki" data-ext="nginx" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-nginx"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 省略的配置</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">http</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	# 省略的配置</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      80</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> vboxes.home.local;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">		# 网页静态资源</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> / {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            root </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">   /srv/data/vboxes-ui;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">		# 接口和 .box 文件代理</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ~* </span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">(^/api|\.box$) </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            proxy_pass </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">http://127.0.0.1:9321;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Host $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">server_port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Real-IP $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Forwarded-For $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>重新加载 Nginx 配置后，即可访问 VBoxes 网页</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>systemctl reload nginx</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/08/24-701ef3ab2a8c8f47fc8a6224a959b304.png" alt="nas-home-local" tabindex="0" loading="lazy"><figcaption>nas-home-local</figcaption></figure>
<h2>一些问题</h2>
<p>部署过程中，主要问题还是在 vboxes 程序上，部署反而并未发现多少问题。程序方面，存在内存占用较高、运行时异常重启和下载速度太慢等问题。</p>
<h3>文件权限被篡改</h3>
<p>部署前端文件时，将文件放指定目录后（该目录下的内容同时会被 SMB 共享出去），在经过一段时间之后目录下文件权限全变成问号了。</p>
<p>后面移到了未被 SMB 共享的目录（即前面配置中出现的位置）也便正常了。</p>
<h3>文件下载速度</h3>
<p>局域网带宽为 1 Gbps ，使用 Vagrant CLI 获取「盒子」时平均速率 10 M/s 。很明显不符合预期，在其他设备上测试也是如此。</p>
<p>使用 iperf3 进行网络测试，能够接近理论带宽。</p>
<p>将 vboxes 重新编译安装之后，然后启服务，<strong>似乎</strong>恢复正常了。</p>
<h2>待续</h2>
<blockquote>
<p>运维不懂开发，开发不懂运维。原地死锁——自动运维也就很难发展了。</p>
<p>开发角度，可以通过一些技术将原来七个步骤缩短为三个步骤。相对于七步而言，三步更不易出错；而相对于机器而言，人在执行重复性任务时更容易出错（虽然可以通过后天训练提高）。</p>
</blockquote>
<p>后续的改善步骤除了搭建好自动化流程，还需将重点放在开发上。</p>
<h3>自动化部署</h3>
<blockquote>
<p>手动部署的方式还是麻烦，如果每次都需要这么做，很容易出错。仅就一台设备而言，好说；可设备多起来的话，岂不原地升天。</p>
<p>如果只有一个二进制程序，不管是本地部署，还是容器化部署都很轻松，虽然当下情况也能使用 docker，但后面还是希望改进为单一的可执行文件形式——把网页文件也编译进二进制。不管你外层是否添加代理还是其他的什么东西，vboxes 程序是完备的。不需要像现在这样，由 Nginx 处理静态文件请求。</p>
<p>另外，单一的二进制也方便嵌入式设备使用，也不用像 Qt 部署时，需要带上一堆动态库（系统自带的和开发时使用的版本往往是不一样的，甚至某些库会因为一些需求而魔改过）。</p>
</blockquote>
<p>自动化方案有两种，第一，编写 Dockerfile ，自动构建 Docker 镜像，然后使用 Docker 自动化部署；第二种，完成「二进制资源访问器」的实现，自动化执行 CMake 的 Build 和 Install 步骤（然后重启服务）。将来可以打包为 rpm ，通过包管理器来安装部署。</p>
<p>容器化方案：</p>
<figure><img src="https://images.mtfh.cc/2025/08/24-ca2d5128a43f4724663195c0b1edc076.jpg" alt="VBoxes-3" tabindex="0" loading="lazy"><figcaption>VBoxes-3</figcaption></figure>
<p>非容器化方案：</p>
<figure><img src="https://images.mtfh.cc/2025/08/24-94284ed5ae0fc1a189fd42c04a955fa8.jpg" alt="VBoxes-2" tabindex="0" loading="lazy"><figcaption>VBoxes-2</figcaption></figure>
<h3>vboxes 程序的改进</h3>
<ol>
<li>
<p>程序崩溃跟踪和日志记录</p>
</li>
<li>
<p>可能存在的内存问题</p>
</li>
<li>
<p>多线程：解析数据时程序时没法正常退出的</p>
</li>
<li>
<p>.box 文件元数据解析：vboxes 模块在 box 文件元数据的读取方面太慢了，除了要计算 MD5 值，还需要解析 tar 包，效率打了大大的折扣。既然都用 C/C++ 了，为什么不定义一个数据结构写到文件头呢？</p>
</li>
<li>
<p>代码工程化：相对于整个 VBoxes 项目而言，包括前端和其他的衍生工具。</p>
<ol>
<li>
<p>单元测试：因为修改代码引发 Bug ，谁也不愿因此背锅。如果开发时可以大胆地改进糟糕的实现，而不用担心因为代码的修改导致接口出错而引发的 Bug。单元测试可以很好减轻修改代码引发错误的可能，只需要跑一遍单元测试就可以知道接口有没有被改坏。</p>
</li>
<li>
<p>代码格式化：想要统一所有的 C/C++ 语法代码不可能，但是格式化语言子集还是能够做到的。</p>
</li>
</ol>
</li>
</ol>
<h2>参考</h2>
<ol>
<li>鸟哥著.鸟哥的Linux私房菜.基础学习篇[M].北京：人民邮电出版社，2018.</li>
<li><a href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">ChatGPT</a></li>
</ol>
]]></content>
    <published>2025-08-24T14:02:01.000Z</published>
  </entry>
  <entry>
    <title type="text">Vuepress  文章自动分类</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vuepress%20%20%E6%96%87%E7%AB%A0%E8%87%AA%E5%8A%A8%E5%88%86%E7%B1%BB.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vuepress%20%20%E6%96%87%E7%AB%A0%E8%87%AA%E5%8A%A8%E5%88%86%E7%B1%BB.html"/>
    <updated>2026-01-31T09:20:36.000Z</updated>
    <summary type="html"><![CDATA[
<p><a href="https://theme-hope.vuejs.press/zh/guide/blog/category-and-tags.html" target="_blank" rel="noopener noreferrer">Vuepress Theme Hope</a> 主题默认启用文章分类功能，该功能通过 FrontMatter 中的 category 字段来识别不同类别。如果我希望为文章添加分类就需要维护每篇文章的 category 字段。如果我将这篇文章放在 Web 分类下，一个月后忘记自己设置过 Web 分类，需要翻阅过去的内容，要么重新命名一个 「前端」分类。</p>]]></summary>
    <content type="html"><![CDATA[
<p><a href="https://theme-hope.vuejs.press/zh/guide/blog/category-and-tags.html" target="_blank" rel="noopener noreferrer">Vuepress Theme Hope</a> 主题默认启用文章分类功能，该功能通过 FrontMatter 中的 category 字段来识别不同类别。如果我希望为文章添加分类就需要维护每篇文章的 category 字段。如果我将这篇文章放在 Web 分类下，一个月后忘记自己设置过 Web 分类，需要翻阅过去的内容，要么重新命名一个 「前端」分类。</p>
<p>因此，我希望实现根据文章所在目录自动进行分类的功能。观察目录结构，所有的类别一目了然，也不需要手动编辑 FrontMatter 中的 category 属性。</p>
<blockquote>
<p>根据 Vuepress 插件文档，定制一套分类规则也许更灵活。但毕竟对 Vuepress 理解有限，直接修改博客插件规则较为麻烦，不如改写 FrontMatter 更容易理解。</p>
</blockquote>
<h2>自动分类插件</h2>
<p>自动分类插件通过 <code>autoCategory</code> 函数递归地遍历文档根目录，也就项目中的 posts 目录。通过比较文章所在目录和文档根目录获得到文章的分类列表。比如 <code>posts/前端/Vue/基于 highlight.js 的代码块组件封装</code> 就被归类到 <code>前端</code> 和 <code>Vue</code> 这两个类别。</p>
<p>实现代码如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// scripts/auto-category.js</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fs</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "fs"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "path"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> matter</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "gray-matter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">let</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> docsDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">dir</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) { </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">; }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> entries</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readdirSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> entry</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> entries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> fullPath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">join</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">entry</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> stat</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">statSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">stat</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isDirectory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">entry</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">endsWith</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".md"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> content</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readFileSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"utf8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> parsed</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> matter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">continue</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> dirRelativePath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">relative</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> category</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> dirRelativePath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ===</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> ?</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [] </span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> dirRelativePath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">sep</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">length</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ></span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> newContent</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> matter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stringify</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">writeFileSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">newContent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`Added category "</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" to </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">path</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">relative</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>插件部分定义如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// src/index.ts</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'path'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@vuepress/core'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './node/autoCategory.js'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> autoCategoryPlugin</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">docDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF"> |</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[])</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Plugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> =></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vuepress-plugin-auto-category'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        onPrepared</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> dirs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> Array</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isArray</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD">?</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> docDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> :</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">];</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> absDirs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> dirs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">((</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolve</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">source</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> absDir</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> absDirs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">                autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">absDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    };</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> autoCategoryPlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>导入依赖并注册插件，然后执行打包脚本<strong>两次</strong>即可获得带有文章分类的博客。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 其他内容省略</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> autoCategoryPlugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "./plugins/auto-category/index.js"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineUserConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 省略的配置</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  plugins</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    autoCategoryPlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"posts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">});</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/08/08-8d56ed399d8e857a08ddc7d7dc58d423.png" alt="Screenshot 2025-08-08 at 13.36.11" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-08-08 at 13.36.11</figcaption></figure>
<h2>改插件为 FrontMatter 预处理脚本</h2>
<p>因为<a href="#%E9%87%87%E7%94%A8%E6%8F%92%E4%BB%B6%E7%9A%84%E6%96%B9%E5%BC%8F%E7%94%9F%E6%88%90%E5%88%86%E7%B1%BB%E4%BF%A1%E6%81%AF%E5%A4%B1%E8%B4%A5">某些原因</a>，不得不将插件的 <code>autoCategory.ts</code> 挪出来作为独立的预处理脚本。</p>
<p>修改后 <code>scripts/auto-category.ts</code> 代码内容如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fs</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'fs'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'path'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> matter</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'gray-matter'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> docsDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolve</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">__dirname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'../src/posts'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">dir</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    let</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> count</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> entries</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readdirSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> entry</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> entries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> fullPath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">join</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">entry</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> stat</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">statSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">stat</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isDirectory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">            count</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +=</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">entry</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">endsWith</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".md"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)) {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> content</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readFileSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"utf8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> parsed</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> matter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">continue</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> dirRelativePath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">relative</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> category</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> dirRelativePath</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ===</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> ?</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [] </span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> dirRelativePath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">sep</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">length</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ></span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> newContent</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> matter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stringify</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">parsed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">writeFileSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">newContent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">                console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`Added category "</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">category</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" to </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">path</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">relative</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> fullPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">                count</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">++</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> count</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 执行入口  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`📁 Scanning Markdown files in: </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> changed</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> autoCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">docsDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`✅ Done. </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">changed</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> file(s) updated.`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})();</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>由于需要将该文件作为独立的脚本运行，并且使用的是 TypeScript 语法（也可以把代码改为 JavaScript 直接丢给 Node 执行），这里使用 ts-node 包来执行 Ts 脚本。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -D</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ts-node</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> typescript</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>另外在 package.json 中的 <code>scripts</code> 对象中添加如下命令</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "type"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 这一行需要删掉</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "scripts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // 需要添加的命令</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "docs:prebuild"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts-node scripts/auto-category.ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,   </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里需要删掉 <code>&quot;type&quot;: &quot;module&quot;</code> 这行， 不然会出现 <code>TypeError: Unknown file extension &quot;.ts&quot; for xxx.ts</code> 错误。</p>
<figure><img src="https://images.mtfh.cc/2025/08/08-8c8aa601163a60595e617cb5fd268b87.png" alt="Screenshot 2025-08-08 at 14.08.06" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-08-08 at 14.08.06</figcaption></figure>
<p>最后执行以下命令便可以生成和前面相同的内容</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> docs:prebuild</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> docs:dev</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 或者</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#yarn docs:build</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>当时在做这项修改的时候挺膈应的，需要变更的内容太多了。除了修改代码，还需要安装额外的依赖、修改 package.json 和 CI 文件（build-docs.yaml）。</p>
<p>经验有限，一时半会儿也想不到比较好的办法，因此只能强行去改了。</p>
<p>写这篇文章时想到了一个更快的方法，只需要改动一行代码就能解决问题 😂。</p>
</blockquote>
<h2>实现自动分类时出现的问题</h2>
<p>以下是在测试中遇到的一些问题。<s>第一条并非真实问题，最后一条属于事先调研不充分，第二条才算是实实在在的问题。但既然已经发生，就不存在真实问题和虚假问题的区别了</s>。</p>
<h3>添加分类后文章排序降低</h3>
<p>修改文章的目录结构后，预期结果——最新发布的文章排序应该和未分类时的相同，并且文章状态栏包含分类信息。此时使用的还是插件的形式，在分类页中能看到已经分类过的文章，但是主页中这些文章被放到了文章列表的末尾（开始在主页里不到，下意识认为主页没索引这些文章）。</p>
<figure><img src="https://images.mtfh.cc/2025/08/08-4abdd9f5f3d703ceac694d999ac7053d.png" alt="Screenshot 2025-08-08 at 13.50.22" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-08-08 at 13.50.22</figcaption></figure>
<p>此时怀疑是 Vuepress 的文章排序规则导致的——子目录中的文章优先级始终低于父目录。实则并没有这项机制，不论是 AI 还是自行验证都是如此。</p>
<blockquote>
<p>这时候就体现了 AI 的优势，问 AI 几秒钟就能得出结论，但手动测试的话就需要十五到二十分钟了。因为本就是不熟悉才需要测试，在不熟悉的状态下配置相同的环境，然后再去模拟相似的场景实验，就非常麻烦。</p>
<p>上述场景建立在独立探索情景下，如果有前辈指导那这篇文章也许就不存在了。</p>
</blockquote>
<p>知道了文章排序优先级不受目录层级影响后，发现一条关键线索——分类过的文章日期丢了。</p>
<figure><img src="https://images.mtfh.cc/2025/08/08-b2b94e08b35601ffe7d235b0d97fbed6.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p>顺着年初给 Git 插件打补丁的思路，一路排查到 Git 命令行发现——<strong>修改了文章目录位置却还没提交</strong>，Git 也就查不到记录。</p>
<p>也许插件的实现和发布中间隔的时间太久了，改完之后就做测试和发布应该不会出这种情况。</p>
<h3>文章目录结构变化导致原链接失效</h3>
<p>由于改变目录结构就会改变链接地址，考虑到改动已发布的文章目录位置，会导原链接失效。需要为旧地址设置重定向，来指向新的地址。</p>
<p>做法很简单，为每个移动过的文章添加一条 FrontMatter 属性，名称为 <code>redirectFrom</code> ，值为移动前文的位置，原文件不需要真实存在。该功能是由 <code>redirect-plugin</code> 实现，已由 <a href="https://theme-hope.vuejs.press/zh/config/plugins/others.html#redirect" target="_blank" rel="noopener noreferrer">Vuepress Theme Hope</a> 主题内置并默认开启。</p>
<p>需要注意的是默认的开发服务器对此项设置并不会生效（但 <code>redirectTo</code> 却可行）。打包之后，使用 Python 启动一个静态服务器，测试成功。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">python3</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -m</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> http.server</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 8080</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --directory</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> src/.vuepress/dist/</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>具体原因是<a href="https://theme-hope.vuejs.press/zh/config/theme/basic.html#hotreload" target="_blank" rel="noopener noreferrer">……</a> ?</p>
<h3>采用插件的方式生成分类信息失败</h3>
<p>由于在插件中注册的任何一个钩子调用时机均迟于 plugin-blog 创建分类信息的时机，因此 plugin-blog 创建分类信息时并不知道文章有分类信息。而第二次执行能成功，是因为第一次已经为文章添加好了分类数据——这就相当于人为地为文章添加了分类信息，然后执行打包脚本。事实上，第二次 auto-category 插件并未向文件写入分类信息。</p>
<p>| Hook            | 触发时机             | 能否修改 FrontMatter | 备注                      |
|</p>
]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-08-08T06:38:43.000Z</published>
  </entry>
  <entry>
    <title type="text">基于 highlight.js 的代码块组件封装</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/%E5%9F%BA%E4%BA%8E%20highlight.js%20%E7%9A%84%E4%BB%A3%E7%A0%81%E5%9D%97%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/%E5%9F%BA%E4%BA%8E%20highlight.js%20%E7%9A%84%E4%BB%A3%E7%A0%81%E5%9D%97%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85.html"/>
    <updated>2025-07-31T13:36:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>代码块组件主要用于显示 Vagrantfile 模版，而 Vagrantfile 中的实际内容其实是 Ruby 脚本。使用 highlight.js 对模版内容进行高亮展示能够提供更好的用户体验。另外还需要拷贝模版和下载模版的需求。</p>
<h2>安装依赖和配置</h2>
<p>安装 <code>highlight.js</code> 和 <code>@highlightjs/vue-plugin</code></p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> highlight.js</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @highlightjs/vue-plugin</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<p>代码块组件主要用于显示 Vagrantfile 模版，而 Vagrantfile 中的实际内容其实是 Ruby 脚本。使用 highlight.js 对模版内容进行高亮展示能够提供更好的用户体验。另外还需要拷贝模版和下载模版的需求。</p>
<h2>安装依赖和配置</h2>
<p>安装 <code>highlight.js</code> 和 <code>@highlightjs/vue-plugin</code></p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> highlight.js</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @highlightjs/vue-plugin</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>注册插件的方式有两种：</p>
<p>第一种，全局注册——在 <code>main.ts</code> 文件中导入相关依赖，调用 <code>app.use()</code> 接口来注册插件</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/styles/github-dark.css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/lib/common'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> hljsVuePlugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@highlightjs/vue-plugin'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> app</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> createApp</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">App</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">use</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">router</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">use</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">hljsVuePlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mount</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'#app'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二种，局部注册——在 <code>CodeBlock.vue</code> 文件（手动创建）中导入相关依赖，手动获取组件</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  &#x3C;!--</span><span style="--shiki-light:white;--shiki-light-font-style:italic;--shiki-dark:#FFFFFF;--shiki-dark-font-style:italic">></span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">省略的模版内容&#x3C;--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">highlightjs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"rounded-xl bg-gray-800 text-sm text-white p-4"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  /></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/styles/github-dark.css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/lib/common'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> hljsVuePlugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@highlightjs/vue-plugin'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> highlightjs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> hljsVuePlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">component</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 其他功能代码</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>实际使用视具体情况而定，全局注册比较简单，注册之后项目中任何需要展示代码的地方都可一使用 <code>highlightjs</code> 这个组件；局部注册仅在 <code>CodeBlock.vue</code> 文件中可用，如果其他地方没有代码展示需要，仅在 <code>CodeBlock.vue</code> 文件内使用会更好。不建议两种方式同时存在。</p>
<h2>代码高亮组件封装</h2>
<p>组成 UI 的元素仅两个按钮和一个 <code>highlightjs</code> 组件，样式采用 <code>TailwindCSS</code>，图标则由 <code>Lucide</code> 提供，具体可以查看这篇<a href="/posts/%E5%89%8D%E7%AB%AF/Vue/Vue3%20%E7%BB%84%E4%BB%B6%E4%B8%8E%E6%A0%B7%E5%BC%8F%E5%BA%93%E7%9A%84%E9%80%89%E6%8B%A9.html" target="_blank">内容</a>。代码大致如下：</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"relative group"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"absolute top-5 right-5 flex gap-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    >  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"p-2 bg-gray-700 hover:bg-gray-600 rounded text-white"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">click</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">copyCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">title</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">copied</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> ?</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'Copied!'</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> :</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'Copy code'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      >  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">component</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">is</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">copied</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> ?</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> Check</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> :</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> ClipboardCopy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"w-4 h-4"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"p-2 bg-gray-700 hover:bg-gray-600 rounded text-white"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">click</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">downloadCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        title</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Download code"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      >  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Download</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"w-4 h-4"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">highlightjs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"rounded-xl bg-gray-800 text-sm text-white p-4"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">} </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ClipboardCopy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Check</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Download</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'lucide-vue-next'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/styles/github-dark.css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'highlight.js/lib/common'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> hljsVuePlugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@highlightjs/vue-plugin'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> highlightjs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> hljsVuePlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">component</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> props</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineProps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  code</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">required</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  language</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">default</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'javascript'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  filename</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">default</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ''</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里组件注册使用的是局部注册的方式。基础外观完成了，下面就是主要交互功能了。</p>
<h2>代码拷贝与下载实现</h2>
<p>具体的代码其实并不需要自己去写，只需要告诉 AI 需求，就能够完成我想要的功能。</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 省略的内容</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> copied</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> copyCode</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> async</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  try</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    await</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> navigator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">clipboard</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">writeText</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    copied</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">value</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    setTimeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(() </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">copied</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">value</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">), </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1500</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">catch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">err</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'Copy failed:'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">err</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> downloadCode</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> blob</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Blob</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">([</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#C18401;--shiki-dark:#E06C75">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">], { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'text/plain;charset=utf-8'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> })  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> extensionMap</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { [</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">key</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ruby</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'rb'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    cpp</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'cpp'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    python</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'py'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    bash</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'sh'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> ext</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> extensionMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#C18401;--shiki-dark:#E06C75">language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">||</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'txt'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> filename</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">filename</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ||</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> `code.</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">ext</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> link</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> document</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">createElement</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'a'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  link</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">href</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">createObjectURL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">blob</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  link</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">download</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> filename</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  link</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">click</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">  URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">revokeObjectURL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">link</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">href</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完整代码参考<a href="https://gitea.mtfh.cc/mtfhx/vboxes-ui/src/branch/main/src/components/CodeBlock.vue" target="_blank" rel="noopener noreferrer">这里</a></p>
<h2>实现效果</h2>
<p>编辑 <code>AboutView.vue</code> 内容如下：</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"about flex flex-col justify-center py-8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">CodeBlock</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ruby"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> filename</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Vagrantfile"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> CodeBlock</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@/components/CodeBlock.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> code</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      'Vagrant.configure("2") do |config|</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  config.vm.box = "ubuntu-focal-qt"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  config.vm.box_url = "http://192.168.5.114:9321/api/ubuntu-focal-desktop-en-qt"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  config.vm.provider "virtualbox" do |vb|</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '    vb.gui = true</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '    vb.cpus = 4</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '    vb.memory = "2048"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  end</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  config.vm.provision "shell", inline: &#x3C;&#x3C;-SHELL</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '    apt-get update</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '  SHELL</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      'end'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/07/29-262337a8d8655187ee15d7a342311a7a.gif" alt="vue-code-block-demo" tabindex="0" loading="lazy"><figcaption>vue-code-block-demo</figcaption></figure>
<h2>参考</h2>
<ol>
<li><a href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">ChatGPT</a></li>
<li><a href="https://github.com/highlightjs/vue-plugin/blob/main/README.md" target="_blank" rel="noopener noreferrer">highlightjs/vue-plugin/README.md</a></li>
</ol>
]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-07-29T01:23:38.000Z</published>
  </entry>
  <entry>
    <title type="text">Vue3 组件与样式库的选择</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vue3%20%E7%BB%84%E4%BB%B6%E4%B8%8E%E6%A0%B7%E5%BC%8F%E5%BA%93%E7%9A%84%E9%80%89%E6%8B%A9.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vue3%20%E7%BB%84%E4%BB%B6%E4%B8%8E%E6%A0%B7%E5%BC%8F%E5%BA%93%E7%9A%84%E9%80%89%E6%8B%A9.html"/>
    <updated>2025-07-31T13:36:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>网络教程和一些书籍上提供的组件库多以 Element Plus 为主。了解 TailwindCSS 之后发现它更符合我的品味，另外还发现了 Tailwind Plus 替代 —— Shadcn-Vue。</p>
<p>图标库使用 AI 推荐 <code>lucide-vue-next</code>，话又说回来 <code>Shadcn-Vue</code> 自带的也许省事儿点。</p>
<h2>TailwindCSS 配置</h2>
<p>TailwindCSS 提供一组原子化的 CSS 样式类来构建复杂的 UI 组件，这些原子化的样式涵盖了布局、字体、动画、交互和响应式布局，还有主题配置等等内容。用得好的话，会非常强。</p>]]></summary>
    <content type="html"><![CDATA[
<p>网络教程和一些书籍上提供的组件库多以 Element Plus 为主。了解 TailwindCSS 之后发现它更符合我的品味，另外还发现了 Tailwind Plus 替代 —— Shadcn-Vue。</p>
<p>图标库使用 AI 推荐 <code>lucide-vue-next</code>，话又说回来 <code>Shadcn-Vue</code> 自带的也许省事儿点。</p>
<h2>TailwindCSS 配置</h2>
<p>TailwindCSS 提供一组原子化的 CSS 样式类来构建复杂的 UI 组件，这些原子化的样式涵盖了布局、字体、动画、交互和响应式布局，还有主题配置等等内容。用得好的话，会非常强。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 通过 yarn 安装 tailwindcss 和 @tailwindcss/vite</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> tailwindcss</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @tailwindcss/vite</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>TailwindCSS 安装完成后需要在 vite 配置中激活 TailwindCSS 插件，并在主样式文件中引入样式</p>
<p>vite.config.ts 配置如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> tailwindcss</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@tailwindcss/vite'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  plugins</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [   </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    tailwindcss</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>main.css 主样式文件导入内容：</p>
<div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-css"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">@import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "tailwindcss"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>Shadcn-Vue 配置和使用</h2>
<p>Shadcn-Vue 是一个由社区主导的 shadcn/ui 移植项目。shadcn/ui 专为 React（基于 Tailwind CSS）设计的 UI 组件库，没有 Vue3 的原生支持。Shadcn-Vue 的诞生便是源于 Vue 生态系统对它的需求。</p>
<p>安装依赖前需要编辑两个文件。首先，编辑 <code>tsconfig.json</code></p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 省略已有内容</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "compilerOptions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "baseUrl"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "paths"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "@/*"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "./src/*"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      ]  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再编辑 <code>tsconfig.app.json</code> 这个文件</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "compilerOptions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // path 属性在 tsconfig.app.json 文件中默认存在</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // 所以之需要添加 baseUrl 即可</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "baseUrl"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "paths"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "@/*"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "./src/*"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外根据官方文档说明，需要对 vite.config.ts 进行配置。但是脚手架默认生成的内容是有路径解析的配置。</p>
<blockquote>
<p>当前项目：</p>
<ul>
<li><code>create-vue</code> 版本 <code>3.17.0</code></li>
<li><code>Vue</code> 版本 <code>3.5.17</code></li>
<li><code>Vite</code> 版本 <code>7.0.0</code> 。</li>
</ul>
</blockquote>
<p>为保障后续步骤的正常进行，应该检查一下这项配置。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">/* 注释的内容是官方文档提供的配置 */</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">/* 未注释的是默认已存在配置 */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">//import path from 'node:path'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">fileURLToPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'node:url'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	// ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  resolve</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    alias</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">      //'@': path.resolve(__dirname, './src'),</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '@'</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> fileURLToPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'./src'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">meta</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    },  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>执行以下命令进行安装，然后根据需要选择组件进行添加。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 安装初始化库</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dlx</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shadcn-vue@latest</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> init</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 添加组件</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dlx</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shadcn-vue@latest</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> button</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>官方给出的 <code>yarn</code> 安装命令使用的是 <code>npx</code> ，使用该命令执行后，包管理变成 <code>npm</code> 了？</p>
<p>根据 <code>Vue3</code> 脚手架使用经验，试了 <code>yarn dlx</code> 命令，成功且尚未发现问题。</p>
</blockquote>
<p>安装并添加组件后，工程目录下会多些东西。<code>components.json</code> 、<code>lib/utils.ts</code> 和 <code>components/ui/*</code></p>
<figure><img src="https://images.mtfh.cc/2025/07/16-a8bef86aa521097b756732c3bd8e510a.png" alt="components-dir-tree" tabindex="0" loading="lazy"><figcaption>components-dir-tree</figcaption></figure>
<h3>简单的使用案例</h3>
<p>在已有 <code>AboutView.vue</code> 文件编辑如下内容</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"about"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>This is an about page&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    &#x3C;!-- 添加的内容，使用了 TailwindCSS 的样式类 --></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex w-full max-w-sm items-center gap-1.5"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Input</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"text"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">placeholder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> v-model</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> @</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">click</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">sayHello</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>你好！&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!-- 添加的内容，使用组合式 API --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@/components/ui/button'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Input</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@/components/ui/input'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> sayHello</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">  alert</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`Hello </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">name</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">value</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">!`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">style</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">@media</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (min-width: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1024</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75">px</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  .about</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    min-height: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">100</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75">vh</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    display: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">flex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    align-items: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">center</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">style</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/07/16-1dd508609662f0a1828675a09bb2ee56.gif" alt="components-demo" tabindex="0" loading="lazy"><figcaption>components-demo</figcaption></figure>
<h2>Lucide 配置和使用</h2>
<p><em>Lucide 是一个开源图标库，提供 1000 多个矢量 (SVG) 文件，用于在数字和非数字项目中显示图标和符号。该库旨在通过提供多个官方软件包，帮助设计师和开发者更轻松地将图标融入到项目中，从而更轻松地在项目中使用这些图标。</em></p>
<p>安装 <code>lucide-vue-next</code></p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> lucide-vue-next</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>使用案例</h2>
<p>编辑 <code>AboutView.vue</code> 内容如下</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"about"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">		&#x3C;!-- ... --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex w-full max-w-sm items-center gap-1.5"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">User</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> color</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"black"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">size</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">32</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">			&#x3C;!-- ... --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// ...</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'lucide-vue-next'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/07/16-991012962f4dda7e09460c0aabf798b2.png" alt="icon-sample" tabindex="0" loading="lazy"><figcaption>icon-sample</figcaption></figure>
<p>具体的图标名称在<a href="https://lucide.dev/icons" target="_blank" rel="noopener noreferrer">这里</a>搜索。</p>
<p>Lucide 图标默认以 Vue 组件方式提供，在用作状态图标时需要单独对其大小进行设置，不太方便，将来能否改用其他方式——以类名方式使用的图标，比如 <a href="https://unocss.net/" target="_blank" rel="noopener noreferrer">UnoCSS</a> ？</p>
<h2>参考</h2>
<ol>
<li><a href="https://tailwindcss.com/docs/installation/using-vite" target="_blank" rel="noopener noreferrer">Installing Tailwind CSS with Vite - Tailwind CSS</a></li>
<li><a href="https://www.shadcn-vue.com/docs/introduction.html" target="_blank" rel="noopener noreferrer">Introduction - shadcn/vue</a></li>
<li><a href="https://lucide.dev/guide/" target="_blank" rel="noopener noreferrer">What is Lucide? | Lucide</a></li>
</ol>
]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-07-16T05:59:42.000Z</published>
  </entry>
  <entry>
    <title type="text">使用 create-vue 脚手架创建 Vue3 项目</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/%E4%BD%BF%E7%94%A8%20create-vue%20%E8%84%9A%E6%89%8B%E6%9E%B6%E5%88%9B%E5%BB%BA%20Vue3%20%E9%A1%B9%E7%9B%AE.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/%E4%BD%BF%E7%94%A8%20create-vue%20%E8%84%9A%E6%89%8B%E6%9E%B6%E5%88%9B%E5%BB%BA%20Vue3%20%E9%A1%B9%E7%9B%AE.html"/>
    <updated>2025-10-29T08:50:15.000Z</updated>
    <summary type="html"><![CDATA[
<p>根据官方文档，使用 Vue3 脚手架能够很轻松地搭建起 Vue 项目。唯一遇到的问题就是 yarn 对 node module 处理方式上的问题（如果使用其他包管理工具请忽略）。</p>
<p>yarn4+ 默认使用 PnP 模式来处理 node modules ，但是这样在执行 package.json 的脚本和 IDE 错误提示都存在问题。最简单的解决方法就是——在项目根目录下添加 .yarnrc.yml 文件并添内容如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">nodeLinker</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-modules</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<p>根据官方文档，使用 Vue3 脚手架能够很轻松地搭建起 Vue 项目。唯一遇到的问题就是 yarn 对 node module 处理方式上的问题（如果使用其他包管理工具请忽略）。</p>
<p>yarn4+ 默认使用 PnP 模式来处理 node modules ，但是这样在执行 package.json 的脚本和 IDE 错误提示都存在问题。最简单的解决方法就是——在项目根目录下添加 .yarnrc.yml 文件并添内容如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">nodeLinker</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">node-modules</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>以下是项目搭建说明。</p>
<h2>安装 nodejs</h2>
<p>Windows 直接在<a href="https://nodejs.org/en/download" target="_blank" rel="noopener noreferrer">官网</a>下载最新的 LTS 版本安装。</p>
<figure><img src="https://images.mtfh.cc/2025/07/14-946edf17192bb21eae5871373a34aa4b.png" alt="download-node-js" tabindex="0" loading="lazy"><figcaption>download-node-js</figcaption></figure>
<p>Mac 可以使用 Homebrew 安装，但需要「魔法」就不提供具体说明了——各显神通吧。</p>
<p>Linux 的不同发行版本有 apt、yum 或 pacman 等包管理器，根据具体系统版本使用相应的包管理器安装即可。另外，如果考虑需要用到不同的 node 版本可以选择 <a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener noreferrer">nvm</a>。</p>
<p>下面是 Linux 使用 nvm 安装 nodejs 的说明：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 如果没有 git 先安装 git</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   apt install -y git</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   pacman -Syu git</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   dnf install -y git</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yum</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 下载并安装 nvm:</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NVM_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$HOME</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/.nvm"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x26;&#x26; (</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">  git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> clone</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://gitee.com/mirrors/nvm-sh.git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">  cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">  git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> checkout</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> `</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> describe </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">--abbrev=0</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --tags</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --match</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "v[0-9]*" $(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> rev-list </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">--tags</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --max-count=1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">)`</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) &#x26;&#x26; </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">\.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/nvm.sh"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 安装 Node.js:</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">nvm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 22</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 验证 node 版本</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">node</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">nvm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> current</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 启用 yarn:</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">corepack</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> enable</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> yarn</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 验证 yarn 版本</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>安装完成后，添加如下内容到 <code>~/.bashrc</code>、 <code>~/.bash_profile</code>、 <code>~/.zshrc</code> 或 <code>~/.profile</code> 。仅需要添加内容到其中某一个文件末尾即可，主要目的是为了 Shell 重启后 <code>nvm</code> 命令还能够正常使用（类似于 Windows 中配置 Path 环境变量）。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NVM_DIR</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$HOME</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/.nvm"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[ </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-s</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/nvm.sh"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ] &#x26;&#x26; </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">\.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/nvm.sh"</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> # This loads nvm</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[ </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-s</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/bash_completion"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ] &#x26;&#x26; </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">\.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$NVM_DIR</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/bash_completion"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方式适用于大多数 Linux 发行版本（也支持 Mac ），除了安装步骤有点麻烦外暂未发现其他问题。</p>
<p>由于某些原因不能使用默认的安装脚本，只好采用克隆镜像仓库的方式进行安装了。</p>
<h2>更换 yarn 源</h2>
<p>这算是前端工程化，首次创建工程前的基操了。</p>
<p>yarn4+ 版本命令：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 查询当前使用的镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> get</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npmRegistryServer</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 设置为淘宝镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npmRegistryServer</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -H</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.npmmirror.com</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 还原为官方镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npmRegistryServer</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -H</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.yarnpkg.com</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>yarn 1.x 版本命令：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 查询当前使用的镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> get</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 设置为淘宝镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.npmmirror.com/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 还原为官方镜像源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.yarnpkg.com/</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>create-vue 创建工程</h2>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dlx</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> create-vue@latest</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>执行命令后需要根据提示完成以下几个步骤：</p>
<ol>
<li>设置项目名称
<ul>
<li>名称：vboxes-ui</li>
</ul>
</li>
<li>功能选择
<ul>
<li>TypeScript
<ul>
<li>用于提供 TypeScript 语言支持。</li>
<li>会 Ts 肯定要选的，要不然就原生 JS 了</li>
<li>我直接选上了，边用边学</li>
</ul>
</li>
<li>JSX Support
<ul>
<li>JavaScript 语法扩展，可以在 JavaScript 文件中书写类似 HTML 的标签</li>
<li>为 React 用户准备的
<ul>
<li>有 React 开发经验的话，可以选择该项</li>
<li>要不然还是直接学习 Vue 语法来的方便</li>
</ul>
</li>
</ul>
</li>
<li>Router (SPA development)
<ul>
<li><em>单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时，URL 会随之更新，但页面不需要从服务器重新加载。</em></li>
<li>个人项目以 SPA 为主，随着功能的丰富，必然会用到</li>
</ul>
</li>
<li>Pinia (state management)
<ul>
<li>牛 B 一点的全局变量 ？</li>
<li><a href="https://pinia.vuejs.org/zh/introduction.html" target="_blank" rel="noopener noreferrer">简介 | Pinia</a></li>
<li>尚且用不到</li>
</ul>
</li>
<li>Vitest (unit testing)
<ul>
<li><a href="https://cn.vuejs.org/guide/scaling-up/testing#unit-testing" target="_blank" rel="noopener noreferrer">单元测试</a>，目前还用不到</li>
</ul>
</li>
<li>End-to-End Testing
<ul>
<li>看这个：<a href="https://cn.vuejs.org/guide/scaling-up/testing#e2e-testing" target="_blank" rel="noopener noreferrer">端到端（E2E）测试</a></li>
<li>似乎用不上</li>
</ul>
</li>
<li>ESLint (error prevention)
<ul>
<li>静态分析工具
<ul>
<li>「问题越早发现，越容易被解决」</li>
</ul>
</li>
<li>要选上</li>
</ul>
</li>
<li>Prettier (code formatting)
<ul>
<li>代码格式化工具，统一代码风格——需要</li>
<li>仅针对代码文本格式，并不能统一编码风格[笑]</li>
<li>编码风格还是需要统一的编码规范来协调
<ul>
<li>需求都赶不完，谁还有精力管代码规范，能跑就行[笑哭]</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>试验功能
<ul>
<li>Oxlint (experimental)
<ul>
<li>由 Rust 实现的 ESLinter</li>
<li>比 ESLinter 更快，也不需要繁琐的配置</li>
<li><a href="https://oxc.rs/docs/guide/usage/linter" target="_blank" rel="noopener noreferrer">Linter</a></li>
</ul>
</li>
<li>rolldown-vite (experimental)
<ul>
<li>由 Rust 驱动的 JavaScript 打包工具</li>
<li><a href="https://cn.vite.dev/guide/rolldown" target="_blank" rel="noopener noreferrer">Rolldown 集成 | Vite 官方中文文档</a></li>
<li><a href="https://rolldown.rs/guide/" target="_blank" rel="noopener noreferrer">Introduction | Rolldown</a></li>
</ul>
</li>
</ul>
</li>
</ol>
<h2>测试并提交代码</h2>
<p>切换到工程目录，使用命令或者打开 IDE 运行项目。</p>
<p>使用命令启动项目：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 切换到工程目录</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> vboxes-ui</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 安装 npm 包</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 启动开发服务器</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>根据提示打开网页地址，如图：</p>
<figure><img src="https://images.mtfh.cc/2025/07/14-5ac25ceb641c3224fec584aa868cca2b.png" alt="vite-dev-server-output" tabindex="0" loading="lazy"><figcaption>vite-dev-server-output</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/07/14-d83e6d19d6585a7ca115b157ea9d032b.png" alt="vue3-start-up" tabindex="0" loading="lazy"><figcaption>vue3-start-up</figcaption></figure>
<p>没问题后就可以直接提交内容到 Git 仓库了。初始化 Git 仓库并创建提交：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 初始化版本仓库</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> init</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 添加项目下的文件到暂存区</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -A</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 提交修改</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> commit</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -m</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Init: vboxes-ui"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>推送内容到远程仓库：</p>
<blockquote>
<p>Github、Gitee、GitCode、GitLab or Gitea ？</p>
<p>如果不考虑网络因素，当然首选 Github。Git 私有仓库方案 Gitea 比较好，GitLab 功能丰富，但也吃资源啊。</p>
</blockquote>
<p>先在平台创建空的版本仓库，拷贝仓库地址，为本地仓库添加远程仓库，再推送本地分支</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 添加远程仓库</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># git remote add &#x3C;remote name> &#x3C;remote url></span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> remote</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> add</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> origin</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git@gitea.mtfh.cc:mtfhx/vboxes-ui.git</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 关联远程分支(当前分支首次推送时)</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   git push -u &#x3C;remote name> &#x3C;branch name></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#     同： git push --set-upstream &#x3C;remote name> &#x3C;branch name></span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> push</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -u</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> origin</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 推送当前分支</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   git push &#x3C;remote name> &#x3C;branch name></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   git push &#x3C;remote name> [&#x3C;current branch>]</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#   git push [&#x3C;default remote>] [&#x3C;current branch>]</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> push</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>目录结构</h2>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">tree</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -L</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 2</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --prune</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -P</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'node_modules|.*|*'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 输出：</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># .</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── .editorconfig</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── env.d.ts</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── eslint.config.ts</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── .gitattributes</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── .gitignore</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── index.html</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── node_modules</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># │   └── .yarn-integrity</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── package.json</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── .prettierrc.json</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── public</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># │   └── favicon.ico</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── README.md</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── src</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># │   ├── App.vue</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># │   └── main.ts</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── tsconfig.app.json</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── tsconfig.json</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── tsconfig.node.json</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── vite.config.ts</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># ├── yarn.lock</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># └── .yarnrc.yml</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>项目文件说明（由 ChatGPT 生成）：</p>
<p>| 路径 / 文件              | 说明                                   |
|</p>
]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-07-14T06:02:09.000Z</published>
  </entry>
  <entry>
    <title type="text">基于 Vue3 的 VBoxes-UI 搭建</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/VBoxes%20UI/%E5%9F%BA%E4%BA%8E%20Vue3%20%E7%9A%84%20VBoxes-UI%20%E6%90%AD%E5%BB%BA.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/VBoxes%20UI/%E5%9F%BA%E4%BA%8E%20Vue3%20%E7%9A%84%20VBoxes-UI%20%E6%90%AD%E5%BB%BA.html"/>
    <updated>2025-08-24T14:31:09.000Z</updated>
    <summary type="html"><![CDATA[
<p>vboxes-ui 是 vboxes 服务的网页 UI 负责展示从 vboxes 服务请求到的环境数据。而 vboxes 通过分析目录下的所有 <code>.box</code> 文件并将其归类到不同的环境当中去，然后提供不同的接口供网页的数据查询与展示、Vagrant CLI 的数据请求和 <code>.box</code> 文件下载。</p>
<p>vboxe-ui 采用的是 Vue3 框架，配合 Schade-Vue 和 TailwindCSS 的组件与样式创建的一个简单前端网页，目前还处在开发中。</p>
<blockquote>
<p>完整项目开发周期可能持续一个月以上，设置一些关键节点能够很好的维持项目的推进。比如完成 vboxes 关键接口或完成 vboxe-ui 创建（等待接口测试）等等。设置节点也方便记录与总结。一口气完成中大型的项目的开发目前来看有点吃力，不仅考验耐力也考验体力[汗]。</p>
</blockquote>]]></summary>
    <content type="html"><![CDATA[
<p>vboxes-ui 是 vboxes 服务的网页 UI 负责展示从 vboxes 服务请求到的环境数据。而 vboxes 通过分析目录下的所有 <code>.box</code> 文件并将其归类到不同的环境当中去，然后提供不同的接口供网页的数据查询与展示、Vagrant CLI 的数据请求和 <code>.box</code> 文件下载。</p>
<p>vboxe-ui 采用的是 Vue3 框架，配合 Schade-Vue 和 TailwindCSS 的组件与样式创建的一个简单前端网页，目前还处在开发中。</p>
<blockquote>
<p>完整项目开发周期可能持续一个月以上，设置一些关键节点能够很好的维持项目的推进。比如完成 vboxes 关键接口或完成 vboxe-ui 创建（等待接口测试）等等。设置节点也方便记录与总结。一口气完成中大型的项目的开发目前来看有点吃力，不仅考验耐力也考验体力[汗]。</p>
</blockquote>
<p>以下是已完成内容的实现记录。</p>
<h2>VMBoxCard 组件</h2>
<p>通过接口请求到的数据是一组相同的数据格式，根据 Vue 的设计思路只需要定义其中一个显示组件，完整数据集的展示直接套用这个组件即可。VMBoxCard 需要展示数据格式参考如下：</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ubuntu-focal-desktop-en-qt"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "date"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1750553587</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "author"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"vboxes"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "description"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Ubuntu Focal Desktop Environment for Qt Development"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "site"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://127.0.0.1:9321/api"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://127.0.0.1:9321/api/ubuntu-focal-desktop-en-qt"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "vagrantfile"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Vagrant.configure(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">2</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">) do |config|</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  config.vm.box = </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ubuntu-focal-qt</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  config.vm.box_url = </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://127.0.0.1:9321/api/ubuntu-focal-desktop-en-qt</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"\n\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  config.vm.provider </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">virtualbox</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> do |vb|</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    vb.gui = true</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    vb.cpus = 4</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    vb.memory = </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">2048</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  end</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  config.vm.provision </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">shell</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">, inline: &#x3C;&#x3C;-SHELL</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    apt-get update</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  SHELL</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">end"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过上面的 json 数据再结合 AI 就能很轻松地搞定（实际调试和改进花了不少功夫[笑哭]）。</p>
<blockquote>
<p>AI 可能并不能一次就能做到我们想要的，需要提供必要的提示，然后修改测试。所以 AI 尚且还不能完全接替人工开发。如果从开发者的角度考虑，反而能够为开发者节省大量的时间——不论是设计、实现、问题的排查都比传统的人工加搜索引擎高效得多。因此它更像是个更高级的搜索引擎。</p>
</blockquote>
<p>为了优化 VMBoxCard 组件对 Vagrantfile 文件内容的展示和业务需求（代码拷贝和下载），实现了 CodeBlock 组件，完整的 VMBoxCard 组件代码如下：</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"rounded-2xl shadow p-6 bg-white space-y-4 cursor-pointer hover:ring-2 hover:ring-blue-300 transition"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">dblclick</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">expanded</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> !</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">expanded</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  >  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h2</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"text-2xl font-semibold text-gray-800"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>{{ box.name }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex flex-wrap items-center gap-x-6 text-sm text-gray-500"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex items-center gap-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">UserRound</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"inline-block w-4 h-4 mr-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /> {{ box.author }}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex items-center gap-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Calendar</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"inline-block w-4 h-4 mr-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /> {{ formattedDate }}  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"flex items-center gap-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Globe</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"inline-block w-4 h-4 mr-1"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">href</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">site</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> target</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"_blank"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"underline break-all"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>{{ box.site }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"text-gray-600 leading-relaxed"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>{{ box.description || '无描述信息' }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"text-xs italic text-gray-400"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>💡 单击卡片查看或隐藏 Vagrantfile&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Transition</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"fold"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        v-if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">expanded</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"overflow-hidden transition-all duration-500 ease-in-out"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      >  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">CodeBlock</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">vagrantfile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> language</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ruby"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> filename</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Vagrantfile"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> />  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Transition</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> CodeBlock</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './CodeBlock.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">UserRound</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Calendar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Globe</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'lucide-vue-next'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineProps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  box</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> Object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> expanded</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> formattedDate</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(() </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> date</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">date</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1000</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toLocaleDateString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>VMBoxList 组件</h2>
<p>通过向服务请求数据，然后使用 <code>v-for</code> 关键字迭代数据集即可，向 VMBoxCard 传递数据可以通过 <code>:box</code> 属性完成，该属性已在 VMBoxCard 组件中定义。</p>
<p>因为接口比较简单，直接使用 <code>fetch()</code> ，内容如下：</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"grid gap-6"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">VMBoxCard</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> v-for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">box</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> in</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> boxes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">box</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> VMBoxCard</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './VMBoxCard.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> boxes</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[]>([])  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">fetch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'/api'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">then</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">((</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">response</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> response</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">json</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">())  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">then</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">((</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    boxes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">value</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">infos</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  })  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">catch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">((</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'Error fetching data:'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>测试过程中接口地址开和发服务器地址不一致（二者非同一个进程，甚至不在同一台设备上），会出现浏览器同源策略的限制，导致无法获取到接口数据情况。解决同源限制的方式有很多，这里使用的是代理的方式。</p>
<p>编辑 vite.config.js 配置文件，添加指向接口服务反向代理即可解决该问题了。配置文件示例如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">defineConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vite'</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 省略的包引用</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// https://vite.dev/config/  </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 省略的内容</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  server</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    proxy</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      '/api'</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        target</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'http://127.0.0.1:9321'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        changeOrigin</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        //rewrite: (path) => path.replace(/^\/api/, ''),  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>运行服务端，等待数据解析完成，最终呈现效果如下</p>
<figure><img src="https://images.mtfh.cc/2025/07/10-e7a528f1e37cd2964eb5f2e0569a8110.png" alt="Screenshot 2025-07-10 at 17.32.43" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-07-10 at 17.32.43</figcaption></figure>
<p>题外话——尝试使用模版文件来启动环境，成功运行 [开心]。</p>
<figure><img src="https://images.mtfh.cc/2025/07/10-0e49ef5b791ce21e2ff2cec5b3e25fea.png" alt="Screenshot 2025-07-10 at 17.35.21" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-07-10 at 17.35.21</figcaption></figure>
<h2>待续</h2>
<p>目前的网页部分页仅仅只是完成了基础信息的展示，虽然网页端的核心功能仅限于数据展示，但必然不仅仅是列表信息，除此之外还有搜索与过滤、帮助、文件列表和 .box 文件下载。这只是开始，更多功能还需 vboxes 服务的配合来完成。</p>
<h2>参考</h2>
<ol>
<li><a href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">ChatGPT</a></li>
<li><a href="https://cn.vuejs.org/" target="_blank" rel="noopener noreferrer">Vue.js - 渐进式 JavaScript 框架 | Vue.js</a></li>
<li><a href="https://cn.vite.dev/config/server-options.html#server-proxy" target="_blank" rel="noopener noreferrer">开发服务器选项 | Vite 官方中文文档</a></li>
<li>《Vue.js 3 + TypeScript 完全指南》</li>
</ol>
]]></content>
    <category term="前端"/>
    <category term="VBoxes UI"/>
    <published>2025-07-10T13:04:49.000Z</published>
  </entry>
  <entry>
    <title type="text">Vuepress 插件开发 - 版本历史</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vuepress%20%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%20-%20%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vuepress%20%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%20-%20%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2.html"/>
    <updated>2025-07-31T13:36:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>在此之前博客关于页内容始终是空白的，刚好自己也有查看博客改进记录的想法。「Vuepress Theme Hope」主题中的时间线功能仅展示文章发布历史，但是我希望能够看到博客修改记录（也就是 Git 提交历史），方便我快速浏览整个网站所做的修改。虽然 Gitea 本来就可以查看历史，但是把需要关注的内容收集到一块岂不更好？同时还能填补关于页的空白，何乐不为？</p>
<h2>插件开发</h2>
<p>在 .vuepress 目录里创建 plugins 目录用于存放所有的插件目录（目前只有一个）。然后创建 git-history 目录，这个目录里所有文件便是该插件的全部内容了。完成后的目录结构如下：</p>]]></summary>
    <content type="html"><![CDATA[
<p>在此之前博客关于页内容始终是空白的，刚好自己也有查看博客改进记录的想法。「Vuepress Theme Hope」主题中的时间线功能仅展示文章发布历史，但是我希望能够看到博客修改记录（也就是 Git 提交历史），方便我快速浏览整个网站所做的修改。虽然 Gitea 本来就可以查看历史，但是把需要关注的内容收集到一块岂不更好？同时还能填补关于页的空白，何乐不为？</p>
<h2>插件开发</h2>
<p>在 .vuepress 目录里创建 plugins 目录用于存放所有的插件目录（目前只有一个）。然后创建 git-history 目录，这个目录里所有文件便是该插件的全部内容了。完成后的目录结构如下：</p>
<figure><img src="https://images.mtfh.cc/2025/05/31-e019b94b2bf2bb025ca510fa3093b85e.png" alt="Screenshot 2025-05-31 at 20.10.46" tabindex="0" loading="lazy"><figcaption>Screenshot 2025-05-31 at 20.10.46</figcaption></figure>
<p>git-history 目录下的 index.ts 文件，提供插件的定义和导出。内容如下:</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// src/index.ts</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@vuepress/utils'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">Plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@vuepress/core'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">prepareGitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './prepareGitData.js'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> pathModule</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'path'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> OUTPUT_FILE</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> pathModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolve</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">__dirname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'./git-data.json'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> gitHistoryPlugin</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ()</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Plugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> =></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vuepress-plugin-git-history'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        onPrepared</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            prepareGitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">source</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">OUTPUT_FILE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        },</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        clientConfigFile</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolve</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">__dirname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'./client/clientConfig.ts'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    };</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> gitHistoryPlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>clientConfig.ts 文件用于注册 Vue 组件，并向 Vue 组件提供历史数据。大致内容如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// src/client/clientConfig.ts</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">defineClientConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@vuepress/client'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> RepoHistory</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './components/RepoHistory.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> gitData</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '../git-data.json'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineClientConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    enhance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({ </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> }) {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">        app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">component</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'RepoHistory'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">RepoHistory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">        app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">provide</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'gitData'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">gitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">});</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完成上述插件定义之后，需要去 vuepress 的配置文件使用插件。.vuepress 目录里的 config.ts 内容如下：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 省略其他导入</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> gitHistoryPlugin</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> './plugins/git-history/index.js'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> defineUserConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">({</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 省略其他配置</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  plugins</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    gitHistoryPlugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">});</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后便是面向 AI 编程了[笑]。</p>
<blockquote>
<p>最初并未想到可以直接在 .vuepress 目录下直接编写插件。原计划在项目根目录创建一个名叫 vupress-plugin-git-history 的 NPM 包项目，以一个独立的软件包视角去考虑这个问题。但是这样发现如何本地测试代码成为关键问题。参考了别人的指导并分析 tsconfig.json 配置之后发现事实并不需要如此麻烦。</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"include"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  "src/.vuepress/**/*.ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  "src/.vuepress/**/*.vue"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">],</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这两段内容告诉我放在 .vuepress 下的任何 Ts 和 Vue 组件都会被解析。将测试代码移动到 .vuepress 目录之后，是能够奏效的。</p>
</blockquote>
<h2>插件功能分析</h2>
<p>版本历史插件核心功能便是展示博客仓库主分支的提交历史，并过滤掉文章相关的提交历史。另外方便查看具体的文件修改，提供了 Gitea Commit 超链接。具体的功能列表如下：</p>
<ol>
<li>倒序排列所有非文章提交记录</li>
<li>展示每个提交记录的具体操作（如果有的话）</li>
<li>为每个记录提供 Gitea Commit 超链接（能够查看具体内容修改）</li>
</ol>
<p>接下来详细介绍功能实现细节。每一个功能的前提都需要完整的提交记录，这点在之前给官方的 git 插件打补丁时有所了解。通过 node 执行相应的 git 命令可以很轻松地获取完整的提交记录，然后就是解析和过滤了，这个先不作具体分析。</p>
<p>解析出的数据首先存到 json 文件中去，然后 Vue 组件通过「客户端配置」将数据导入。查看 index.ts 和 clientConfig.ts 可以知道，插件调用 <code>prepareGitData()</code> 函数将数据写到指定的 json 文件，然后客户端调用 <code>app.provide()</code> 将数据提供给组件。Vue 组件内容如下：</p>
<div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-vue"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"history-container"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>Version History&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">h1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"timeline"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> v-for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">index</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">in</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> groupedCommits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">index</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"timeline-group"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"timeline-label"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>{{ group.label }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> v-for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">commit</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> in</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">commits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">hash</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"timeline-entry"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"commit-dot"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"commit-content"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">a</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"commit-message"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> target</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"_blank"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">href</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">gitData</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">repoUrl</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/commit/</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">commit</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">hash</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>{{ commit.message.short }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"commit-meta"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> {{ commit.author }} · {{ formatDate(commit.date) }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ul</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> v-if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">              &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">li</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> v-for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">item</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> in</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> {{ item }}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">li</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">ul</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ts"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> setup</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">inject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'vue'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">GitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '../../node/prepareGitData.js'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> gitData</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> inject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">GitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'gitData'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">repoUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">commits</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [] });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> formatDate</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">dateStr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> date</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">dateStr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toLocaleDateString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> groupedCommits</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(() </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> groups</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {}</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> commit</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> gitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">commits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> year</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getFullYear</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">groups</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">year</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]) </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">groups</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">year</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> []</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    groups</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">year</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">].</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> Object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">entries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">groups</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sort</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">((</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">b</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> b</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]) </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 倒序</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(([</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">year</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">commits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ({</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        label</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> year</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">        commits</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      }))</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">})</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Vue 组件主要两件事儿，对提交的记录按年份分组；拼接 Gitea Commit 超链接。知道需要哪些数据之后，再来看 prepareGitData.ts 如何定义并解析数据。prepareGitData.ts 代码及解析如下：</p>
<p>提交记录的数据定义（应该把它们单独拿出来）。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    tag</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    short</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    description</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[];</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    hash</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    author</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    date</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    message</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    repoUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    commits</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[];</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>获取远程仓库地址，并将 git 协议转换为 https。这里可以改进一下先判断当前协议，再考虑需不需要转换。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">execSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'child_process'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRemoteRepoUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">repo</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    try</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> logs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> execSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            `git remote get-url </span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">repo</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            {</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">encoding</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'utf-8'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        );</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> match</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> logs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">trim</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">match</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">/</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">^</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">git@(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">[</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">^</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">:]</span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">+</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">):(</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75">.</span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">+?</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\.</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">git</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">$</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> domain</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> match</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">];</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> path</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> match</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">];</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> `https://</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">domain</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">path</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    catch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">e</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>获取提交记录并以 GitCommit 数组形式返回。首先获取每个记录时需要完整哈希，用于拼接 Gitea Commit 超链接。</p>
<p>其次由于每条提交记录需要有详细描述，因此在分割时就不能简单地把换行符作为分隔符了，这里参考了官方 git 插件的做法。</p>
<p>然后详细描述在记录的时候约定使用 Markdown 形式的无序或有序列表，这里为了方便组件渲染，将它拆解为字符串数组。需要注意的是详细描述可能为空字符串，这时的 <code>description</code> 字段应该是空数组，而不是 <code>[&quot;&quot;]</code> 。</p>
<p>最后根据提交描述约定，比如 “Posts: 2025-04-09 14:03:58” 、“Feature: 关于页添加版本历史” ，使用 &quot;: &quot; 分隔符拆解出提交类型和描述信息即可。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">execSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'child_process'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> SPLIT_CHAR</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '[GIT_LOG_COMMIT_END]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> RE_SPLIT</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75"> /</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\[</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">GIT_LOG_COMMIT_END</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\]</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">$</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRepoHistory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[] {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    try</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> logs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> execSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">            `git log --pretty=format:"%H|%an|%ad|%s|%b</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">SPLIT_CHAR</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">" --date=short`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            {</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">encoding</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'utf-8'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        );</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> logs</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">replace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">RE_SPLIT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">''</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">${</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">SPLIT_CHAR</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD">}</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">line</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> =></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                const</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">hash</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">author</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> line</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'|'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> description</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> content</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ===</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> ?</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [] </span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">trim</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                const</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">tag</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">short</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">split</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">': '</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">hash</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">author</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">message</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">tag</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">short</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}};</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            });</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">catch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [];</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>过滤掉文章记录以及写到文件。根据前面提到的「描述约定」，只需要筛选出非 Posts 提交类型即可。</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fs</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'fs'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> filterPosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">commits</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[])</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitCommit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[] {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> commits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">filter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">commit</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> =></span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> commit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">tag</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">?.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toLowerCase</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">!==</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'posts'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> prepareGitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">sourceDir</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">outputFilePath</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> gitData</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GitData</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">repoUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRemoteRepoUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"origin"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">), </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">commits</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> filterPosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRepoHistory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) };</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">    fs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">writeFileSync</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">outputFilePath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B">JSON</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stringify</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">gitData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后得到的 json 数据大致如下：</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "repoUrl"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"https://gitea.mtfh.cc/mtfhx/vuepress-starter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "commits"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "hash"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"b153bf72c70712a856d2a32983d6b3069c770394"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "author"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Happilys"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "date"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"2024-11-12"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "message"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "tag"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Update"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "short"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"删除一些不需要的内容"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        "description"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          "1. 删除示例文件"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          "2. 删除一些不需要的注释"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          "3. 取消导航栏自动隐藏功能"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          "4. 先不使用搜索功能"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>至此这个插件所需要的功能基本完成。</p>
<h2>问题和需要完善的内容</h2>
<ol>
<li>是否将插件独立出来 ？</li>
</ol>
<p>过去我理解的是一个完整的作品，必然需要成体系化的打包出来。比如说这个插件，我就应该将它作为一个独立的插件打包成一个 npm 包，后续迭代都基于这个 npm 包。但工作几年后，有很多软件、作品并不能走完完整的生命周期。有时候一个工具可能只是单纯的几行代码，想起来的时候就拷贝过来，后来随着需求和技术的迭代也许再也用不上了。这种情况下，去把这些代码打包成插件或软件，便显得非常多余了。</p>
<p>因此在这件事上，我会改变过去的思维习惯，以需求为核心——关注功能本身吧。至于需不需要打包出来，如果这玩意真的能够被大多数人所认可，再打包也不迟。</p>
<ol start="2">
<li>将来需要完善的内容 ？</li>
</ol>
<p>首先 UI 这块不满意。本来计划等到 UI 完善之后再发布，但是不论是 UI 设计和实现上自身能力都不能达到自己所期望的样子，如果任何事情都等到自己达到相应的能力再去执行的话，就会失去很多机会——最现实的问题就是时间线会无限拉长，最后导致计划「难产」。索性就以当下的状态展示出来，给自己时间学习然后再去改进。</p>
<p>当下历史记录并不多，因此随手划一下就到底了，也就不需要分页或懒加载的方式来应对数据问题。但是将来修改的记录变多，不管是 UI 交互上还是背后的数据处理上都需要做相应的改进。像翻最早的记录要划啦半天，加载数据需要耗费数秒时间这些事是不能够的。数据的积累可能需要半年至一年的时间吧，所这件事不急。</p>
<ol start="3">
<li>插件 UI 语言问题</li>
</ol>
<p>开头的 「Version History」看起来挺扎眼的。博客的默认语言是中文，但总有许多英文出现的地方。一些地方是固定内容不作翻译，但还有很多地方其实是需要翻译为中文的。对了，博客目前没做国际化支持，所以语言方面都挺随意的。后续不管是插件还是博客本身，需要对语言进行统一管理，至少支持中英两种语言支持。</p>
<h2>参考</h2>
<ol>
<li><a href="https://github.com/mqyqingfeng/Blog/issues/250" target="_blank" rel="noopener noreferrer">从零实现一个 VuePress 插件 · Issue #250 · mqyqingfeng/Blog</a></li>
<li><a href="https://vuepress.vuejs.org/zh/advanced/plugin.html" target="_blank" rel="noopener noreferrer">开发插件 | VuePress</a></li>
</ol>
]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-05-31T12:42:47.000Z</published>
  </entry>
  <entry>
    <title type="text">Anki 同步服务部署 (Rocky 9)</title>
    <id>https://blog.mtfh.cc/posts/Anki%20%E5%90%8C%E6%AD%A5%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2%20(Rocky%209).html</id>
    <link href="https://blog.mtfh.cc/posts/Anki%20%E5%90%8C%E6%AD%A5%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2%20(Rocky%209).html"/>
    <updated>2025-04-09T06:03:58.000Z</updated>
    <summary type="html"><![CDATA[
<p>「Anki 是一款让记忆变得轻而易举的软件。与传统学习方式相比，它的效率要高得多，你可以大幅缩短学习时间， 或者显著扩充学习量。」—— 选自 Anki 中文文档，更多内容参考<a href="https://open-spaced-repetition.github.io/anki-manual-zh-CN/background.html" target="_blank" rel="noopener noreferrer">这里</a>。</p>
<p>考虑到将来学习任务依赖该软件来辅助记忆。PC 端制作卡片，移动端学习，如果仅仅依靠手动导出再导入的话就比较麻烦了。因此选择部署同步服务来完成任务，应用启动就会自动同步数据，可以节省很多时间。</p>]]></summary>
    <content type="html"><![CDATA[
<p>「Anki 是一款让记忆变得轻而易举的软件。与传统学习方式相比，它的效率要高得多，你可以大幅缩短学习时间， 或者显著扩充学习量。」—— 选自 Anki 中文文档，更多内容参考<a href="https://open-spaced-repetition.github.io/anki-manual-zh-CN/background.html" target="_blank" rel="noopener noreferrer">这里</a>。</p>
<p>考虑到将来学习任务依赖该软件来辅助记忆。PC 端制作卡片，移动端学习，如果仅仅依靠手动导出再导入的话就比较麻烦了。因此选择部署同步服务来完成任务，应用启动就会自动同步数据，可以节省很多时间。</p>
<p>Anki 官方免费的同步服务 Anki Web，不过有时间限制——在一段时间不使用的话，就会清除数据。以下为服务部署大致流程：</p>
<ol>
<li>下载 Anki 客户端和安装 zstd 解压软件</li>
<li>安装 Anki 并解决依赖问题</li>
<li>启动同步服务并使用客户端登陆</li>
<li>配置 Systemd 启动脚本</li>
<li>配置 Ningx 反向代理</li>
</ol>
<h2>下载 Anki 客户端和安装 zstd 解压软件</h2>
<p>根据官方文档描述，Anki 的同步服务和客户端绑定在一起的，拥有 PC 客户单即可使用同步服务。下载 Anki Linux 平台客户端，Anki 最新版本 2.25.04，但是对 GLIBC 版本要求比较高，Rocky 9 的 GLIBC 版本为 2.34</p>
<figure><img src="https://images.mtfh.cc/2025/04/09-ee13d1628bd662fc5009bb27fb2857dd.png" alt="compare-glibc-version" tabindex="0" loading="lazy"><figcaption>compare-glibc-version</figcaption></figure>
<p>所以这里选择倒退一个次版本号使用 2.24.11，Linux 平台有 Qt5 和 Qt6 两个版本，都可以。安装包默认使用 zstd 压缩的，因此需要先安装 zstd ，没有 tar 命令的话也要安装 tar</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span># 安装 zstd 和 tar</span></span>
<span class="line"><span>sudo dnf install -y zstd tar</span></span>
<span class="line"><span></span></span>
<span class="line"><span># 解压软件包</span></span>
<span class="line"><span>tar -Izstd -xvf anki-24.11-linux-qt5.tar.zst</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>安装 Anki 并解决依赖问题</h2>
<p>直接运行 Anki 会出现缺少库的问题，安装 X11 Server 即可。Linux 下的桌面环境一般是由 <a href="https://zh.wikipedia.org/wiki/X%E8%A6%96%E7%AA%97%E7%B3%BB%E7%B5%B1" target="_blank" rel="noopener noreferrer">X11</a> 提供的（除此之外还有 Wyland），而部署的服务镜像一般不带桌面环境。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>sudo dnf install -y xorg-x11-server-Xorg</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><blockquote>
<figure><img src="https://images.mtfh.cc/2025/04/09-4e3fd836ff53c4bb3f8f10b94e78d232.png" alt="anki-missing-libgl-dependency" tabindex="0" loading="lazy"><figcaption>anki-missing-libgl-dependency</figcaption></figure>
<p>如果参考上面的报错，开始想到的是缺少 libGL ， 安装 <code>mesa-libGL</code> 就可以搞定。但是再次执行依然缺库，看到缺少的库名反倒是提醒了我。</p>
<figure><img src="https://images.mtfh.cc/2025/04/09-6e41972d8faeb8587f97832213a522e0.png" alt="anki-missing-libXcomposite-dependency" tabindex="0" loading="lazy"><figcaption>anki-missing-libXcomposite-dependency</figcaption></figure>
<p>当我再次在虚拟机中测试的时候，缺少的库可能不只有这两个，所以<strong>实际部署的时候还需要根据实际情况来判断</strong></p>
<p>想把同步服务给拿出来，就不用安装 X11 Server 了。</p>
</blockquote>
<p>执行解压包内自带的安装脚本，默认安装路径在 /usr/local/share，可执行文件在 /usr/local/bin</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>sudo ./install.sh</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>启动同步服务并使用客户端登陆</h2>
<p>官方文档中 Linux 平台服务启动方式如下：</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>SYNC_USER1=user:pass anki --syncserver</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p><code>SYNC_USER1</code> 变量用来设置登录的用户名和密码，增加用户可以设置 <code>SYNC_USER2</code> 、<code>SYNC_USER3</code> 等以此类推。<code>SYNC_HOST</code> 和 <code>SYNC_PORT</code> 可以设置监听主机 IP 和端口，默认 0.0.0.0:8080 。</p>
<p>客户端登录设置</p>
<figure><img src="https://images.mtfh.cc/2025/04/09-cf99de54d03036eb0df00307ebd60578.png" alt="login-sync-server-step-1-5" tabindex="0" loading="lazy"><figcaption>login-sync-server-step-1-5</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/04/09-3fafc3ded4fff74bada13fdf43b70211.png" alt="login-sync-server-step-6-8" tabindex="0" loading="lazy"><figcaption>login-sync-server-step-6-8</figcaption></figure>
<p>登录成功同步服务会输出如下内容</p>
<figure><img src="https://images.mtfh.cc/2025/04/09-ed432cc9ab9b77ce7490e8b5d579e407.png" alt="anki-sync-server-log" tabindex="0" loading="lazy"><figcaption>anki-sync-server-log</figcaption></figure>
<h2>配置 Systemd 启动脚本</h2>
<p>方便服务管理，给 Anki 写了 Systemd 配置，参考如下：</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>Description=Anki Sync Server</span></span>
<span class="line"><span>After=syslog.target</span></span>
<span class="line"><span>After=network.target</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Service]</span></span>
<span class="line"><span>RestartSec=2s</span></span>
<span class="line"><span>Type=simple</span></span>
<span class="line"><span>User=anki</span></span>
<span class="line"><span>Group=anki</span></span>
<span class="line"><span>WorkingDirectory=/home/anki</span></span>
<span class="line"><span>ExecStart=/usr/local/bin/anki --syncserver</span></span>
<span class="line"><span>Restart=always</span></span>
<span class="line"><span>Environment=SYNC_USER1=username:password SYNC_HOST=127.0.0.1 SYNC_PORT=8081</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Install]</span></span>
<span class="line"><span>WantedBy=multi-user.target</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建好文件，放到 /etc/systemd/system 目录下即可。由于配置里的执行用户和组使用的是 anki 用户，因此启动前需要添加 anki 用户</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 创建用户及家目录（创建用户时默认创建同名用户组）</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">useradd</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -m</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>服务相关的控制命令</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 启动服务</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 服务状态</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> status</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 停止服务</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> stop</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 激活/禁用开机自启</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> enable</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">systemctl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> disable</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 查看服务日志</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">journalctl</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -u</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> anki</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>配置 Ningx 反向代理</h2>
<p>站点内的所有服务都是通过 Ningx 代理出去的，这样可以做到将一个顶级域名下的各二级域名映射到不同的服务上去，不同的服务由端口区分。比如 <a href="https://gitea.mtfh.cc" target="_blank" rel="noopener noreferrer">gitea.mtfh.cc</a> 对应的就是 Gitea 服务，<a href="https://blog.mtfh.cc" target="_blank" rel="noopener noreferrer">blog.mtfh.cc</a> 对应的就是当前博客。另外还可以通过 Nginx 来支持 https 协议。</p>
<p>Nginx 配置参考</p>
<div class="language-nginx line-numbers-mode" data-highlighter="shiki" data-ext="nginx" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-nginx"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">      80</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> anki.mtfh.cc;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 301</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> https://$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">server_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">request_uri</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">server</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        listen </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">443</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ssl;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        server_name </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">anki.mtfh.cc;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_certificate </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/etc/nginx/certificates/anki.mtfh.cc.pem;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_certificate_key </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">/etc/nginx/certificates/anki.mtfh.cc.key;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_session_timeout </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 5m</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_ciphers </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_protocols </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">TLSv1 TLSv1.1 TLSv1.2;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        ssl_prefer_server_ciphers </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        location</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> / {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_pass </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">http://127.0.0.1:8081;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Host $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Real-IP $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Forwarded-For $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">                proxy_set_header </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">X-Forwarded-Proto $</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">scheme</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>SSL 证书可以去购买或者去某某云白嫖吧，Couldflare 也有。设置完成后客户端中的自托管服务设置修改为对应的域名即可。</p>
<h2>参考</h2>
<ol>
<li><a href="https://open-spaced-repetition.github.io/anki-manual-zh-CN/sync-server.html" target="_blank" rel="noopener noreferrer">同步服务器 - Anki 手册</a></li>
</ol>
]]></content>
    <published>2025-04-09T06:03:58.000Z</published>
  </entry>
  <entry>
    <title type="text">使用 Yarn 为软件打补丁</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/%E4%BD%BF%E7%94%A8%20Yarn%20%E4%B8%BA%E8%BD%AF%E4%BB%B6%E6%89%93%E8%A1%A5%E4%B8%81.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/%E4%BD%BF%E7%94%A8%20Yarn%20%E4%B8%BA%E8%BD%AF%E4%BB%B6%E6%89%93%E8%A1%A5%E4%B8%81.html"/>
    <updated>2025-07-31T13:36:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>上一篇提到 plugin-git 获取 git 子模块信息时，不能正确获取时间问题。于是根据源码对 plugin-git 插件进行一番修改之后，成功获取文章的修改时间信息。在此对 yarn 补丁功能的使用做些记录。</p>
<h2>Yarn 补丁相关的命令</h2>
<p>为某个软件包创建补丁，支持二级包名，比如  <code>@vuepress/plugin-git</code> 。它会提取软件包中的文件到临时目录，用户在临时目录修改之后，使用 patch-commit 子命令提交修改。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch &#x3C;package></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<p>上一篇提到 plugin-git 获取 git 子模块信息时，不能正确获取时间问题。于是根据源码对 plugin-git 插件进行一番修改之后，成功获取文章的修改时间信息。在此对 yarn 补丁功能的使用做些记录。</p>
<h2>Yarn 补丁相关的命令</h2>
<p>为某个软件包创建补丁，支持二级包名，比如  <code>@vuepress/plugin-git</code> 。它会提取软件包中的文件到临时目录，用户在临时目录修改之后，使用 patch-commit 子命令提交修改。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch &#x3C;package></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>生成 patch 文件并注册到 package.json 文件当中，<code>path</code> 参数为上条命令创建的临时目录。除了 package.json ，<strong>生成的补丁文件也需要提交到版本仓库</strong>。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch-commit -s &#x3C;path></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>更新补丁，该命令不同于第一条命令（不带 -u 参数），生成临时目录中的文件会根据已有的补丁文件作出相应的修改。而不带 -u 参数生成的临时目录中的文件则是软件包原本的样子，即使 package.json 包含补丁信息。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch -u &#x3C;package></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>使用 yarn 为 plugin-git 打补丁</h2>
<p>这是一个具体的打补丁的例子，其他软件包可以参考这个方法。相关步骤如下：</p>
<ol>
<li>修改代码，测试</li>
<li>准备软件包</li>
<li>提交修改</li>
<li>更新补丁
<ol>
<li>基于已有内容修改</li>
<li>重新创建</li>
</ol>
</li>
</ol>
<p>第一步，修改代码只需要在 node_modules 目录找到 plugin-git 的软件包目录，然后修改其中的文件即可。这些修改能够直接生效，然后调试、修改、再调试……</p>
<figure><img src="https://images.mtfh.cc/2025/04/01-ec6c334f937492a219cb712d16848c28.png" alt="modify-plugin-git-source-code" tabindex="0" loading="lazy"><figcaption>modify-plugin-git-source-code</figcaption></figure>
<p>第二步，准备软件包——执行下面的命令，会把软件包放到临时目录下，软件包中的内容不会有任何修改，就是它原本的样子。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch @vuepress/plugin-git</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/04/01-262b68f5b3fadcad94b4f272b6f9201d.png" alt="yarn-patch-stdout" tabindex="0" loading="lazy"><figcaption>yarn-patch-stdout</figcaption></figure>
<figure><img src="https://images.mtfh.cc/2025/04/01-e3ac8ab00b106f953fb740d832becad0.png" alt="temp-directory-source-code" tabindex="0" loading="lazy"><figcaption>temp-directory-source-code</figcaption></figure>
<p>第三步，提交修改——把 node_modules 中修改的文件替换到刚才生成的文件下，就可以执行下面的命令——根据临时目录中所做的修改生成 patch 文件到 .yarn/patches 目录下；在 package.json 中记录修改，当执行 <code>yarn install</code> 时会根据 package.json 中的记录自动修改文件。最后应用补丁（只不过临时目录的修改也是从 node_modules 中拷贝过去的——不会发生变化罢了）。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 拷贝文件</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">cp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> node_modules/@vuepress/plugin-git/lib/node/utils/getUpdatedTime.js</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /private/var/folders/tf/vp9nb9m13ys_c96f3jkkdgx40000gn/T/xfs-7d8e5614/user/lib/node/utils/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 提交更新</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> patch-commit</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -s</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /private/var/folders/tf/vp9nb9m13ys_c96f3jkkdgx40000gn/T/xfs-7d8e5614/user</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 应用修改</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://images.mtfh.cc/2025/04/01-129c0c66409eed2c935188eb87ed8dad.png" alt="patchfile-and-package-dot-json" tabindex="0" loading="lazy"><figcaption>patchfile-and-package-dot-json</figcaption></figure>
<p>第四步，更新补丁有两种方式，第一种是在已有的修改上再进行修改；第二种则是无视已经做过的修改，直接基于原有的内容修改。</p>
<p>先讨论第一种方式。在完成第三步之后，如果需要改进前面的内容，可以执行以下命令。生成的临时目录中的文件内容是根据前面操作修改过的内容。</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>yarn patch -u @vuepress/plugin-git</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>同上面步骤提交之后，package.json 会更新原来记录和生成一个新的补丁文件。</p>
<figure><img src="https://images.mtfh.cc/2025/04/01-5bf15f9ac75e91395a690dd75720fa65.png" alt="patch-update" tabindex="0" loading="lazy"><figcaption>patch-update</figcaption></figure>
<p>第二种方式和软件初次打补丁方式相同，但是在提交更新前需要清除现有的补丁（<strong>记得备份 node_modules 中修改过的文件</strong>），否则部署时就会报错。</p>
<figure><img src="https://images.mtfh.cc/2025/04/01-5e3ac6b44d4a53712a02281295490e91.png" alt="yarn-apply-patch-error" tabindex="0" loading="lazy"><figcaption>yarn-apply-patch-error</figcaption></figure>
<p>删除掉 package.json 文件中 <code>resolutions</code> 字段对应的内容，再执行 <code>yarn install</code> 即可清除补丁，另外 .yarn 目录中的补丁也应该同步删除。</p>
<h2>参考</h2>
<ol>
<li><a href="https://yarnpkg.com/cli/patch" target="_blank" rel="noopener noreferrer">yarn patch | Yarn</a></li>
</ol>
]]></content>
    <category term="前端"/>
    <published>2025-04-01T06:56:56.000Z</published>
  </entry>
  <entry>
    <title type="text">Vupress 自动部署的时间问题</title>
    <id>https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vupress%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%AE%E9%A2%98.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%89%8D%E7%AB%AF/Vue/Vupress%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%AE%E9%A2%98.html"/>
    <updated>2025-07-31T13:36:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>使用 Gitea Actions 来自动化部署 Vuepress 二月份已经整理出来，但部署的时候一直存在文章修改时间在同一天的问题。</p>
<figure><img src="https://images.mtfh.cc/2025/03/20-e768c2ffbe9b8c3ab0a1352a835a799d.png" alt="time-issue-on-git-shallow-copy" tabindex="0" loading="lazy"><figcaption>time-issue-on-git-shallow-copy</figcaption></figure>
<p>另外 Vuepress 中的 plugin-git 插件是不能够获取 git 子模块中的文件修改时间，只能自己查看源码，打补丁。Vuepress 初始设计也许就没有计划给文档的内容独立出一个（私有）版本仓库，可是我希望将博客的主体框架和内容分开，于是只好自己实现了。第一版修改的内容如下</p>]]></summary>
    <content type="html"><![CDATA[
<p>使用 Gitea Actions 来自动化部署 Vuepress 二月份已经整理出来，但部署的时候一直存在文章修改时间在同一天的问题。</p>
<figure><img src="https://images.mtfh.cc/2025/03/20-e768c2ffbe9b8c3ab0a1352a835a799d.png" alt="time-issue-on-git-shallow-copy" tabindex="0" loading="lazy"><figcaption>time-issue-on-git-shallow-copy</figcaption></figure>
<p>另外 Vuepress 中的 plugin-git 插件是不能够获取 git 子模块中的文件修改时间，只能自己查看源码，打补丁。Vuepress 初始设计也许就没有计划给文档的内容独立出一个（私有）版本仓库，可是我希望将博客的主体框架和内容分开，于是只好自己实现了。第一版修改的内容如下</p>
<div class="language-javascript line-numbers-mode" data-highlighter="shiki" data-ext="javascript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-javascript"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">diff</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> --</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">git</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> a</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">lib</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">node</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">utils</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">getCommits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">js</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> b</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">lib</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">node</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">utils</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">getCommits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">js</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">index</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> 18</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">e0f5602df72dec54020f4847d34ace1c309e0d</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">..1</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">c19ce921ad2d127213c78a499a5e8d89728bcb3</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 100644</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content>
    <category term="前端"/>
    <category term="Vue"/>
    <published>2025-03-20T08:21:11.000Z</published>
  </entry>
  <entry>
    <title type="text">Vuepress 自动化部署（基于 Gitea Actions）</title>
    <id>https://blog.mtfh.cc/posts/Vuepress%20%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2%EF%BC%88%E5%9F%BA%E4%BA%8E%20Gitea%20Actions%EF%BC%89.html</id>
    <link href="https://blog.mtfh.cc/posts/Vuepress%20%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2%EF%BC%88%E5%9F%BA%E4%BA%8E%20Gitea%20Actions%EF%BC%89.html"/>
    <updated>2026-03-01T09:27:51.000Z</updated>
    <summary type="html"><![CDATA[
<ul>
<li>环境准备</li>
<li>注册 Gitea Runner</li>
<li>创建 Gitea Actions</li>
</ul>
<p>操作系统 Rocky Linux 9</p>
<h2>环境准备</h2>
<p>安装 Git、 Node 环境和 yarn4+</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> module</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nodejs:22</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -g</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> corepack</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">corepack</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> enable</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> init</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -2</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<ul>
<li>环境准备</li>
<li>注册 Gitea Runner</li>
<li>创建 Gitea Actions</li>
</ul>
<p>操作系统 Rocky Linux 9</p>
<h2>环境准备</h2>
<p>安装 Git、 Node 环境和 yarn4+</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> git</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> module</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nodejs:22</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -g</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> corepack</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">corepack</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> enable</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> init</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -2</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>npm 默认全局安装位置没权限，但也不建议使用 <code>sudo</code> 命令（待解决）</p>
</blockquote>
<p><code>yarn init -2</code> 初始化失败，可以采取<a href="https://docs.gitea.com/zh-cn/usage/actions/overview" target="_blank" rel="noopener noreferrer">离线安装</a></p>
<p>下载 <a href="https://gitea.com/gitea/act_runner/releases" target="_blank" rel="noopener noreferrer">Act Runner</a> 二进制包</p>
<h2>注册 Act Runner</h2>
<p>获取注册令牌</p>
<figure><img src="https://images.mtfh.cc/gitea-act-runner-registration-token.png" alt="gitea-act-runner-registration-token" tabindex="0" loading="lazy"><figcaption>gitea-act-runner-registration-token</figcaption></figure>
<p>Act Runner 一共有三个注册级别：实例级别、组织级别、存储库级别。这里用存储库级别，因为这个 Act Runner 当前仅用于 Vuepress 的构建和部署任务。不同注册级别区别如下：</p>
<ul>
<li>实例级别：Runner将为实例中的所有存储库运行Job。</li>
<li>组织级别：Runner将为组织中的所有存储库运行Job。</li>
<li>存储库级别：Runner将为其所属的存储库运行Job。</li>
</ul>
<p>创建配置文件，注册 Runner 并运行</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 生成配置</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">./act_runner</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> generate-config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> > </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">config.yaml</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 注册 Runner</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">./act_runner</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> register</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --no-interactive</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --instance</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://gitea.mtfh.cc</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --token</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> XXXxxxXXx</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --name</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> vuepress-builder</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config.yaml</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 启动 Runner </span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">./act_runner</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> daemon</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config.yaml</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>使用 systemd 管理 Act Runner， 配置示例</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>[Unit]</span></span>
<span class="line"><span>Description=Gitea Act Runner</span></span>
<span class="line"><span>After=syslog.target</span></span>
<span class="line"><span>After=network.target</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Service]</span></span>
<span class="line"><span>RestartSec=2s</span></span>
<span class="line"><span>Type=simple</span></span>
<span class="line"><span>User=happilys</span></span>
<span class="line"><span>Group=happilys</span></span>
<span class="line"><span>WorkingDirectory=/home/happilys</span></span>
<span class="line"><span>ExecStart=/home/happilys/act_runner daemon --config /home/happilys/config.yaml</span></span>
<span class="line"><span>Restart=always</span></span>
<span class="line"><span></span></span>
<span class="line"><span>[Install]</span></span>
<span class="line"><span>WantedBy=multi-user.target</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>创建 Gitea Actions</h3>
<p>创建 <code>.gitea/workflow/xxx.ymal</code> 文件（Gitea 从该文件中获取任务，Action 语法同 Github Actions）如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Build docs automatically</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ gitea.actor }} 🚀</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    branches</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  Build-Docs-and-Deploy-It</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">linux-amd64</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Check out repository code</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">https://gitea.com/actions/checkout@v4</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${{ secrets.DEPLOYKEY }}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          ssh-known-hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ssh-keyscan gitea.mtfh.cc</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          submodules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">yarn install</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">yarn docs:build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">cp -r ./src/.vuepress/dist/* /var/www/html</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p><code>actions/checkout@v4</code> 由于某些原因不能直接从 Github 获取，根据官方文档使用 Gitea 官方仓库</p>
</blockquote>
<p>关于脚本中 <code>secrets.DEPLOYKEY</code> 变量的配置如下：</p>
<ol>
<li>生成 ssh 密钥对，<code>ssh-keygen</code> 一路回车</li>
</ol>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ssh-keygen</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -t</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ed25519</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -C</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "deploy@mtfh.cc"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -f</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  ~/Downloads/id_ed25519</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p><img src="https://images.mtfh.cc/gitea-deploy-ssh-key-gen.png" alt="gitea-deploy-ssh-key-gen" loading="lazy">
2. Actions 密钥管理添加密钥，添加上一步生成的私钥（文件名为 id_rsa）</p>
<figure><img src="https://images.mtfh.cc/2025/02/10-cfbda933fc7e56e680a5e815ce98fa71.png" alt="gitea-actions-add-secrets" tabindex="0" loading="lazy"><figcaption>gitea-actions-add-secrets</figcaption></figure>
<ol start="3">
<li>
<p>为需要访问的仓库添加部署密钥</p>
<figure><img src="https://images.mtfh.cc/gitea-repository-add-deploy-key.png" alt="gitea-repository-add-deploy-key" tabindex="0" loading="lazy"><figcaption>gitea-repository-add-deploy-key</figcaption></figure>
<p><code>action/checkout@v4</code> 如果配置 ssh-key 默认走 git 协议，所以虽然 vuepress-starter 是公开仓库，也许要添加部署密钥</p>
<figure><img src="https://images.mtfh.cc/ blog-repositories.png" alt="blog-repositories" tabindex="0" loading="lazy"><figcaption>blog-repositories</figcaption></figure>
</li>
</ol>
<p>提交并推送</p>
<figure><img src="https://images.mtfh.cc/2025/02/10-c0989361694c2500010e4e67d9c27e95.png" alt="10-c0989361694c2500010e4e67d9c27e95.png" tabindex="0" loading="lazy"><figcaption>10-c0989361694c2500010e4e67d9c27e95.png</figcaption></figure>
<h2>参考</h2>
<ol>
<li><a href="https://yarnpkg.com/getting-started/install" target="_blank" rel="noopener noreferrer">Installation | Yarn</a></li>
<li><a href="https://github.com/nodejs/corepack?tab=readme-ov-file#offline-workflow" target="_blank" rel="noopener noreferrer">GitHub - nodejs/corepack: Zero-runtime-dependency package acting as bridge between Node projects and their package managers</a></li>
<li><a href="https://docs.gitea.com/zh-cn/usage/actions/overview" target="_blank" rel="noopener noreferrer">Overview | Gitea Documentation</a></li>
<li><a href="https://github.com/actions/checkout" target="_blank" rel="noopener noreferrer">GitHub - actions/checkout: Action for checking out a repo</a></li>
</ol>
]]></content>
    <published>2025-03-15T13:21:43.000Z</published>
  </entry>
  <entry>
    <title type="text">关于 PicGo + Typora 图片自动上传设置</title>
    <id>https://blog.mtfh.cc/posts/%E5%85%B3%E4%BA%8E%20PicGo%20_%20Typora%20%E5%9B%BE%E7%89%87%E8%87%AA%E5%8A%A8%E4%B8%8A%E4%BC%A0%E8%AE%BE%E7%BD%AE.html</id>
    <link href="https://blog.mtfh.cc/posts/%E5%85%B3%E4%BA%8E%20PicGo%20_%20Typora%20%E5%9B%BE%E7%89%87%E8%87%AA%E5%8A%A8%E4%B8%8A%E4%BC%A0%E8%AE%BE%E7%BD%AE.html"/>
    <updated>2025-02-14T13:23:16.000Z</updated>
    <summary type="html"><![CDATA[
<h2>PicGo-Core 和一些插件安装配置</h2>
<p>准备安装 PicGo-Core 需要 Node 环境，使用 NVM 或二进制安装包</p>
<p>使用 npm 全局安装</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>npm install -g picgo</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<h2>PicGo-Core 和一些插件安装配置</h2>
<p>准备安装 PicGo-Core 需要 Node 环境，使用 NVM 或二进制安装包</p>
<p>使用 npm 全局安装</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>npm install -g picgo</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>安装 rename-file 插件</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>picgo install rename-file</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>添加图床设置，以七牛云为例</p>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>picgo set uploader</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>根据命令行提示，完成后配置文件大致如下；插件相关配置需要手动编辑，内容同下。<code>picgo-plugin-rename-file</code> 配置<a href="https://github.com/liuwave/picgo-plugin-rename-file?tab=readme-ov-file#picgo-plugin-rename-file" target="_blank" rel="noopener noreferrer">参考</a></p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "picBed"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "uploader"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"qiniu"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "current"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"qiniu"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "qiniu"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "accessKey"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"xxxxx-xxxxxxx-xxxxx-xxxx-xxx-xxxx-xxx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "secretKey"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"xxxxx-xxxxxxx-xxxxx-xxxx-xxx-xxxx-xxx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "bucket"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"notes-album"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"https://images.mtfh.cc"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "area"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"cn-east-2"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "options"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "path"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">""</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "picgoPlugins"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "picgo-plugin-rename-file"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "picgo-plugin-rename-file"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "format"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"{y}/{m}/{d}-{hash}"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>七牛云 AccessKey 和 SecretKey 获取——前往个人中心-密钥管理，新增密钥</p>
<figure><img src="https://images.mtfh.cc/2025/02/10-41d5158c821c899c9e5c4876f3a27d6e.png" alt="10-41d5158c821c899c9e5c4876f3a27d6e" tabindex="0" loading="lazy"><figcaption>10-41d5158c821c899c9e5c4876f3a27d6e</figcaption></figure>
<p><code>bucket</code> 即对象存储空间名称；<code>url</code> 为资源访问地址，CDN 加速域名和源站域名都可以，这里用源站域名因为 CDN 域名配置 https 要钱 🤣</p>
<figure><img src="https://images.mtfh.cc/2025/02/10-021feee7e0e78ddf01952d474677273b.png" alt="10-021feee7e0e78ddf01952d474677273b" tabindex="0" loading="lazy"><figcaption>10-021feee7e0e78ddf01952d474677273b</figcaption></figure>
<p><code>area</code> 存储空间区域代码，更多内容请参考<a href="https://developer.qiniu.com/kodo/1671/region-endpoint-fq" target="_blank" rel="noopener noreferrer">存储区域_产品简介_对象存储 - 七牛开发者中心</a></p>
<p>| 存储区域         | 区域 Region ID   |
|</p>
]]></content>
    <published>2025-02-14T13:23:16.000Z</published>
  </entry>
  <entry>
    <title type="text">Windows 远程桌面</title>
    <id>https://blog.mtfh.cc/posts/Windows%20%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2.html</id>
    <link href="https://blog.mtfh.cc/posts/Windows%20%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2.html"/>
    <updated>2024-12-28T14:16:25.000Z</updated>
    <summary type="html"><![CDATA[
<ol>
<li>服务端
<ol>
<li>Win10 专业版</li>
<li>Win10 家庭版</li>
</ol>
</li>
<li>客户端
<ol>
<li>Win10</li>
<li>Mac</li>
</ol>
</li>
</ol>
<h3>Win10远程桌面服务</h3>
<p>Win10专业版才有远程桌面，直接到设置里开启就可以：<img src="https://images.mtfh.cc/win10-settings-remote-desktop.png" alt="win10-settings-remote-desktop" loading="lazy"></p>]]></summary>
    <content type="html"><![CDATA[
<ol>
<li>服务端
<ol>
<li>Win10 专业版</li>
<li>Win10 家庭版</li>
</ol>
</li>
<li>客户端
<ol>
<li>Win10</li>
<li>Mac</li>
</ol>
</li>
</ol>
<h3>Win10远程桌面服务</h3>
<p>Win10专业版才有远程桌面，直接到设置里开启就可以：<img src="https://images.mtfh.cc/win10-settings-remote-desktop.png" alt="win10-settings-remote-desktop" loading="lazy"></p>
<p><strong>防火墙设置</strong></p>
<p>远程桌面默认3389端口，需要授权之后才能从局域网的另一台主机登录（外网要做端口映射，没固定IP还要动态域名）。</p>
<ol>
<li>Windows安全中心 --&gt; 防火墙和网络保护<img src="https://images.mtfh.cc/win10-defender-firewall.png" alt="win10-defender-firewall" loading="lazy"></li>
<li>允许应用通过防火墙![win10-defender-firewall-remote-desktop](</li>
</ol>
<p>专业版可能是这样：</p>
<ol start="3">
<li>把小框勾选上然后确定，如过已经勾选了不用管了<img src="https://images.mtfh.cc/windows-remote-desktop.png" alt="windows-remote-desktop" loading="lazy"></li>
</ol>
<h3>RDP Wrapper Library</h3>
<p>非专业版就不能在设置里直接打开了。官方描述非专业版并不支持远程桌面，但非专业版是拥有该特性的。因此在非专业版上使用远程桌面的话就需要这个—— <a href="https://github.com/stascorp/rdpwrap" target="_blank" rel="noopener noreferrer">RDP Wrapper Library</a></p>
<ol>
<li>下载最新发布版本，<a href="https://github.com/stascorp/rdpwrap/releases/download/v1.6.2/RDPWrap-v1.6.2.zip" target="_blank" rel="noopener noreferrer">当前</a><img src="https://images.mtfh.cc/github-rdp-wrapper-library-release-page.png" alt="github-rdp-wrapper-library-release-page" loading="lazy"></li>
<li>解压到<code>C:\Program Files\RDP Wrapper</code></li>
<li>以管理员身份执行<code>install.bat</code>，然后执行<code>update.bat</code>，完成之后运行<code>RDPConf.exe</code><img src="https://images.mtfh.cc/rdp-wrapper-configuration.png" alt="rdp-wrapper-configuration" loading="lazy"></li>
<li>出现如图效果表示已经成功，可以运行<code>RDPCheck.exe</code>测试</li>
</ol>
<p><strong>NOTE</strong></p>
<ol>
<li>同专业版一样，允许通过防火墙。</li>
<li>ISSUE1530：<a href="https://github.com/asmtron/rdpwrap#rdp-wrapper--autoupdate" target="_blank" rel="noopener noreferrer">RDP Wrapper &amp; Autoupdate</a></li>
</ol>
<h3>客户端</h3>
<ol>
<li>Win: <code>win</code>+<code>r</code> --&gt; mstsc<img src="https://images.mtfh.cc/windows-remote-desktop-mstsc.png" alt="windows-remote-desktop-mstsc" loading="lazy"></li>
<li>Mac(美版App Store)<img src="https://images.mtfh.cc/apple-appstore-microsoft-remote-desktop.png" alt="apple-appstore-microsoft-remote-desktop" loading="lazy"></li>
</ol>
]]></content>
    <published>2024-12-28T14:16:25.000Z</published>
  </entry>
  <entry>
    <title type="text">Vagrant 通用环境打包 - Ubuntu 20.04 Base</title>
    <id>https://blog.mtfh.cc/posts/Vagrant%20%E9%80%9A%E7%94%A8%E7%8E%AF%E5%A2%83%E6%89%93%E5%8C%85%20-%20Ubuntu%2020.04%20Base.html</id>
    <link href="https://blog.mtfh.cc/posts/Vagrant%20%E9%80%9A%E7%94%A8%E7%8E%AF%E5%A2%83%E6%89%93%E5%8C%85%20-%20Ubuntu%2020.04%20Base.html"/>
    <updated>2024-11-29T01:43:22.000Z</updated>
    <summary type="html"><![CDATA[
<p>准备：</p>
<ul>
<li><a href="https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/20.04.6/ubuntu-20.04.6-desktop-amd64.iso" target="_blank" rel="noopener noreferrer">ubuntu-20.04.6-desktop-amd64.iso</a></li>
<li><a href="https://developer.hashicorp.com/vagrant/downloads" target="_blank" rel="noopener noreferrer">Vagrant</a> 与 <a href="https://www.virtualbox.org/wiki/Downloads" target="_blank" rel="noopener noreferrer">Virtual Box</a> 程序</li>
<li><a href="https://github.com/hashicorp/vagrant/tree/main/keys" target="_blank" rel="noopener noreferrer">insecure keypair</a></li>
</ul>]]></summary>
    <content type="html"><![CDATA[
<p>准备：</p>
<ul>
<li><a href="https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/20.04.6/ubuntu-20.04.6-desktop-amd64.iso" target="_blank" rel="noopener noreferrer">ubuntu-20.04.6-desktop-amd64.iso</a></li>
<li><a href="https://developer.hashicorp.com/vagrant/downloads" target="_blank" rel="noopener noreferrer">Vagrant</a> 与 <a href="https://www.virtualbox.org/wiki/Downloads" target="_blank" rel="noopener noreferrer">Virtual Box</a> 程序</li>
<li><a href="https://github.com/hashicorp/vagrant/tree/main/keys" target="_blank" rel="noopener noreferrer">insecure keypair</a></li>
</ul>
<h3>1. Ubuntu 20.04 安装</h3>
<p><strong>创建虚拟机</strong>：设置虚拟机名称，选择虚拟机存储路径，选择准备好的镜像</p>
<blockquote>
<p>虚拟机名称随意，这里使用的命名规则为 <code>&lt;系统名&gt;-&lt;版本号&gt;-&lt;环境(server/desktop)&gt;-&lt;语言(zh/en)&gt;-prototype</code><br>
虚拟机存储路径保证拥有足够的空间，可以在创建时修改或者修改全局设置</p>
</blockquote>
<figure><img src="http://images.mtfh.cc/packaging-generic-linux-env-01.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p><strong>硬件设置</strong>：内存 <code>2G</code>，核心数分配 2 核即可</p>
<blockquote>
<p>核心数分配单核也可以，不过部分 Linux 系统会出现出现内核 Panic 无法启动的情况</p>
</blockquote>
<figure><img src="http://images.mtfh.cc/packaging-generic-linux-env-02.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p><strong>硬盘设置</strong>：动态分配 <code>50G</code></p>
<blockquote>
<p>50G 的空间主要用来安装应用并不承担用户数据负载，实际的数据在使用过程中直接与宿主机共享<br>
❓Windows 与 Linux 虚拟机文件共享时文件系统不一致问题</p>
</blockquote>
<figure><img src="http://images.mtfh.cc/packaging-generic-linux-env-03.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p>概览：</p>
<figure><img src="http://images.mtfh.cc/packaging-generic-linux-env-04.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p>启动虚拟机进入系统安装界面</p>
]]></content>
    <published>2024-11-12T14:44:49.000Z</published>
  </entry>
  <entry>
    <title type="text">Win 11 或 Win 10 开启 SSH 远程</title>
    <id>https://blog.mtfh.cc/posts/Win%2011%20%E6%88%96%20Win%2010%20%20%E5%BC%80%E5%90%AF%20SSH%20%E8%BF%9C%E7%A8%8B.html</id>
    <link href="https://blog.mtfh.cc/posts/Win%2011%20%E6%88%96%20Win%2010%20%20%E5%BC%80%E5%90%AF%20SSH%20%E8%BF%9C%E7%A8%8B.html"/>
    <updated>2024-11-12T15:11:44.000Z</updated>
    <summary type="html"><![CDATA[
<p>一、以管理员身份运行 <code>Powershell</code> 以获取当前是否安装 SSH 服务</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Get-WindowsCapability</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Online | </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Where-Object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> Name </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-like</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'OpenSSH*'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<p>一、以管理员身份运行 <code>Powershell</code> 以获取当前是否安装 SSH 服务</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Get-WindowsCapability</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Online | </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Where-Object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> Name </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-like</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'OpenSSH*'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>执行可能结果如下：</p>
<figure><img src="http://images.mtfh.cc/win11-ssh-remote-get-windows-capability.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p>二、以管理员身份执行如下命令来安装 SSH 服务</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Install the OpenSSH Server</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Add-WindowsCapability</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Online </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Name OpenSSH.Server~~~~</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0.0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1.0</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>执行结果：</p>
<figure><img src="http://images.mtfh.cc/win11-ssh-remote-add-windows-capability.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p>三、启动 SSH 服务，并开放对应端口</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Start the sshd service</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Start-Service</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> sshd</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Auto start, OPTIONAL but recommended:</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Set-Service</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Name sshd </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">StartupType </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'Automatic'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Confirm the Firewall rule is configured. It should be created automatically by setup. Run the following to verify</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (!(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Get-NetFirewallRule</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Name </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"OpenSSH-Server-In-TCP"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ErrorAction SilentlyContinue | </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Select-Object</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> Name, Enabled)) {</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    Write-Output</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    New-NetFirewallRule</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Name </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'OpenSSH-Server-In-TCP'</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">DisplayName </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'OpenSSH Server (sshd)'</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Enabled True </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Direction Inbound </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Protocol TCP </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Action Allow </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">LocalPort </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">22</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">} </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    Write-Output</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>四、将默认的 <code>CMD</code> 改为 <code>Powershell</code></p>
<blockquote>
<p>!!! Note：此命令需要保证 Powershell 7 已安装并且路径正确或自行替换正确的 Powershell 路径</p>
</blockquote>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">New-ItemProperty</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Path </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HKLM:\SOFTWARE\OpenSSH"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Name DefaultShell </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Value </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"C:\Program Files\PowerShell\7\pwsh.exe"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">PropertyType String </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Force</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>执行结果：</p>
<figure><img src="http://images.mtfh.cc/win11-ssh-remote-new-item-property.png" alt tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<h3>使用 SSH 密钥登录</h3>
<ol>
<li>生成密钥对</li>
<li>部署公钥</li>
</ol>
<p>管理员用户公钥需放置在服务器上的一个名为 <code>administrators_authorized_keys</code> 的文本文件中，此文件存在于 <code>C:\ProgramData\ssh</code> ， 并且该文件上的 ACL 需要配置为仅允许访问管理员和系统。</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">icacls.exe</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "C:\ProgramData\ssh\administrators_authorized_keys"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> /</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">inheritance:r </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">grant </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Administrators:F"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> /</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">grant </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"SYSTEM:F"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>参考</h2>
<ol>
<li><a href="https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse" target="_blank" rel="noopener noreferrer">安装 OpenSSH</a></li>
<li><a href="https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_server_configuration" target="_blank" rel="noopener noreferrer">适用于 Windows 10 1809 和 Windows Server 2019 的 OpenSSH 服务器配置</a></li>
</ol>
]]></content>
    <published>2024-11-12T14:44:49.000Z</published>
  </entry>
  <entry>
    <title type="text">openEuler 基础环境创建</title>
    <id>https://blog.mtfh.cc/posts/openEuler%20%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83%E5%88%9B%E5%BB%BA.html</id>
    <link href="https://blog.mtfh.cc/posts/openEuler%20%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83%E5%88%9B%E5%BB%BA.html"/>
    <updated>2024-11-16T09:15:56.000Z</updated>
    <summary type="html"><![CDATA[
<h2>环境配置</h2>
<ol>
<li>更换 YUM 源</li>
</ol>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> sed</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^metalink=|#metalink=|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^gpgcheck=1|gpgcheck=0|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^baseurl=http://repo.openeuler.org|baseurl=https://mirrors.nju.edu.cn/openeuler|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -i.bak</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /etc/yum.repos.d/openEuler.repo</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> makecache</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[
<h2>环境配置</h2>
<ol>
<li>更换 YUM 源</li>
</ol>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> sed</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^metalink=|#metalink=|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^gpgcheck=1|gpgcheck=0|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -e</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 's|^baseurl=http://repo.openeuler.org|baseurl=https://mirrors.nju.edu.cn/openeuler|g'</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> \</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         -i.bak</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /etc/yum.repos.d/openEuler.repo</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> makecache</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol>
<li>更新内核</li>
</ol>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> reboot</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>重启后删除老内核</p>
<h2>安装 VirtualBox 增强工具</h2>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> tar</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> make</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gcc</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> gcc-c++</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> kernel-devel</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># VBoxAddition runtime</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dnf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -y</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> libXt</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> libXmu</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>点击 VirtualBox 设备菜单栏中 “安装增强功能”</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> mount</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /dev/cdrom</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /srv</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x26;&#x26; </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /srv</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">sudo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ./VBoxLinuxAdditions.run</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content>
    <published>2024-11-12T14:44:49.000Z</published>
  </entry>
</feed>