<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>Ryan Wang's Blog</title><link>https://ryanc.cc</link><atom:link href="https://ryanc.cc/rss.xml" rel="self" type="application/rss+xml"/><description>Ryan Wang's Blog</description><generator>Halo v2.24.0</generator><language>zh-cn</language><image><url>https://ryanc.cc/upload/avatar-128-2024.jpg</url><title>Ryan Wang's Blog</title><link>https://ryanc.cc</link></image><lastBuildDate>Sat, 11 Apr 2026 04:00:05 GMT</lastBuildDate><follow_challenge><feedId>69290139450912768</feedId><userId>41706424548048896</userId></follow_challenge><item><title><![CDATA[为 Docusaurus 添加 Shiki 代码高亮支持]]></title><link>https://ryanc.cc/archives/docusaurus-shiki</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E4%B8%BA%20Docusaurus%20%E6%B7%BB%E5%8A%A0%20Shiki%20%E4%BB%A3%E7%A0%81%E9%AB%98%E4%BA%AE%E6%94%AF%E6%8C%81&amp;url=/archives/docusaurus-shiki" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">本文记录 <a href="https://docs.halo.run/" target="_blank" rel="">https://docs.halo.run</a> 集成 Shiki 代码高亮的过程。</p>
</blockquote>
<h2 style="" id="%E5%BC%95%E8%A8%80">引言</h2>
<p style="">从 Halo 1.x 开始，我们就一直在使用 Docusaurus 来构建 Halo 的文档。直到 Halo 2.21，我们已经累积了大量的文档，期间发现代码块高亮的问题难以解决。Docusaurus 默认使用 Prism.js 来渲染代码块，且几乎没有其他选择。而我们的文档中使用的一些语言或框架（如 Vue），Prism 并没有提供高亮支持，因此长期以来这些代码块都没有显示语法高亮。即使是 Prism 所支持的语言，渲染出来的语法高亮效果也不尽如人意。</p>
<p style="">直到后来了解到了 <hyperlink-inline-card target="_blank" href="https://shiki.style/" theme="inline"><a href="https://shiki.style/" target="_blank">https://shiki.style/</a></hyperlink-inline-card>，一个较新的代码高亮库，基于与 VSCode 同源的 TextMate 代码高亮引擎，渲染出来的代码效果与 VSCode 一致，并且支持现在主流语言和框架代码的高亮。我认为这几乎是当前各方面最优的代码高亮库。后面我们也为 Halo 开发了 Shiki 代码高亮插件，表现十分良好。因此最近也考虑将 Halo 文档中的代码高亮渲染改为使用 Shiki。好在搜索一番后，已经有了一个广泛讨论的 issue：<hyperlink-inline-card target="_blank" href="https://github.com/facebook/docusaurus/issues/9122" theme="inline" custom-title="Use shiki for code highlight"><a href="https://github.com/facebook/docusaurus/issues/9122" target="_blank">https://github.com/facebook/docusaurus/issues/9122</a></hyperlink-inline-card>，并且里面也有人提供了可行的方案。于是我按照这个方案并添加了一些额外功能，本文将记录实现的完整过程。如果你也在用 Docusaurus，并且对 Shiki 有需求，可以参考本文。</p>
<h2 style="" id="%E8%83%8C%E6%99%AF"><strong>背景</strong></h2>
<ul>
 <li>
  <p style="">目前使用的 Docusaurus 版本是 3.8.0</p>
 </li>
</ul>
<h2 style="" id="%E5%AE%89%E8%A3%85%E4%BE%9D%E8%B5%96"><strong>安装依赖</strong></h2>
<p style="">Docusaurus 的 Markdown 引擎核心为 <hyperlink-inline-card target="_blank" href="https://mdxjs.com/" theme="inline" custom-title="MDX"><a href="https://mdxjs.com/" target="_blank">https://mdxjs.com/</a></hyperlink-inline-card>，而 MDX 内部基于 <a href="https://github.com/remarkjs/remark" target="_blank" rel="">remark</a> 和 <a href="https://github.com/rehypejs/rehype" target="_blank" rel="">rehype</a>，因此 Docusaurus 实际上预留了可以添加 rehype 插件的<a href="https://docusaurus.io/docs/markdown-features/plugins" target="_blank" rel="">配置</a>，所以可以直接使用 Shiki 官方的 <a href="https://shiki.style/packages/rehype" target="_blank" rel="">rehype 插件</a>。</p>
<pre><code class="language-shellscript">pnpm add @shikijs/rehype shiki -D</code></pre>
<h2 style="" id="%E6%B7%BB%E5%8A%A0%E9%85%8D%E7%BD%AE"><strong>添加配置</strong></h2>
<pre><code class="language-javascript">const { bundledLanguages } = require("shiki");
const { default: rehypeShiki } = require("@shikijs/rehype");

/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          // ...
// [!code ++:11]
          beforeDefaultRehypePlugins: [
            [
              rehypeShiki,
              {
                theme: "catppuccin-mocha",
                langs: Object.keys(bundledLanguages),
                // or
                // langs: ['js', 'ts']
              },
            ],
          ],
          // ...
        },
      }),
    ],
  ],
  // ...
};</code></pre>
<p style="">其中，<code>langs</code> 可以只填写所需的语言列表，我这里为了省事直接添加 Shiki 所有语言，主要是因为文档太多，已经懒得去统计用到了哪些语言。</p>
<p style="">此外，<code>theme</code> 也可以指定多主题，如果需要让文档的暗色和亮色模式下代码块的主题不同，可以按照下面的方式更改：</p>
<pre><code class="language-javascript">const { bundledLanguages } = require("shiki");
const { default: rehypeShiki } = require("@shikijs/rehype");

/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          // ...
          beforeDefaultRehypePlugins: [
            [
              rehypeShiki,
              {
// [!code --]
                theme: "catppuccin-mocha",
// [!code ++:4]
                themes: {
                  light: "github-light",
                  dark: "github-dark"
                },
                langs: Object.keys(bundledLanguages),
                // or
                // langs: ['js', 'ts']
              },
            ],
          ],
          // ...
        },
      }),
    ],
  ],
  // ...
};

module.exports = config;</code></pre>
<p style="">然后在 <code>custom.css</code> 中添加：</p>
<pre><code class="language-css">[data-theme="dark"] pre {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
}
[data-theme="dark"] pre span {
  color: var(--shiki-dark) !important;
}</code></pre>
<p style="">由于我期望亮色和暗色模式下都使用暗色的代码块主题，所以没有添加多主题配置。</p>
<h2 style="" id="%E7%BB%84%E4%BB%B6%E8%A6%86%E7%9B%96"><strong>组件覆盖</strong></h2>
<p style="">由于需要完全让 <code>@shikijs/rehype</code> 接管 Markdown 文档中的代码块渲染，我们需要覆盖 Docusaurus 内部 Pre/Code 的组件，避免被默认的 Prism 处理。Docusaurus 默认提供了 <a href="https://docusaurus.io/docs/swizzling" target="_blank" rel="">CLI</a> 用于导出 Docusaurus 主题中的组件。</p>
<pre><code class="language-shellscript">npx docusaurus swizzle @docusaurus/theme-classic MDXComponents/Code --typescript --eject</code></pre>
<p style="">然后打开 <code>src/theme/MDXComponents/Code.tsx</code> 并修改为：</p>
<pre><code class="language-typescript">import type { ComponentProps, ReactNode } from "react";
import React from "react";
import CodeInline from "@theme/CodeInline";
import type { Props } from "@theme/MDXComponents/Code";

function shouldBeInline(props: Props) {
  return (
    // empty code blocks have no props.children,
    // see https://github.com/facebook/docusaurus/pull/9704
    typeof props.children !== "undefined" &amp;&amp;
    React.Children.toArray(props.children).every(
      (el) =&gt; typeof el === "string" &amp;&amp; !el.includes("\n")
    )
  );
}

// [!code ++:3]
function CodeBlock(props: ComponentProps&lt;"code"&gt;): JSX.Element {
  return &lt;code {...props} /&gt;;
}

export default function MDXCode(props: Props): ReactNode {
  return shouldBeInline(props) ? (
    &lt;CodeInline {...props} /&gt;
  ) : (
    &lt;CodeBlock {...(props as ComponentProps&lt;typeof CodeBlock&gt;)} /&gt;
  );
}</code></pre>
<pre><code class="language-shellscript">npx docusaurus swizzle @docusaurus/theme-classic MDXComponents/Pre --typescript --eject</code></pre>
<p style="">然后打开 <code>src/theme/MDXComponents/Pre.tsx</code> 并修改为：</p>
<pre><code class="language-typescript">import React, { type ReactNode } from "react";
import type { Props } from "@theme/MDXComponents/Pre";
export default function MDXPre(props: Props): ReactNode | undefined {
  return &lt;pre {...props} /&gt;;
}</code></pre>
<blockquote>
 <p style="">小插曲：当时到了这一步的时候，突然意识到似乎可以复用之前为 Halo 开发 Shiki 插件时发布的 NPM 包（<a href="https://www.npmjs.com/package/@halo-dev/shiki-code-element" target="_blank" rel="">@halo-dev/shiki-code-element</a>）。因为这个包封装了一个 Web Component，所以肯定可以用在这里，只需要在 <code>pre</code> 标签外包裹一个 <code>shiki-code</code> 即可。尝试了一下确实可行，但这样就必须在客户端渲染了。虽然可行，但始终不如在构建阶段就渲染好。虽然可以尝试使用 <a href="https://lit.dev/docs/ssr/server-usage/" target="_blank" rel="">Lit SSR</a>，但考虑到文档中有一些代码块使用了 Title Meta，而目前 <code>@halo-dev/shiki-code-element</code> 还不支持，所以放弃了这个方案。</p>
</blockquote>
<p style="">完成这一步之后，就可以尝试启动开发服务器了。不出意外的话，代码块就可以正常使用 Shiki 来渲染了。</p>
<h2 style="" id="%E6%B7%BB%E5%8A%A0%E6%A0%87%E9%A2%98%E6%94%AF%E6%8C%81"><strong>添加标题支持</strong></h2>
<p style="">原来 Docusaurus 的默认方案是支持为代码块添加<a href="https://docusaurus.io/docs/next/markdown-features/code-blocks#code-title" target="_blank" rel="">顶部标题</a>的，切换到 Shiki 之后，这一部分需要自行实现，以下是具体步骤：</p>
<p style="">首先为 Shiki 添加一个自定义的 Transformer，用于解析标题的书写语法和添加代码块参数：</p>
<p style="">创建 <code>src/shiki/meta-transformer.js</code>：</p>
<pre><code class="language-javascript">function parseTitleFromMeta(meta) {
  if (!meta) {
    return "";
  }
  const kvList = meta.split(" ").filter(Boolean);
  for (const item of kvList) {
    const [k, v = ""] = item.split("=").filter(Boolean);
    if (k === "title" &amp;&amp; v.length &gt; 0) {
      return v.replace(/["'`]/g, "");
    }
  }
  return "";
}

export function transformerAddMeta() {
  return {
    name: "shiki-transformer:add-meta",
    pre(pre) {
      const title = parseTitleFromMeta(this.options.meta?.__raw);
      if (title.length &gt; 0) {
        pre.properties = {
          ...pre.properties,
          "data-title": title,
        };
      }
      return pre;
    },
  };
}</code></pre>
<p style="">然后修改配置：</p>
<pre><code class="language-javascript">const { bundledLanguages } = require("shiki");
const { default: rehypeShiki } = require("@shikijs/rehype");
const { transformerAddMeta } = require("./src/shiki/meta-transformer");

/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          // ...
          beforeDefaultRehypePlugins: [
            [
              rehypeShiki,
              {
                theme: "catppuccin-mocha",
                langs: Object.keys(bundledLanguages),
// [!code ++:3]
                transformers: [
                  transformerAddMeta(),
                ]
              },
            ],
          ],
          // ...
        },
      }),
    ],
  ],
  // ...
};</code></pre>
<p style="">修改 Pre.tsx 显示标题：</p>
<pre><code class="language-tsx">import React, { type ReactNode } from "react";
import type { Props } from "@theme/MDXComponents/Pre";

type PreWithDataTitle = Props &amp; { "data-title"?: string };

export default function MDXPre(props: Props): ReactNode | undefined {
  const title = props["data-title"];
  return (
    &lt;div
      style={{
        ...props.style,
        borderRadius: "var(--ifm-pre-border-radius)",
      }}
      className="shiki-code-wrapper"
    &gt;
      {title &amp;&amp; (
        &lt;div className="shiki-code-header"&gt;
          &lt;span&gt;{title}&lt;/span&gt;
        &lt;/div&gt;
      )}
      &lt;div className="shiki-code-content"&gt;
        &lt;pre {...props} ref={preRef} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<blockquote>
 <p style="">这里还修改了 MDXPre 标签的组件结构，为后续的功能做准备，其中最外层的 <code>div</code> 添加的 <code>style</code> 属性来自于 Shiki 渲染结果的 <code>pre</code> 标签的样式，包含背景色和字体默认颜色。</p>
</blockquote>
<p style="">最后我们需要为自定义的 MDXPre 组件结构添加样式，这里为了让结构看起来更清晰，我引入了 SASS 插件：</p>
<pre><code class="language-bash"># 安装所需依赖
pnpm add docusaurus-plugin-sass sass -D</code></pre>
<p style="">修改配置文件：</p>
<pre><code class="language-javascript">/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        // ...
        theme: {
// [!code --]
          customCss: require.resolve("./src/css/custom.css"),
// [!code ++:4]
          customCss: [
            require.resolve("./src/css/custom.css"),
            require.resolve("./src/css/shiki.scss"),
          ],
        },
      }),
    ],
  ],
// [!code ++]
  plugins: [require.resolve("docusaurus-plugin-sass")],
};
</code></pre>
<p style="">然后创建 <code>src/css/shiki.scss</code>：</p>
<pre><code class="language-scss">.shiki-code-wrapper {
  overflow: hidden;
  margin-bottom: var(--ifm-leading);
  color-scheme: dark;

  .shiki-code-header {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--ifm-color-gray-700);
    font-size: var(--ifm-code-font-size);
  }
}</code></pre>
<p style="">这样就支持使用原有的语法为代码块添加标题了。</p>
<h2 style="" id="%E6%98%BE%E7%A4%BA%E8%A1%8C%E5%8F%B7"><strong>显示行号</strong></h2>
<p style="">Shiki 原生并不支持在渲染的 HTML 结果中包含行号信息，但社区中有人提供了一种使用纯 CSS 实现的方案，详见：<hyperlink-inline-card target="_blank" href="https://github.com/shikijs/shiki/issues/3#issuecomment-830564854" theme="inline" custom-title="Line numbers"><a href="https://github.com/shikijs/shiki/issues/3#issuecomment-830564854" target="_blank">https://github.com/shikijs/shiki/issues/3#issuecomment-830564854</a></hyperlink-inline-card></p>
<p style="">修改 <code>shiki.scss</code>：</p>
<pre><code class="language-scss">.shiki-code-wrapper {
  overflow: hidden;
  margin-bottom: var(--ifm-leading);
  color-scheme: dark;

  .shiki-code-header {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--ifm-color-gray-700);
    font-size: var(--ifm-code-font-size);
  }
// [!code ++:36]
  .shiki-code-content {
    position: relative;

    pre {
      position: relative;
      padding: 0.75rem;
      margin: 0;
      border-radius: initial;
      code {

        counter-reset: step;
        counter-increment: step 0;

        .line {
          position: relative;
        }

        // line numbers start
        .line::before {
          content: counter(step);
          counter-increment: step;
          width: 0.6rem;
          margin-right: 1.1rem;
          display: inline-block;
          text-align: right;
          color: rgba(115, 138, 148, 0.5);
          user-select: none;
        }

        .line:last-child:empty::before {
          content: none;
          counter-increment: none;
        }
        // line numbers end
      }
    }
  }
}
</code></pre>
<p style="">这样就可以默认为所有代码块添加行号显示了。</p>
<h2 style="" id="%E5%A4%8D%E5%88%B6%E6%8C%89%E9%92%AE"><strong>复制按钮</strong></h2>
<p style="">Docusaurus 默认的代码块有复制按钮，改为 Shiki 之后这部分也需要自行实现，以下是具体步骤：</p>
<p style="">修改 <code>src/theme/MDXComponents/Pre.tsx</code>：</p>
<pre><code class="language-tsx">import React, { type ReactNode, useRef, useState } from "react";
import type { Props } from "@theme/MDXComponents/Pre";

type PreWithDataTitle = Props &amp; { "data-title"?: string };

export default function MDXPre(props: PreWithDataTitle): ReactNode | undefined {
  const title = props["data-title"];
// [!code ++:11]
  const preRef = useRef&lt;HTMLPreElement&gt;(null);
  const [copied, setCopied] = useState(false);

  const handleCopy = () =&gt; {
    const code = preRef.current?.innerText || preRef.current?.textContent || "";

    copyText(code, () =&gt; {
      setCopied(true);
      setTimeout(() =&gt; setCopied(false), 2000);
    });
  };

  return (
    &lt;div
      style={{
        ...props.style,
        borderRadius: "var(--ifm-pre-border-radius)",
      }}
      className="shiki-code-wrapper"
    &gt;
      {title &amp;&amp; (
        &lt;div className="shiki-code-header"&gt;
          &lt;span&gt;{title}&lt;/span&gt;
        &lt;/div&gt;
      )}
      &lt;div className="shiki-code-content"&gt;
// [!code ++:8]
        &lt;button
          className="shiki-code-copy-button"
          onClick={handleCopy}
          title={copied ? "已复制!" : "复制代码"}
          style={{ ...props.style }}
        &gt;
          &lt;i className={copied ? "tabler--check" : "tabler--copy"}&gt;&lt;/i&gt;
        &lt;/button&gt;
        &lt;pre {...props} ref={preRef} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

// [!code ++:24]
export function copyText(text: string, cb: () =&gt; void) {
  if (navigator.clipboard) {
    navigator.clipboard.writeText(text).then(() =&gt; {
      cb();
    });
  } else {
    const textArea = document.createElement("textarea");
    textArea.value = text;
    textArea.style.position = "fixed";
    textArea.style.opacity = "0";
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();
    try {
      const successful = document.execCommand("copy");
      if (successful) {
        cb();
      }
    } catch (err) {
      console.error("Fallback: Oops, unable to copy", err);
    }
    document.body.removeChild(textArea);
  }
}</code></pre>
<p style="">添加样式：</p>
<pre><code class="language-scss">.shiki-code-wrapper {
  overflow: hidden;
  margin-bottom: var(--ifm-leading);
  color-scheme: dark;

  .shiki-code-header {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--ifm-color-gray-700);
    font-size: var(--ifm-code-font-size);
  }

  .shiki-code-content {
    position: relative;

    pre {
      position: relative;
      padding: 0.75rem;
      margin: 0;
      border-radius: initial;
      code {

        counter-reset: step;
        counter-increment: step 0;

        .line {
          position: relative;
        }

        // line numbers start
        .line::before {
          content: counter(step);
          counter-increment: step;
          width: 0.6rem;
          margin-right: 1.1rem;
          display: inline-block;
          text-align: right;
          color: rgba(115, 138, 148, 0.5);
          user-select: none;
        }

        .line:last-child:empty::before {
          content: none;
          counter-increment: none;
        }
        // line numbers end
      }
    }
// [!code ++:15]
    .shiki-code-copy-button {
      position: absolute;
      top: 0.5rem;
      right: 0.5rem;
      opacity: 0;
      z-index: 2;
      width: 2rem;
      height: 2rem;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0.4rem;
      border: none;
      cursor: pointer;
    }
  }
// [!code ++:33]
  &amp;:hover {
    .shiki-code-copy-button {
      opacity: 1;
    }
  }

  .tabler--copy {
    display: inline-block;
    width: 28px;
    height: 28px;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z'/%3E%3Cpath d='M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1'/%3E%3C/g%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }

  .tabler--check {
    display: inline-block;
    width: 28px;
    height: 28px;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 12l5 5L20 7'/%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }
}
</code></pre>
<p style="">这样就可以在鼠标悬停到代码块时显示复制按钮了。</p>
<h2 style="" id="%E9%9B%86%E6%88%90%E5%BF%85%E8%A6%81%E7%9A%84-transformer"><strong>集成必要的 Transformer</strong></h2>
<p style="">Shiki 官方提供了一些非常有用的 <a href="https://shiki.style/packages/transformers" target="_blank" rel="">Transformers</a>，比如行高亮、代码对比等，这里根据自身需要添加即可。</p>
<pre><code class="language-shell">pnpm add -D @shikijs/transformers</code></pre>
<pre><code class="language-javascript">const { bundledLanguages } = require("shiki");
const { default: rehypeShiki } = require("@shikijs/rehype");
const { transformerAddMeta } = require("./src/shiki/meta-transformer");
// [!code ++:4]
const { transformerMetaHighlight } = require("@shikijs/transformers");
const { transformerNotationDiff } = require("@shikijs/transformers");
const { transformerNotationFocus } = require("@shikijs/transformers");
const { transformerNotationErrorLevel } = require("@shikijs/transformers");

/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...
  presets: [
    [
      "classic",
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          // ...
          beforeDefaultRehypePlugins: [
            [
              rehypeShiki,
              {
                theme: "catppuccin-mocha",
                langs: Object.keys(bundledLanguages),
                transformers: [
// [!code ++:8]
                  // 行高亮，使用 Meta 信息的方式，比如 ```java {1}
                  transformerMetaHighlight(),
                  // 代码对比，使用注释的方式
                  transformerNotationDiff(),
                  // 行聚焦，使用注释的方式
                  transformerNotationFocus(),
                  // 行高亮的错误和警告变体，使用注释的方式
                  transformerNotationErrorLevel(),
                  transformerAddMeta(),
                ]
              },
            ],
          ],
          // ...
        },
      }),
    ],
  ],
  // ...
};
</code></pre>
<p style="">这些 Transformers 只是为对应的行添加了 class，我们需要自行实现样式。以下是完整的 <code>shiki.scss</code>：</p>
<details class="details">
 <summary>点击查看 shiki.scss</summary>
 <div data-type="detailsContent">
  <pre><code class="language-scss">.shiki-code-wrapper {
  overflow: hidden;
  margin-bottom: var(--ifm-leading);
  color-scheme: dark;

  .shiki-code-header {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--ifm-color-gray-700);
    font-size: var(--ifm-code-font-size);
  }

  .shiki-code-content {
    position: relative;

    pre {
      position: relative;
      padding: 0.75rem;
      margin: 0;
      border-radius: initial;
      code {
        z-index: 1;
        display: block;
        width: max-content;
        position: relative;
        min-width: 100%;

        counter-reset: step;
        counter-increment: step 0;

        .line {
          position: relative;
        }

        // line numbers start
        .line::before {
          content: counter(step);
          counter-increment: step;
          width: 0.6rem;
          margin-right: 1.1rem;
          display: inline-block;
          text-align: right;
          color: rgba(115, 138, 148, 0.5);
          user-select: none;
        }

        .line:last-child:empty::before {
          content: none;
          counter-increment: none;
        }
        // line numbers end

        // highlighted lines start
        .highlighted {
          width: 100%;
          display: inline-block;
          position: relative;
        }

        .highlighted::after {
          content: "";
          position: absolute;
          top: 0;
          bottom: 0;
          left: -0.75rem;
          right: -0.75rem;
          background: rgba(101, 117, 133, 0.16);
          border-left: 1px solid rgba(34, 197, 94, 0.8);
          z-index: 0;
        }

        .highlighted.error::after {
          background: rgba(244, 63, 94, 0.16) !important;
        }

        .highlighted.warning::after {
          background: rgba(234, 179, 8, 0.16) !important;
        }
        // highlighted lines end
      }

      // focus line start
      &amp;.has-focused .line:not(.focused) {
        opacity: 0.7;
        filter: blur(0.095rem);
        transition: filter 0.35s, opacity 0.35s;
      }

      &amp;.has-focused:hover .line:not(.focused) {
        opacity: 1;
        filter: blur(0);
      }
      // focus line end

      // diff start
      &amp;.has-diff .diff {
        width: 100%;
        display: inline-block;
        position: relative;
      }

      &amp;.has-diff .diff.remove::before {
        content: "-";
      }

      &amp;.has-diff .diff.add::before {
        content: "+";
      }

      &amp;.has-diff .diff.remove::after {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        left: -0.75rem;
        right: -0.75rem;
        background: rgb(239 68 68 / 0.15);
        border-left: 1px solid rgb(239 68 68 / 0.8);
        z-index: -1;
      }

      &amp;.has-diff .diff.add::after {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        left: -0.75rem;
        right: -0.75rem;
        background: rgb(34 197 94 / 0.15);
        border-left: 1px solid rgb(34 197 94 / 0.8);
        z-index: -1;
      }
      // diff end
    }

    .shiki-code-copy-button {
      position: absolute;
      top: 0.5rem;
      right: 0.5rem;
      opacity: 0;
      z-index: 2;
      width: 2rem;
      height: 2rem;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0.4rem;
      border: none;
      cursor: pointer;
    }
  }

  &amp;:hover {
    .shiki-code-copy-button {
      opacity: 1;
    }
  }

  .tabler--copy {
    display: inline-block;
    width: 28px;
    height: 28px;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z'/%3E%3Cpath d='M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1'/%3E%3C/g%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }

  .tabler--check {
    display: inline-block;
    width: 28px;
    height: 28px;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 12l5 5L20 7'/%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }
}</code></pre>
 </div>
</details>
<p style="">至此，一个比较好看、功能丰富的代码高亮改造方案就完成了，具体修改代码也可以查阅 Halo 文档的 PR：<hyperlink-inline-card target="_blank" href="https://github.com/halo-dev/docs/pull/521" theme="inline" custom-title="Use shiki as code highlighter"><a href="https://github.com/halo-dev/docs/pull/521" target="_blank">https://github.com/halo-dev/docs/pull/521</a></hyperlink-inline-card>，也可以访问 <a href="https://docs.halo.run/" target="_blank" rel="">Halo 文档</a>查看修改之后的效果。</p>
<h2 style="" id="%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99"><strong>参考资料</strong></h2>
<ul>
 <li>
  <p style=""><a href="https://github.com/facebook/docusaurus/issues/9122" target="_blank" rel="">https://github.com/facebook/docusaurus/issues/9122</a></p>
 </li>
 <li>
  <p style=""><a href="https://lachieh.github.io/docusaurus-with-shiki-rehype/docs/intro/" target="_blank" rel="">https://lachieh.github.io/docusaurus-with-shiki-rehype/docs/intro/</a></p>
 </li>
 <li>
  <p style=""><a href="https://github.com/shikijs/shiki/issues/3" target="_blank" rel="">https://github.com/shikijs/shiki/issues/3</a></p>
 </li>
 <li>
  <p style=""><a href="https://docusaurus.io/docs/markdown-features/plugins" target="_blank" rel="">https://docusaurus.io/docs/markdown-features/plugins</a></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/docusaurus-shiki</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdocusaurus%2Bshiki-cover.png&amp;size=m" type="image/jpeg" length="50681"/><category>学习记录</category><pubDate>Wed, 29 Oct 2025 15:24:03 GMT</pubDate></item><item><title><![CDATA[使用 Meilisearch 提升 Halo 网站的搜索体验]]></title><link>https://ryanc.cc/archives/halo-meilisearch</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8%20Meilisearch%20%E6%8F%90%E5%8D%87%20Halo%20%E7%BD%91%E7%AB%99%E7%9A%84%E6%90%9C%E7%B4%A2%E4%BD%93%E9%AA%8C&amp;url=/archives/halo-meilisearch" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">原文地址：<a href="https://www.lxware.cn/archives/halo-meilisearch" target="_blank" rel="nofollow">https://www.lxware.cn/archives/halo-meilisearch</a></p>
</blockquote>
<p style="">Halo 从 2.0 版本开始支持了全文搜索功能，自带的 Lucene 搜索引擎在轻度使用场景下可以满足需求，但在重度依赖搜索功能的场景下，可能在搜索速度和用户体验上存在不足，这时我们会更推荐使用独立的搜索引擎。</p>
<p style="">本文将介绍如何使用 <hyperlink-inline-card target="_blank" href="https://www.meilisearch.com/" theme="inline" custom-title="Meilisearch"><a href="https://www.meilisearch.com/" target="_blank">https://www.meilisearch.com/</a></hyperlink-inline-card> 搜索引擎来提升 Halo 网站的搜索体验。</p>
<h2 style="" id="%E6%90%AD%E5%BB%BA-meilisearch-%E6%9C%8D%E5%8A%A1"><strong>搭建 Meilisearch 服务</strong></h2>
<p style="">部署 Meilisearch 服务通常有两种方式：你可以选择自行在服务器上托管，或者使用 Meilisearch 官方提供的云服务。</p>
<h3 style="" id="%E4%BA%91%E6%9C%8D%E5%8A%A1"><strong>云服务</strong></h3>
<p style="">访问 <hyperlink-inline-card target="_blank" href="https://www.meilisearch.com/cloud" theme="inline"><a href="https://www.meilisearch.com/cloud" target="_blank">https://www.meilisearch.com/cloud</a></hyperlink-inline-card> 注册账号，根据引导创建项目。创建完成后，可以在控制台获取实例地址和 Master Key。</p>
<blockquote>
 <p style="">需要特别注意 Meilisearch 云服务的计费方式。</p>
</blockquote>
<h3 style="" id="%E8%87%AA%E6%89%98%E7%AE%A1"><strong>自托管</strong></h3>
<p style="">下面介绍两种常见的部署方式：</p>
<h4 style="" id="%E4%BD%BF%E7%94%A8-docker-compose-%E7%8B%AC%E7%AB%8B%E7%BC%96%E6%8E%92%E9%83%A8%E7%BD%B2"><strong>使用 Docker Compose 独立编排部署</strong></h4>
<blockquote>
 <p style="">这种方式适合多个项目需要同时使用一个 Meilisearch 服务的场景。 部署完成后，你可以配置域名和反向代理来暴露服务到公网。</p>
</blockquote>
<pre><code class="language-yaml">services:
  meilisearch:
  image: getmeili/meilisearch:v1.15
  restart: unless-stopped
  ports:
    - "7700:7700"
  environment:
    - MEILI_ENV=production
// [!code highlight]
    - MEILI_MASTER_KEY=&lt;your-super-secret-master-key-here&gt;
  volumes:
    - meilisearch_data:/meili_data

volumes:
  meilisearch_data:
    driver: local</code></pre>
<h4 style="" id="%E4%B8%8E-halo-%E7%9A%84-compose-%E7%BC%96%E6%8E%92%E4%B8%80%E8%B5%B7%E9%83%A8%E7%BD%B2"><strong>与 Halo 的 Compose 编排一起部署</strong></h4>
<blockquote>
 <p style="">结合 <a href="https://docs.halo.run/getting-started/install/docker-compose">使用 Docker Compose 部署 Halo</a> 的示例，将 Meilisearch 服务添加到 <code>docker-compose.yml</code> 文件中。</p>
</blockquote>
<blockquote>
 <p style="">通过这种方式部署之后，插件设置中的 <strong>Meilisearch 服务地址</strong> 应该是 <code>http://meilisearch:7700</code>（即服务在同一 Compose 网络下可通过服务名访问）</p>
</blockquote>
<pre><code class="language-yaml">meilisearch:
  image: getmeili/meilisearch:v1.15
  restart: on-failure:3
  networks:
    - halo_network
  volumes:
    - ./meilisearch-data:/meili_data
  environment:
    - MEILI_ENV=production
// [!code highlight]
    - MEILI_MASTER_KEY=&lt;your-super-secret-master-key-here&gt;</code></pre>
<details class="details">
 <summary>点击查看完整示例</summary>
 <div data-type="detailsContent">
  <pre><code class="language-yaml">version: "3"

services:
  halo:
    image: registry.fit2cloud.com/halo/halo:2.21
    restart: on-failure:3
    depends_on:
    halodb:
      condition: service_healthy
    networks:
    halo_network:
    volumes:
      - ./halo2:/root/.halo2
    ports:
      - "8090:8090"
    healthcheck:
    test:
      ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
    interval: 30s
    timeout: 5s
    retries: 5
    start_period: 30s
    environment:
      - JVM_OPTS=-Xmx256m -Xms256m
    command:
      - --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
      - --spring.r2dbc.username=halo
      - --spring.r2dbc.password=openpostgresql
      - --spring.sql.init.platform=postgresql
      - --halo.external-url=http://localhost:8090/
  halodb:
    image: postgres:15.4
    restart: on-failure:3
    networks:
    halo_network:
    volumes:
      - ./db:/var/lib/postgresql/data
    healthcheck:
    test: ["CMD", "pg_isready"]
    interval: 10s
    timeout: 5s
    retries: 5
    environment:
      - POSTGRES_PASSWORD=openpostgresql
      - POSTGRES_USER=halo
      - POSTGRES_DB=halo
      - PGUSER=halo
// [!code focus:10]
  meilisearch:
    image: getmeili/meilisearch:v1.15
    restart: on-failure:3
    networks:
      - halo_network
    volumes:
      - ./meilisearch-data:/meili_data
    environment:
      - MEILI_ENV=production
      - MEILI_MASTER_KEY=&lt;your-super-secret-master-key-here&gt;

networks:
  halo_network:</code></pre>
 </div>
</details>
<p style="">详细的部署方式可以参考 Meilisearch 官方文档：<a href="https://www.meilisearch.com/docs/learn/self_hosted/install_meilisearch_locally">https://www.meilisearch.com/docs/learn/self_hosted/install_meilisearch_locally</a></p>
<h2 style="" id="%E5%AE%89%E8%A3%85%E6%8F%92%E4%BB%B6"><strong>安装插件</strong></h2>
<blockquote>
 <p style="">之前 Halo 社区中已经有人开发了 Meilisearch 插件，但已经<a href="https://github.com/Rainsheep/halo-plugin-meilisearch/issues/12#issuecomment-2969309786">不再维护</a>，因此这里我们选择使用 Halo 官方提供的插件。</p>
</blockquote>
<ol>
 <li>
  <p style=""><strong>下载插件</strong>，目前提供以下两种下载方式：</p>
  <ul>
   <li>
    <p style="">Halo 应用市场：<a href="https://www.halo.run/store/apps/app-7mb5szjt">https://www.halo.run/store/apps/app-7mb5szjt</a></p>
   </li>
   <li>
    <p style="">GitHub Releases：访问 <a href="https://github.com/halo-sigs/plugin-meilisearch/releases">Releases</a> 下载 Assets 中的 JAR 文件。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><strong>安装插件</strong>，插件安装和更新方式可参考：<a href="https://docs.halo.run/user-guide/plugins">https://docs.halo.run/user-guide/plugins</a></p>
 </li>
</ol>
<h2 style="" id="%E9%85%8D%E7%BD%AE%E6%8F%92%E4%BB%B6"><strong>配置插件</strong></h2>
<ol>
 <li>
  <p style="">进入插件设置，配置 <strong>Meilisearch 服务地址</strong> 和 <strong>Master Key</strong>。<strong>索引名称</strong> 可以选择使用默认的 <code>halo</code> 或者自定义（如果你的 Meilisearch 服务会被多个项目使用，建议自定义索引名称）。</p>
  <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
   <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-plugin-setting.png&amp;size=m" width="100%" height="100%">
  </figure>
 </li>
 <li>
  <p style="">进入插件扩展配置，在 <strong>扩展点定义</strong> 中选择 <strong>搜索引擎</strong>，然后选择使用 Meilisearch。</p>
  <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
   <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-extension-point-setting.png&amp;size=m" width="100%" height="100%">
  </figure>
 </li>
</ol>
<h2 style="" id="%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88"><strong>数据概览</strong></h2>
<p style="">配置完插件后，我们可以进入插件的 <strong>数据概览</strong> 页面，查看 Meilisearch 的索引数据。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-overview.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">在这个页面中，你还可以重建索引或测试搜索功能。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-overview-test.png&amp;size=m" width="100%" height="100%">
</figure>
<h2 style="" id="%E5%AF%B9%E6%AF%94"><strong>对比</strong></h2>
<p style="">Lucene（默认搜索引擎）与 Meilisearch 的实际对比：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 913px">
  <colgroup>
   <col style="width: 455px">
   <col style="width: 458px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="455">
     <p style="">Meilisearch</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="458">
     <p style="">Lucene（默认）</p>
    </th>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="455">
     <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
      <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-compare-1.png&amp;size=m" width="100%" height="100%">
     </figure>
    </td>
    <td colspan="1" rowspan="1" colwidth="458">
     <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
      <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-compare-2.png&amp;size=m" alt="meilisearch-compare-2.png" width="100%" height="100%">
     </figure>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="455">
     <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
      <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-compare-3.png&amp;size=m" width="100%" height="100%">
     </figure>
    </td>
    <td colspan="1" rowspan="1" colwidth="458">
     <figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
      <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmeilisearch-compare-4.png&amp;size=m" width="100%" height="100%">
     </figure>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">通过实际使用可以发现，Meilisearch 的搜索结果更加准确，搜索速度更快，并且支持更灵活的搜索语法，无需用户掌握复杂的搜索表达式即可获得理想的搜索结果。</p>
<h2 style="" id="%E6%B3%A8%E6%84%8F">注意</h2>
<ol>
 <li>
  <p style="">如果配置完 Meilisearch 插件之后无法搜索，可以尝试重建一次索引。</p>
 </li>
 <li>
  <p style="">安装 Meilisearch 插件之后仍然需要<hyperlink-inline-card target="_blank" href="https://www.halo.run/store/apps/app-DlacW" theme="inline" custom-title="搜索组件"><a href="https://www.halo.run/store/apps/app-DlacW" target="_blank">https://www.halo.run/store/apps/app-DlacW</a></hyperlink-inline-card>插件，Meilisearch 插件仅仅是提供服务，不会提供 UI。</p>
 </li>
</ol>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/halo-meilisearch</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fhalo-meilisearch-post.png&amp;size=m" type="image/jpeg" length="47519"/><category>关于 Halo</category><category>分享</category><pubDate>Wed, 23 Jul 2025 07:12:55 GMT</pubDate></item><item><title><![CDATA[在 Halo 中导入 Markdown 和 Word 文档]]></title><link>https://ryanc.cc/archives/halo-content-tools</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8%20Halo%20%E4%B8%AD%E5%AF%BC%E5%85%A5%20Markdown%20%E5%92%8C%20Word%20%E6%96%87%E6%A1%A3&amp;url=/archives/halo-content-tools" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">原文地址：<a href="https://www.lxware.cn/archives/halo-content-tools" target="_blank" rel="nofollow">https://www.lxware.cn/archives/halo-content-tools</a></p>
</blockquote>
<p style="">在 Halo 社区中，导入 Markdown 和 Word 文档的需求一直很高，但社区一直缺乏完善的解决方案。其主要原因在于 Markdown 和 Word 的文档格式较为复杂，难以完美支持所有格式特性，且图片资源的处理存在技术难点。</p>
<p style="">现在，社区中已经有了一个插件可以很好地支持导入 Markdown 和 Word 文档，它就是 <hyperlink-inline-card target="_blank" href="https://www.halo.run/store/apps/app-SUvBR" theme="inline" custom-title="内容助手"><a href="https://www.halo.run/store/apps/app-SUvBR" target="_blank">https://www.halo.run/store/apps/app-SUvBR</a></hyperlink-inline-card>。该插件不仅支持导入 Markdown 和 Word 文档，还能够智能处理和导入图片资源，为用户提供了完整的文档迁移解决方案。</p>
<h2 style="" id="%E5%AE%89%E8%A3%85"><strong>安装</strong></h2>
<p style="">可以通过以下两种方式安装插件：</p>
<ol>
 <li>
  <p style="">访问 <hyperlink-inline-card target="_blank" href="https://www.halo.run/store/apps/app-SUvBR" theme="inline" custom-title="应用市场 - 内容助手"><a href="https://www.halo.run/store/apps/app-SUvBR" target="_blank">https://www.halo.run/store/apps/app-SUvBR</a></hyperlink-inline-card> 页面直接下载</p>
 </li>
 <li>
  <p style="">在 Console 内置的应用市场中搜索 <strong>内容助手</strong> 进行安装</p>
 </li>
</ol>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-%E5%AE%89%E8%A3%85.png&amp;size=m" width="100%" height="100%" data-position="left">
 <figcaption data-placeholder="添加描述" style="width: 100%; max-width: 100%; text-align: center;">内容助手插件</figcaption>
</figure>
<h2 style="" id="%E5%AF%BC%E5%85%A5-markdown-%E6%96%87%E6%A1%A3"><strong>导入 Markdown 文档</strong></h2>
<p style="">安装并启用插件后，就可以在 Console 侧边菜单的工具中找到 <strong>文章导入</strong> 的入口。点击进入后，选择 <strong>Markdown 导入</strong> 选项卡即可开始导入，如下图：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E7%95%8C%E9%9D%A2.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E5%8A%9F%E8%83%BD%E8%AF%A6%E8%A7%A3"><strong>功能详解</strong></h3>
<ol>
 <li>
  <p style=""><strong>选择 Markdown 文件</strong>：用于选择单个 Markdown 文档，支持 <code>.md</code> 格式文件。</p>
 </li>
 <li>
  <p style=""><strong>选择 Markdown 文件夹</strong>：用于选择包含 Markdown 文档的文件夹。选择文件夹后，系统会自动扫描其中的所有 Markdown 文档以及图片资源（如有）。</p>
 </li>
 <li>
  <p style=""><strong>选择图片文件夹</strong>：用于选择 Markdown 文档中引用的图片资源。选择文件夹后，系统会自动扫描其中的所有图片资源并在导入时自动关联。</p>
 </li>
 <li>
  <p style=""><strong>转为富文本格式</strong>：默认情况下，导入的 Markdown 文档会保持原有的 Markdown 格式。如果勾选此选项，系统会将文档转换为富文本格式，便于后续使用 Halo 的默认编辑器进行编辑。</p>
 </li>
</ol>
<h3 style="" id="%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><strong>使用场景</strong></h3>
<ul>
 <li>
  <p style="">从其他博客平台或写作工具迁移文章内容</p>
 </li>
 <li>
  <p style="">导入使用本地 Markdown 编辑器创作的文章</p>
 </li>
 <li>
  <p style="">批量导入历史文档和资料</p>
 </li>
</ul>
<h3 style="" id="%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><strong>注意事项</strong></h3>
<ol>
 <li>
  <p style="">导入 Markdown 文档后，如果需要在 Console 中编辑文章，请确保已经安装了任意一个 <a href="https://www.halo.run/store/apps?tag=editor">Markdown 编辑器插件</a>，否则无法正常打开编辑页面。</p>
 </li>
 <li>
  <p style="">如果 Markdown 文档中引用了本地图片资源，请在导入前选择存放图片的文件夹，否则图片将无法正确上传和关联。</p>
 </li>
 <li>
  <p style="">系统支持自动解析 Front Matter（文档头部的元数据），包括标题、别名（slug）、描述、摘要、分类、标签等信息。</p>
 </li>
</ol>
<h3 style="" id="%E6%93%8D%E4%BD%9C%E7%A4%BA%E4%BE%8B"><strong>操作示例</strong></h3>
<p style="">选择 Markdown 文件：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E9%80%89%E6%8B%A9%E6%96%87%E4%BB%B6.png&amp;size=m" width="991px" height="Infinitypx" data-position="left">
</figure>
<p style="">选择图片文件夹（如果文档包含本地图片）：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E9%80%89%E6%8B%A9%E5%9B%BE%E7%89%87.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">点击导入，等待导入完成：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E5%AF%BC%E5%85%A5%E6%88%90%E5%8A%9F.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">检查文章与图片资源是否导入成功：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E6%9F%A5%E7%9C%8B%E6%96%87%E7%AB%A0.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Markdown%E5%AF%BC%E5%85%A5-%E6%9F%A5%E7%9C%8B%E9%99%84%E4%BB%B6.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h2 style="" id="%E5%AF%BC%E5%85%A5-word-%E6%96%87%E6%A1%A3"><strong>导入 Word 文档</strong></h2>
<p style="">进入 <strong>文章导入</strong> 页面后，选择 <strong>Word（.docx）导入</strong> 选项卡，如下图：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Word%E5%AF%BC%E5%85%A5-%E7%95%8C%E9%9D%A2.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E5%8A%9F%E8%83%BD%E8%AF%A6%E8%A7%A3-1"><strong>功能详解</strong></h3>
<ol>
 <li>
  <p style=""><strong>选择 Word 文档</strong>：用于选择单个 Word 文档，支持 <code>.doc</code> 和 <code>.docx</code> 格式。</p>
 </li>
 <li>
  <p style=""><strong>选择 Word 文档文件夹</strong>：用于批量选择包含 Word 文档的文件夹。</p>
 </li>
 <li>
  <p style=""><strong>转为 Markdown 格式</strong>：默认情况下，导入的 Word 文档会转换为富文本格式。如果希望后续使用 Markdown 编辑器编辑文章，请勾选此选项将内容转换为 Markdown 格式。</p>
 </li>
</ol>
<h3 style="" id="%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF-1"><strong>使用场景</strong></h3>
<ul>
 <li>
  <p style="">将公司内部的 Word 文档转换为博客文章</p>
 </li>
 <li>
  <p style="">配合 <hyperlink-inline-card target="_blank" href="https://maxkb.cn/" theme="inline" custom-title="MaxKB"><a href="https://maxkb.cn/" target="_blank">https://maxkb.cn/</a></hyperlink-inline-card> 智能知识库，建立企业知识管理体系</p>
 </li>
 <li>
  <p style="">从传统文档工具迁移内容到现代化的管理平台</p>
 </li>
</ul>
<h3 style="" id="%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9-1"><strong>注意事项</strong></h3>
<ol>
 <li>
  <p style="">由于 Word 文档格式的复杂性，系统可能无法完美解析所有内容格式，建议导入后进行适当调整。</p>
 </li>
 <li>
  <p style="">系统支持自动导入 Word 文档中的图片资源，但其他类型的嵌入对象暂不支持。</p>
 </li>
 <li>
  <p style="">图片会上传到与个人中心关联的存储策略，请提前在用户设置中配置相关参数。</p>
 </li>
</ol>
<h3 style="" id="%E6%93%8D%E4%BD%9C%E7%A4%BA%E4%BE%8B-1"><strong>操作示例</strong></h3>
<p style="">选择 Word 文档：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Word%E5%AF%BC%E5%85%A5-%E9%80%89%E6%8B%A9%E6%96%87%E4%BB%B6.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">点击导入，等待导入完成：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Word%E5%AF%BC%E5%85%A5-%E5%AF%BC%E5%85%A5%E6%88%90%E5%8A%9F.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">检查文章是否导入成功：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Word%E5%AF%BC%E5%85%A5-%E6%9F%A5%E7%9C%8B%E6%96%87%E7%AB%A0.png&amp;size=m" width="991px" height="Infinitypx" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-Word%E5%AF%BC%E5%85%A5-%E6%9F%A5%E7%9C%8B%E9%99%84%E4%BB%B6.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h2 style="" id="%E4%B8%B0%E5%AF%8C%E7%9A%84%E5%86%85%E5%AE%B9%E7%AE%A1%E7%90%86%E5%8A%9F%E8%83%BD"><strong>丰富的内容管理功能</strong></h2>
<p style="">除了核心的导入功能，内容助手还提供了丰富的内容管理功能：</p>
<h3 style="" id="%E6%A0%BC%E5%BC%8F%E8%BD%AC%E6%8D%A2"><strong>格式转换</strong></h3>
<p style="">支持 Markdown 与富文本格式的双向转换，让用户可以根据编辑需求灵活切换文档格式。你可以在文章管理页面点击文章的 <code>···</code> 按钮，在转换菜单中选择相应的格式转换选项，也可以在文章编辑页面顶部的编辑器选择框中选择 <strong>内容格式转换器</strong> 进行转换。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-%E8%BD%AC%E6%8D%A2%E6%A0%BC%E5%BC%8F.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E6%96%87%E7%AB%A0%E5%AF%BC%E5%87%BA"><strong>文章导出</strong></h3>
<p style="">支持将文章导出为多种格式，方便内容备份和分享：</p>
<ul>
 <li>
  <p style=""><strong>以原格式导出</strong>：保持文章的原始格式进行导出</p>
 </li>
 <li>
  <p style=""><strong>转换为 Markdown 并导出</strong>：将文章转换为 Markdown 格式后导出</p>
 </li>
 <li>
  <p style=""><strong>转换为 PDF 并导出</strong>：将文章转换为 PDF 格式进行导出</p>
 </li>
</ul>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-%E5%AF%BC%E5%87%BA.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E6%96%87%E7%AB%A0%E5%85%8B%E9%9A%86"><strong>文章克隆</strong></h3>
<p style="">提供文章克隆功能，便于基于现有文章创建相似内容。克隆后的文章会自动在标题后添加"（副本）"标识，并生成新的别名以避免冲突。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%E5%86%85%E5%AE%B9%E5%8A%A9%E6%89%8B-%E5%85%8B%E9%9A%86.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">以上功能都可以在文章管理页面点击文章的 <code>···</code> 按钮找到相应选项。</p>
<h2 style="" id="%E6%80%BB%E7%BB%93"><strong>总结</strong></h2>
<p style="">内容助手插件为 Halo 用户提供了完整的文档导入和内容管理解决方案，有效解决了 Markdown 和 Word 文档的导入难题。插件不仅支持智能处理图片资源，还提供了格式转换、文章导出、文章克隆、内容复制等丰富功能，能够满足大部分用户的内容管理需求。</p>
<p style="">无论你是从其他平台迁移内容，还是需要批量导入历史文档，内容助手都能为你提供便捷、高效的解决方案。如果你有文档导入或内容格式转换的需求，欢迎尝试使用内容助手插件。</p>]]></description><guid isPermaLink="false">/archives/halo-content-tools</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fhalo-content-tools.png&amp;size=m" type="image/jpeg" length="106878"/><category>关于 Halo</category><category>分享</category><pubDate>Thu, 17 Jul 2025 04:36:20 GMT</pubDate></item><item><title><![CDATA[通过 1Panel MCP 自动部署静态网站]]></title><link>https://ryanc.cc/archives/1panel-mcp</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E9%80%9A%E8%BF%87%201Panel%20MCP%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99&amp;url=/archives/1panel-mcp" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">原文地址：<a href="https://www.lxware.cn/archives/1panel-mcp" target="_blank" rel="nofollow">https://www.lxware.cn/archives/1panel-mcp</a></p>
</blockquote>
<h2 style="" id="%E5%BC%95%E8%A8%80"><strong>引言</strong></h2>
<p style="">随着大语言模型（LLM）技术的快速发展，我们正在见证软件开发领域的一场革命。从 ChatGPT 到 Claude，从 GitHub Copilot 到各种 AI 编程助手，人工智能正在深刻改变着开发者的工作方式。</p>
<p style="">在这个 AI 驱动的时代，Agent（智能代理）概念应运而生。Agent 不仅能理解自然语言指令，还能执行复杂的任务流程，真正实现了"对话式编程"的愿景。而 MCP（Model Context Protocol）作为连接 AI 模型与外部工具的标准协议，为构建强大的 AI Agent 提供了技术基础。</p>
<p style="">MCP 的出现解决了一个关键问题：如何让 AI 模型安全、高效地与各种外部系统交互。通过标准化的协议，开发者可以创建各种 MCP 工具，让 AI 助手能够执行文件操作、API 调用、数据库查询等复杂任务。</p>
<p style="">本文将介绍如何使用 <hyperlink-inline-card target="_blank" href="https://github.com/ruibaby/1Panel-mcp" theme="inline" custom-title="ruibaby/1Panel-mcp"><a href="https://github.com/ruibaby/1Panel-mcp" target="_blank">https://github.com/ruibaby/1Panel-mcp</a></hyperlink-inline-card> 工具，在 AI 编辑器中实现自动将网站项目部署到 1Panel 中。</p>
<h2 style="" id="%E9%85%8D%E7%BD%AE"><strong>配置</strong></h2>
<p style=""><hyperlink-inline-card target="_blank" href="https://github.com/ruibaby/1Panel-mcp" theme="inline" custom-title="ruibaby/1Panel-mcp"><a href="https://github.com/ruibaby/1Panel-mcp" target="_blank">https://github.com/ruibaby/1Panel-mcp</a></hyperlink-inline-card> 中只提供了一个工具，即 <code>deploy_website</code>，用于将静态网站项目部署到 1Panel 中，并支持自动创建网站配置。下面将主要介绍在 VSCode 和 Cursor 中如何配置并使用此工具。</p>
<p style=""><strong>VSCode</strong>:</p>
<p style="">打开 VSCode 的配置文件，添加以下配置：</p>
<pre><code class="language-json">{
  "mcp": {
    "inputs": [],
    "servers": {
      "1panel-mcp": {
        "command": "npx",
        "args": [
          "-y",
          "1panel-mcp"
        ],
        "env": {
// [!code highlight:3]
          "ONEPANEL_API_KEY": "TOSXWBVfcG7dLlD1Gj0DK5D4L9tKz6FF",
          "ONEPANEL_BASE_URL": "http://127.0.0.1:34300/",
          "ONEPANEL_API_VERSION": "v2"
        }
      }
    }
  }
}</code></pre>
<p style="">配置完成后保存，然后在 Copilot Chat 的界面可以看到 <code>1panel-mcp</code> 的 <code>deploy_website</code> 工具，即代表配置成功。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BVSCode.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>Cursor</strong>:</p>
<p style="">打开 Cursor 的设置界面：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BCursor-Settings.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BCursor-MCP.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后在 MCP 配置文件中添加以下配置：</p>
<pre><code class="language-json">{
  "mcpServers": {
    "1panel-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "1panel-mcp"
      ],
      "env": {
// [!code highlight:3]
        "ONEPANEL_API_KEY": "TOSXWBVfcG7dLlD1Gj0DK5D4L9tKz6FF",
        "ONEPANEL_BASE_URL": "http://127.0.0.1:34300/",
        "ONEPANEL_API_VERSION": "v2"
      }
    }
  }
}</code></pre>
<p style="">然后回到设置界面，可以看到 <code>1panel-mcp</code> 的 <code>deploy_website</code> 工具，即代表配置成功。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BCursor-MCP-2.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>参数说明</strong>：</p>
<ul>
 <li>
  <p style=""><code>ONEPANEL_BASE_URL</code>: 1Panel 的 API 地址</p>
 </li>
 <li>
  <p style=""><code>ONEPANEL_API_KEY</code>: 1Panel 的 API 密钥，可以在 1Panel 控制台设置中获取</p>
 </li>
 <li>
  <p style=""><code>ONEPANEL_API_VERSION</code>: 1Panel 的 API 版本，可选值为 <code>v1</code> 或 <code>v2</code>，默认值为 <code>v2</code></p>
 </li>
</ul>
<h2 style="" id="%E4%BD%BF%E7%94%A8"><strong>使用</strong></h2>
<p style="">配置完成后，我们就可以打开任意的静态网站项目并测试这个 MCP 工具，可以使用以下提示词：</p>
<pre><code class="language-shellscript"># 将当前项目部署到 1Panel 中，域名为 halocms.net。</code></pre>
<blockquote>
 <p style="">需要注意，如果你指定的域名不存在，工具会自动创建一个新网站，并设置指定的域名。</p>
</blockquote>
<h2 style="" id="%E6%BC%94%E7%A4%BA"><strong>演示</strong></h2>
<p style="">为了方便演示，我创建了一个新的 Vue 项目，并让 AI 帮我部署到 1Panel，以下是完整过程：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-1.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-2.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-3.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">部署完成后，我们回到 1Panel 后台就可以看到新创建的网站和上传的文件：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-5-JGOt.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BWebsites-Files.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">后续我们完善了项目后，也可以让 AI 再次部署：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-4.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel%2BMCP%2BPreview-5.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="%E6%80%BB%E7%BB%93"><strong>总结</strong></h2>
<p style="">通过以上演示，我们可以看到，使用 1Panel-mcp 工具，我们可以让 AI 自动将静态网站项目部署到 1Panel 中，并支持自动创建网站配置，大大提高了开发和部署效率。</p>
<h2 style="" id="%E5%8F%82%E8%80%83"><strong>参考</strong></h2>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://github.com/ruibaby/1Panel-mcp" theme="inline" custom-title="ruibaby/1Panel-mcp"><a href="https://github.com/ruibaby/1Panel-mcp" target="_blank">https://github.com/ruibaby/1Panel-mcp</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://1panel.cn/" theme="inline" custom-title="1Panel 官网"><a href="https://1panel.cn/" target="_blank">https://1panel.cn/</a></hyperlink-inline-card></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/1panel-mcp</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1Panel-MCP-Cover.jpg&amp;size=m" type="image/jpeg" length="16270"/><category>分享</category><pubDate>Thu, 10 Jul 2025 07:37:04 GMT</pubDate></item><item><title><![CDATA[使用 Rspack 构建 Halo 插件的前端部分]]></title><link>https://ryanc.cc/archives/building-halo-plugin-frontend-with-rspack</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8%20Rspack%20%E6%9E%84%E5%BB%BA%20Halo%20%E6%8F%92%E4%BB%B6%E7%9A%84%E5%89%8D%E7%AB%AF%E9%83%A8%E5%88%86&amp;url=/archives/building-halo-plugin-frontend-with-rspack" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E6%9B%B4%E6%96%B0%EF%BC%8825-06-19%EF%BC%89">更新（25-06-19）</h2>
<p style="">现在已经为插件的 UI 部分提供了新的配置方式，<hyperlink-inline-card target="_blank" href="https://www.npmjs.com/package/@halo-dev/ui-plugin-bundler-kit" theme="inline" custom-title="@halo-dev/ui-plugin-bundler-kit" custom-image="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%20128%20128%22%3E%3Cpath%20fill%3D%22%23cb3837%22%20d%3D%22M0%207.062C0%203.225%203.225%200%207.062%200h113.88c3.838%200%207.063%203.225%207.063%207.062v113.88c0%203.838-3.225%207.063-7.063%207.063H7.062c-3.837%200-7.062-3.225-7.062-7.063zm23.69%2097.518h40.395l.05-58.532h19.494l-.05%2058.581h19.543l.05-78.075l-78.075-.1l-.1%2078.126z%22%2F%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M25.105%2065.52V26.512H40.96c8.72%200%2026.274.034%2039.008.075l23.153.075v77.866H83.645v-58.54H64.057v58.54H25.105z%22%2F%3E%3C%2Fsvg%3E"><a href="https://www.npmjs.com/package/@halo-dev/ui-plugin-bundler-kit" target="_blank">https://www.npmjs.com/package/@halo-dev/ui-plugin-bundler-kit</a></hyperlink-inline-card> 包提供了 <code>rsbuildConfig</code> 方法，可以更加方便的使用 <hyperlink-inline-card target="_blank" href="https://rsbuild.dev/" theme="inline" custom-title="Rsbuild"><a href="https://rsbuild.dev/" target="_blank">https://rsbuild.dev/</a></hyperlink-inline-card> 来构建 UI 部分。</p>
<blockquote>
 <p style="">Rsbuild 基于 Rspack 构建，提供了更完善的 loader 配置，所以在封装的时候就直接选择了 Rsbuild。</p>
</blockquote>
<p style="">安装依赖：</p>
<pre><code class="language-bash">pnpm install @halo-dev/ui-plugin-bundler-kit@2.21.1 @rsbuild/core -D</code></pre>
<p style="">rsbuild.config.mjs:</p>
<pre><code class="language-js">import { rsbuildConfig } from "@halo-dev/ui-plugin-bundler-kit";

export default rsbuildConfig()</code></pre>
<p style="">package.json 添加 scripts:</p>
<pre><code class="language-json">{
  "type": "module",
  "scripts": {
    "dev": "rsbuild build --env-mode development --watch",
    "build": "rsbuild build"
  }
}</code></pre>
<p style="">需要注意的是，为了适应新版的 <hyperlink-inline-card target="_blank" href="https://github.com/halo-dev/plugin-starter" theme="inline" custom-title="halo-dev/plugin-starter"><a href="https://github.com/halo-dev/plugin-starter" target="_blank">https://github.com/halo-dev/plugin-starter</a></hyperlink-inline-card>，默认生产构建输出目录改为了 <code>ui/build/dist</code> ，如果你要从已有的插件项目迁移到 Rsbuild，建议参考 <hyperlink-inline-card target="_blank" href="https://github.com/halo-dev/plugin-starter/pull/52" theme="inline" custom-title="Refactor build scripts and workflows "><a href="https://github.com/halo-dev/plugin-starter/pull/52" target="_blank">https://github.com/halo-dev/plugin-starter/pull/52</a></hyperlink-inline-card> 对 Gradle 脚本进行改动，或者自定义 Rsbuild 的配置以保持原有的输出目录配置：</p>
<pre><code class="language-js">import { rsbuildConfig } from "@halo-dev/ui-plugin-bundler-kit";

const OUT_DIR_PROD = "../src/main/resources/console";
const OUT_DIR_DEV = "../build/resources/main/console";

export default rsbuildConfig({
  rsbuild: ({ envMode }) =&gt; {
    const isProduction = envMode === "production";
    const outDir = isProduction ? OUT_DIR_PROD : OUT_DIR_DEV;

    return {
      resolve: {
        alias: {
          "@": "./src",
        },
      },
      output: {
        distPath: {
          root: outDir,
        },
      },
    };
  },
});</code></pre>
<p style="">示例：<hyperlink-inline-card target="_blank" href="https://github.com/halo-sigs/plugin-migrate" theme="inline"><a href="https://github.com/halo-sigs/plugin-migrate" target="_blank">https://github.com/halo-sigs/plugin-migrate</a></hyperlink-inline-card></p>
<p style="">了解更多：<hyperlink-inline-card target="_blank" href="https://docs.halo.run/developer-guide/plugin/basics/ui/build" theme="inline"><a href="https://docs.halo.run/developer-guide/plugin/basics/ui/build" target="_blank">https://docs.halo.run/developer-guide/plugin/basics/ui/build</a></hyperlink-inline-card></p>
<hr>
<h2 style="" id="%E5%89%8D%E6%83%85%E6%8F%90%E8%A6%81"><strong>前情提要</strong></h2>
<p style="">Halo 插件的 UI 部分（Console / UC）的实现方式其实很简单，本质上就是构建一个结构固定的大对象，交给 Halo 去解析，其中包括全局注册的组件、路由定义、扩展点等。 基于这个前提，在实现插件机制时，主要面临的问题就是如何将这个大对象传递给 Halo。当初做了非常多的尝试，最终选择构建为 <a href="https://en.wikipedia.org/wiki/Immediately_invoked_function_expression" target="_blank" rel="">IIFE</a>（Immediately Invoked Function Expression，立即执行函数），然后 Halo 通过读取 <code>window[PLUGIN_NAME]</code>（PLUGIN_NAME 即插件名）来获取这个对象。 构建方案采用 Vite，并提供了统一的构建配置。回过头来看，这个方案存在不少问题：</p>
<ol>
 <li>
  <p style="">会污染 window 对象，虽然目前并没有出现因为这个导致的问题，但是从长远来看，这个方案并不是最优的。（当然，使用 Rspack 来构建并不是为了解决这个问题）</p>
 </li>
 <li>
  <p style="">Vite 不支持 IIFE / UMD 格式的代码分割（主要是 Rollup 还不支持），无法像 ESM（ECMAScript Module）那样实现异步加载模块的机制。</p>
 </li>
 <li>
  <p style="">基于第 2 点，如果插件中实现了较多的功能，可能会导致最终产物体积巨大，尤其是当用户安装了过多的插件时，会导致页面加载缓慢。</p>
  <ol>
   <li>
    <p style="">以 <hyperlink-inline-card target="_blank" href="http://www.halo.run/" theme="inline" custom-title="Halo 官网"><a href="http://www.halo.run/" target="_blank">http://www.halo.run/</a></hyperlink-inline-card> 为例，gzip 之前接近 10M 的 bundle.js，gzip 之后也有 2M - 3M。</p>
   </li>
   <li>
    <p style="">以此博客为例，gzip 之后也有 1.8M 的 bundle.js。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">基于第 2 点，如果不支持代码分块（Chunk），也无法充分利用资源缓存，访问页面时，也会一次性加载所有插件的代码（即便当前页面不需要）。</p>
 </li>
</ol>
<p style="">基于以上问题，我开始寻找其他替代方案，最终通过翻阅 Rspack（Webpack 的 Rust 实现）的文档发现，Webpack 能够通过配置实现 IIFE 格式的代码分割，最终选择 Rspack 作为尝试。</p>
<h2 style="" id="%E5%9F%BA%E6%9C%AC%E7%9A%84-rspack-%E9%85%8D%E7%BD%AE"><strong>基本的 Rspack 配置</strong></h2>
<p style="">安装依赖：</p>
<pre><code class="language-bash">pnpm install @rspack/cli @rspack/core vue-loader -D</code></pre>
<p style="">package.json 添加 scripts:</p>
<pre><code class="language-json">{
  "type": "module",
  "scripts": {
    "dev": "NODE_ENV=development rspack build --watch",
    "build": "NODE_ENV=production rspack build"
  }
}</code></pre>
<p style="">rspack.config.mjs:</p>
<pre><code class="language-js">import { defineConfig } from '@rspack/cli';
import path from 'path';
import process from 'process';
import { VueLoaderPlugin } from 'vue-loader';
import { fileURLToPath } from 'url';

// plugin.yaml 中的 metadata.name
const PLUGIN_NAME = '&lt;YOUR_PLUGIN_NAME&gt;';

const isProduction = process.env.NODE_ENV === 'production';
const dirname = path.dirname(fileURLToPath(import.meta.url));

// 开发环境启动直接输出到插件项目的 build 目录，无需重启整个插件
// 生产环境输出到插件项目的 src/main/resources/console 目录下
const outDir = isProduction ? '../src/main/resources/console' : '../build/resources/main/console';

export default defineConfig({
  mode: process.env.NODE_ENV,
  entry: {
    // 入口文件，可以参考：https://docs.halo.run/developer-guide/plugin/basics/ui/entry
    main: './src/index.ts',
  },
  plugins: [new VueLoaderPlugin()],
  resolve: {
    alias: {
      '@': path.resolve(dirname, 'src'),
    },
    extensions: ['.ts', '.js'],
  },
  output: {
    // 资源根路径，加载代码分块（Chunk）的时候，会根据这个路径去加载资源
    publicPath: `/plugins/${PLUGIN_NAME}/assets/console/`,
    chunkFilename: '[id]-[hash:8].js',
    cssFilename: 'style.css',
    path: path.resolve(outDir),
    library: {
      // 将对象挂载到 window 上
      type: 'window',
      export: 'default',
      name: PLUGIN_NAME,
    },
    clean: true,
    iife: true,
  },
  optimization: {
    providedExports: false,
    realContentHash: true,
  },
  experiments: {
    css: true,
  },
  devtool: false,
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        loader: 'builtin:swc-loader',
        options: {
          jsc: {
            parser: {
              syntax: 'typescript',
            },
          },
        },
        type: 'javascript/auto',
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          experimentalInlineMatchResource: true,
        },
      },
    ],
  },
  // 这部分依赖已经由 Halo 提供，所以需要标记为外部依赖
  externals: {
    vue: 'Vue',
    'vue-router': 'VueRouter',
    '@vueuse/core': 'VueUse',
    '@vueuse/components': 'VueUse',
    '@vueuse/router': 'VueUse',
    '@halo-dev/console-shared': 'HaloConsoleShared',
    '@halo-dev/components': 'HaloComponents',
    '@halo-dev/api-client': 'HaloApiClient',
    '@halo-dev/richtext-editor': 'RichTextEditor',
    axios: 'axios',
  },
});</code></pre>
<p style="">配置需要懒加载的路由或者组件：</p>
<p style="">在 index.ts 中配置路由：</p>
<pre><code class="language-javascript">import { definePlugin } from '@halo-dev/console-shared';
import { defineAsyncComponent } from 'vue';
import { VLoading } from '@halo-dev/components';
import 'uno.css';
// [!code --]
import DemoPage from './views/DemoPage.vue';

export default definePlugin({
  routes: [
    {
      parentName: 'Root',
      route: {
        path: 'demo',
        name: 'DemoPage',
// [!code --]
        component: DemoPage,
// [!code ++:4]
        component: defineAsyncComponent({
          loader: () =&gt; import('./views/DemoPage.vue'),
          loadingComponent: VLoading,
        }),
      ...
      },
    },
  ],
  extensionPoints: {},
});</code></pre>
<p style="">注：推荐使用 <code>defineAsyncComponent</code> 包裹，而不是直接使用 <code>() =&gt; import()</code> 的方式，后者会在进入路由之前就开始加载页面的代码分块（Chunk），导致页面在加载期间没有任何响应。</p>
<p style="">构建产物示例：</p>
<pre><code class="language-bash">❯ ll src/main/resources/console
.rw-r--r-- 191k ryanwang staff 16 Jun 10:47  359-3bebb968.js
.rw-r--r--  83k ryanwang staff 16 Jun 10:47  962-3bebb968.js
.rw-r--r-- 4.1k ryanwang staff 16 Jun 10:47  main.js</code></pre>
<h2 style="" id="%E5%85%B6%E4%BB%96%E9%85%8D%E7%BD%AE"><strong>其他配置</strong></h2>
<h3 style="" id="%E9%9B%86%E6%88%90-scss-%2F-sass"><strong>集成 Scss / Sass</strong></h3>
<p style="">安装依赖：</p>
<pre><code class="language-bash">pnpm install sass-embedded sass-loader -D</code></pre>
<p style="">rspack.config.mjs 添加配置：</p>
<pre><code class="language-javascript">import { defineConfig } from '@rspack/cli';
import path from 'path';
import process from 'process';
import { VueLoaderPlugin } from 'vue-loader';
import { fileURLToPath } from 'url';
// [!code ++]
import * as sassEmbedded from "sass-embedded";

...

export default defineConfig({
  ...
  module: {
    rules: [
      ...
// [!code ++:13]
      {
        test: /\.(sass|scss)$/,
        use: [
          {
            loader: "sass-loader",
            options: {
              api: "modern-compiler",
              implementation: sassEmbedded,
            },
         },
        ],
        type: "css/auto",
      },
    ],
  },
...
});</code></pre>
<h3 style="" id="%E9%9B%86%E6%88%90-unocss"><strong>集成 UnoCSS</strong></h3>
<p style="">如果你习惯使用 TailwindCSS 或者 UnoCSS 来编写样式，可以参考以下配置：</p>
<blockquote>
 <p style="">本文推荐使用 <hyperlink-inline-card target="_blank" href="https://unocss.dev/" theme="inline" custom-title="UnoCSS"><a href="https://unocss.dev/" target="_blank">https://unocss.dev/</a></hyperlink-inline-card>，因为可以利用 UnoCSS 的 <hyperlink-inline-card target="_blank" href="https://unocss.dev/transformers/compile-class" theme="inline"><a href="https://unocss.dev/transformers/compile-class" target="_blank">https://unocss.dev/transformers/compile-class</a></hyperlink-inline-card> 来编译样式，预防与 Halo 或者其他插件产生样式冲突。</p>
</blockquote>
<p style="">安装依赖：</p>
<pre><code class="language-bash">pnpm install unocss @unocss/webpack @unocss/eslint-config style-loader css-loader -D</code></pre>
<p style="">入口文件（src/index.ts）添加导入：</p>
<pre><code class="language-ts">import 'uno.css';</code></pre>
<p style="">rspack.config.mjs 添加配置：</p>
<pre><code class="language-javascript">import { defineConfig } from '@rspack/cli';
import path from 'path';
import process from 'process';
import { VueLoaderPlugin } from 'vue-loader';
import { fileURLToPath } from 'url';
// [!code ++]
import { UnoCSSRspackPlugin } from '@unocss/webpack/rspack';

...

export default defineConfig({
  ...
  plugins: [
    new VueLoaderPlugin(),
// [!code ++]
    UnoCSSRspackPlugin()
  ],
  ...
  module: {
    rules: [
      ...
// [!code ++:5]
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        type: 'javascript/auto',
      },
    ],
  },
...
});</code></pre>
<p style="">uno.config.ts:</p>
<pre><code class="language-ts">import { defineConfig, presetWind3, transformerCompileClass } from 'unocss';

export default defineConfig({
  presets: [presetWind3()],
  transformers: [transformerCompileClass()],
});</code></pre>
<p style="">.eslintrc.cjs:</p>
<pre><code class="language-javascript">/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');

module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-recommended',
    'eslint:recommended',
    '@vue/eslint-config-typescript/recommended',
    '@vue/eslint-config-prettier',
// [!code ++]
    '@unocss',
  ],
  env: {
    'vue/setup-compiler-macros': true,
  },
// [!code ++:3]
  rules: {
    "@unocss/enforce-class-compile": 1,
  },
};</code></pre>
<h2 style="" id="%E6%80%BB%E7%BB%93">总结</h2>
<p style="">以上就是针对 Halo 插件前端部分的 Rspack 配置。我已经对 Halo 官方维护的部分插件进行了迁移，几乎没有遇到什么问题，并且带来的收益非常明显：<a href="http://www.halo.run/" target="_blank" rel="">www.halo.run</a> 和本博客的 bundle.js 在 gzip 之后仅有不到 200k，各个页面也只会在访问时加载所需的资源。</p>
<p style="">需要注意的是，我对这些构建工具并不算非常熟悉，所以配置仍然有优化空间。我们会持续优化，后续也会考虑提供一个通用的 CLI 或 Rspack 配置，期望实现如下效果：</p>
<p style="">rspack.config.mjs:</p>
<pre><code class="language-js">import { rspackConfig } from '@halo-dev/ui-bundler-kit';

export default rspackConfig({
  ...
});</code></pre>
<p style="">或者基于 Rspack 包装一个 CLI：</p>
<p style="">plugin.config.mjs:</p>
<pre><code class="language-js">import { defineConfig } from '@halo-dev/ui-bundler-kit';

export default defineConfig({
  ...
});</code></pre>
<p style="">package.json:</p>
<pre><code class="language-json">{
  "scripts": {
    "dev": "halo-ui dev",
    "build": "halo-ui build"
  }
}</code></pre>
<h2 style="" id="%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3">参考文档</h2>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://rsbuild.dev/" theme="inline"><a href="https://rsbuild.dev/" target="_blank">https://rsbuild.dev/</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://rspack.rs/" theme="inline"><a href="https://rspack.rs/" target="_blank">https://rspack.rs/</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://webpack.js.org/concepts/" theme="inline"><a href="https://webpack.js.org/concepts/" target="_blank">https://webpack.js.org/concepts/</a></hyperlink-inline-card></p>
 </li>
</ul>
<p style="">感谢阅读，欢迎交流与指正！</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/building-halo-plugin-frontend-with-rspack</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FHalo%2BRspack.png&amp;size=m" type="image/jpeg" length="100657"/><category>关于 Halo</category><category>学习记录</category><pubDate>Mon, 16 Jun 2025 06:37:47 GMT</pubDate></item><item><title><![CDATA[探讨如何为开源项目提交 issue]]></title><link>https://ryanc.cc/archives/open-source-repo-issue</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E6%8E%A2%E8%AE%A8%E5%A6%82%E4%BD%95%E4%B8%BA%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E6%8F%90%E4%BA%A4%20issue&amp;url=/archives/open-source-repo-issue" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">此文来自于我在公司内部 Wiki 上的文章，现将其整理并转载到我的博客。</p>
 <p style="">Co-Authors：OD、wan92hen</p>
</blockquote>
<h2 style="text-align: start; " id="%E4%BB%80%E4%B9%88%E6%98%AF-github-issue">什么是 GitHub Issue</h2>
<p style="text-align: start; ">GitHub 官方文档（<u><hyperlink-inline-card target="_blank" href="https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues" theme="inline"><a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues" target="_blank">https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues</a></hyperlink-inline-card></u>）给出的解释是 <strong>Use GitHub Issues to track ideas, feedback, tasks, or bugs for work on GitHub.</strong>，即通过 GitHub Issues 来跟踪 GitHub 项目相关的想法、反馈、任务及缺陷。从这段描述当中我们可以发现 Issue 不仅代表了 Bug、缺陷，它可以是任何跟项目有关且需要项目维护者知晓的内容，如果将其直译为<strong>问题</strong>可能会引起歧义，所以接下来我们直接使用 Issue 这个单词来表示它。</p>
<p style="text-align: start; ">另一个值得注意的地方是 GitHub Issues 是一种异步沟通体系。所谓异步，即我提交了一个 Issue 以后，并不意味着立即就能够收到反馈。异步沟通虽然不像同步沟通那样来得直接，但并不代表它不高效。因为不期望能立即收到反馈来进行进一步地沟通，双方都需要尽可能地将信息一次性传达到位，在一定程度上反而可以提高沟通效率。</p>
<h2 style="text-align: start; " id="%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8F%90%E4%BA%A4-issue">为什么要提交 Issue</h2>
<p style="text-align: start; ">上文中提到了 Issue 所代表的含义，它可能是关于项目的一个新想法，也可能是对于某个特定功能的使用反馈，还有可能是使用过程中碰到的各种各样的 Bug。提交 Issue 的目的自然是让项目维护者知道有这样的一个 Issue 存在。这看起来是一句废话，但实际上不同的 Issue 内容，能够达成这个目的的效果可能千差万别。一个好的 Issue 能够清晰、准确地表达出自己希望传达的信息，项目维护者跟提交者之间不需要再次进行额外的沟通就能够顺利地处理掉这个 Issue。反之一个不好的 Issue 不仅起不到这样的效果，反而可能在你来我往的交互中逐渐跑偏甚至变得火药味十足。</p>
<h2 style="text-align: start; " id="%E5%A6%82%E4%BD%95%E6%8F%90%E4%BA%A4%E4%B8%80%E4%B8%AA-github-issue">如何提交一个 GitHub Issue</h2>
<p style="text-align: start; ">提交一个 Issue 很简单，打开 GitHub 登录下自己的账号，点几下鼠标敲几下键盘就可以提交一个 Issue。但是要提交一个好的 Issue 还是需要一定技巧的。在此之前，建议先阅读下&nbsp;<a rel="nofollow" href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way" target="_self"><u>提问的智慧</u></a> 这篇文章，虽然是来自英文版的直白翻译，其文化背景、用词风格以及社会环境跟国内有所差异，描绘的场景跟 GitHub Issues 的使用场景也略有区别，但是其中关于提问相关的注意事项还是很值得参考的。这里也借鉴下这篇文章的格式，尝试总结下如何提交一个好的 GitHub Issue。</p>
<h2 style="text-align: start; " id="%E6%8F%90%E4%BA%A4%E4%B9%8B%E5%89%8D">提交之前</h2>
<p style="text-align: start; ">首先，提交一个 Issue 不仅仅是为了请求帮助或者报告问题，也有可能会帮助后续遇到此问题的使用者。从某种意义上来说，这也是对整个开源生态非常具有价值的贡献。所以，不要抱着把问题抛出去等待解决的心理，需要认真对待提交 Issue 这件事，因为你也是一个<strong>贡献者</strong>。在提交 Issue 前，建议先做以下几件事：</p>
<ol>
 <li>
  <p style=""><strong>理清楚你遇到的问题，需要有一定的逻辑性。</strong></p>
 </li>
 <li>
  <p style=""><strong>查阅项目文档，搜索是否有相关功能、问题的说明。</strong>幸运的话在这个环节我们的问题就可以得到解决，也可能会发现某个功能是设计如此，当然这个情况下我们仍可以提出自己的想法，但相应的措辞就需要有所转变。</p>
 </li>
 <li>
  <p style=""><strong>在该项目的 Issues 中搜索有没有类似问题。</strong>同样的，如果能够找到类似问题并且在回复列表中看到解决、规避方案，又可以省下我们好多等待时间。如果问题看起来类似，但又不完全一致，可以就在此 Issue 进行回复并说明你遇到的问题。</p>
 </li>
 <li>
  <p style=""><strong>在 GitHub 项目之外进行搜索，包括但不限于搜索引擎、内部知识库、项目论坛、交流群等等任何你能想到的地方。</strong>如果该项目跟某个其他项目使用了同样的底层库或依赖，有可能类似的问题在其他项目相关资料中可以找到解决方案；如果该项目足够活跃，可能会有用户在其他论坛或博客中分享使用经验或某个问题的解决方案。</p>
 </li>
 <li>
  <p style=""><strong>Double Check 一下问题确实存在。</strong>在项目维护过程中，包括我自己的软件使用经历中，出现过很多因为自己的各种失误而引起的问题，例如某个单词拼写错误、某个功能的前置操作没有完成等等等等。如果这样的 Issue 被提交上来，不仅会浪费双方的时间，还会降低自己在项目维护者心中的可信度，使得后续真正的问题不被重视（参考狼来了的故事）。</p>
 </li>
 <li>
  <p style=""><strong>在新版本中尝试验证。</strong></p>
 </li>
 <li>
  <p style=""><strong>如果是同时遇到多个不相关的问题，不要提在同一个 Issue 里，可以提交多个 Issue，方便维护者跟踪。</strong></p>
 </li>
</ol>
<p style="text-align: start; ">当你真正准备好提交一个问题时，请再次回想下 GitHub Issues 是一个异步沟通系统，假设你只能够发一条消息给项目维护者，你要怎么做才能够让自己的问题得到解决。</p>
<h2 style="text-align: start; " id="%E6%8F%90%E4%BA%A4%E7%9A%84%E5%86%85%E5%AE%B9">提交的内容</h2>
<h3 style="text-align: start; " id="%E4%B8%80%E4%B8%AA%E5%A5%BD%E7%9A%84-issue-%E6%A0%87%E9%A2%98"><strong>一个好的 Issue 标题</strong></h3>
<ol>
 <li>
  <p style="">尽可能使用一句话作为标题，但要求直截了当。千万不要直接把问题的详细内容直接写在标题。</p>
 </li>
 <li>
  <p style="">给定一个范围标记，用于缩小这个问题的范围，方便维护者和后续使用者索引。</p>
 </li>
 <li>
  <p style="">仅填写问题简述，不要添加无关内容。</p>
 </li>
</ol>
<h4 style="text-align: start; " id="%E4%B8%80%E4%BA%9B%E6%A1%88%E4%BE%8B"><strong>一些案例</strong></h4>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 984px">
  <colgroup>
   <col style="width: 447px">
   <col style="width: 377px">
   <col style="width: 160px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="447">
     <p style=""><strong>Bad</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="377">
     <p style=""><strong>Good</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="160">
     <p style=""><strong>Why</strong></p>
    </th>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">表格渲染异常</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">编辑器：表格语法渲染异常，在编辑器可以正常渲染，发布后无法渲染</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够清晰</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">安装报错</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">安装站点异常，提示 Internal Server Error</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够清晰</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">列表无法渲染</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">文章列表：后台文章列表渲染空白，可能是src/components/PostList.vue中的 xx 属性为空</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够清晰</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">数学公式问题</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">文章页面：数学公式原样输出，没有经过渲染</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够清晰</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">上传附件 bug 提交，请作者看看，谢谢</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">附件上传：在附件管理中上传附件提示失败，浏览器控制台提示 413 Request Entity Too Large</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够清晰，包含无关内容</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">将一个带有 “”接口用例且接口用例里面有环境配置 “” 的接口 导出来，然后 导入另外一个项目，再创建接口用例，配置环境，有时候出现新接口用例使用的是旧的环境，根本原因是 接口导出的竟然把 对应用例的环境 ID给导出来。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style="">接口定义导出再导入到另一个项目时，新导入接口下的用例使用了原项目中的环境</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">问题描述不够简洁，将详细内容直接写在了标题里</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="447">
     <p style="">(没有标题)</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="377">
     <p style=""></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="160">
     <p style="">如果不是有责任维护项目，甚至都不想点进去看内容</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="text-align: start; " id="%E9%97%AE%E9%A2%98%E6%8F%8F%E8%BF%B0"><strong>问题描述</strong></h3>
<p style="text-align: start; ">建议参考以下几点：</p>
<ol>
 <li>
  <p style="">避免使用大量啰嗦的文字来描述，保证干练和逻辑性即可。如果无法描述清楚，可以通过补充复现步骤来辅助。一个好的 Issue 能够<strong>恰到好处</strong>地给出所需的信息。所谓恰到好处，是指信息不多也不少，刚刚好能够支持项目维护者解决这个问题或者了解这个需求。</p>
 </li>
 <li>
  <p style="">内容仅针对此问题，不要包含遇到的其他问题，其他问题可以另外提交 Issue。</p>
 </li>
 <li>
  <p style="">不要包含与问题无关的话语。</p>
 </li>
 <li>
  <p style="">没有明显的错别字，合理使用标点符号</p>
 </li>
</ol>
<h3 style="text-align: start; " id="%E6%8F%90%E4%BE%9B%E8%AF%A6%E7%BB%86%E7%9A%84%E7%8E%AF%E5%A2%83%E4%BF%A1%E6%81%AF"><strong>提供详细的环境信息</strong></h3>
<p style="text-align: start; ">一般来说，目前 GitHub 上的项目中都会包含 Issue 模板，会提示让你填写一些环境信息，按照具体要求填写即可。如果没有，也建议根据软件特性提供必要的信息，比如：浏览器类型及版本、服务器版本、使用的设备、使用的数据库版本等。这往往能帮助维护者快速定位某些在特定条件下才会出现的问题。</p>
<h3 style="text-align: start; " id="%E8%AF%A6%E7%BB%86%E7%9A%84%E5%A4%8D%E7%8E%B0%E6%AD%A5%E9%AA%A4"><strong>详细的复现步骤</strong></h3>
<p style="text-align: start; ">这一步主要告诉维护者出现这个问题的前因后果或者上下文，因为某些问题是必须在特定的操作下才可能复现的，甚至某些问题可能是某种意义上随机出现的，如果不提供复现步骤，维护者可能会非常难以复现。这也往往增加了来回询问的过程。另外，为了更加直观的描述问题，可以提供步骤截图，GIF 动图，甚至视频（目前 GitHub 已经支持视频）。</p>
<h3 style="text-align: start; " id="%E6%8F%90%E4%BE%9B%E6%97%A5%E5%BF%97"><strong>提供日志</strong></h3>
<p style="text-align: start; ">日志对于维护者排查问题也是至关重要的，提供日志建议参考以下几点：</p>
<ol>
 <li>
  <p style="">对一些敏感信息脱敏处理，某些软件可能会在日志中打印一些敏感信息，建议提供之前检查一遍并手动脱敏。</p>
 </li>
 <li>
  <p style="">仅截取相关的日志，如果不知道哪部分是相关的，建议手动复现一次这个问题并快速去查阅最新打印的日志。</p>
 </li>
 <li>
  <p style="">不仅仅是软件内提供的日志，如果这个项目是基于浏览器运行的，也可以提供一下浏览器控制台打印的日志。</p>
 </li>
 <li>
  <p style="">提供日志的时候请务必使用 Markdown 的代码块（```）语法包裹，否则日志会非常难以查阅。</p>
 </li>
</ol>
<h3 style="text-align: start; " id="%E5%B7%B2%E7%BB%8F%E5%B0%9D%E8%AF%95%E8%BF%87%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E5%BC%8F"><strong>已经尝试过的解决方式</strong></h3>
<p style="text-align: start; ">如果有自己尝试过解决，建议补充一下。因为维护者可能在不知道的情况下也提供出你已经尝试过的解决方式，这无疑增加了来回交流的次数，一定程度地影响效率。</p>
<h2 style="text-align: start; " id="%E5%90%8E%E7%BB%AD%E8%B7%9F%E8%B8%AA">后续跟踪</h2>
<p style="text-align: start; ">当你提交完成一个 Issue 之后，项目维护者可能并不会及时回复，这也是<strong>异步沟通</strong>的一个特点。但请耐心等待回复，不要在短时间内评论催促，有内容需要补充除外。</p>
<p style="text-align: start; ">当你提交的 Issue 收到回复后，GitHub 会向你的邮箱中发送通知，也可以使用 GitHub 内置的通知功能进行跟进。</p>
<p style="text-align: start; ">几个建议：</p>
<ol>
 <li>
  <p style="">如果收到回复，你的邮箱可能会收到 GitHub 的提醒邮件，虽然 GitHub 支持通过邮件回复 Issue，但请<strong>千万不要</strong>使用邮件回复，因为各个邮件客户端的差异，很可能导致最终的评论混乱，如：<a href="https://github.com/halo-dev/halo/issues/1799#issuecomment-1084632899"><u>https://github.com/halo-dev/halo/issues/1799#issuecomment-1084632899</u></a></p>
 </li>
 <li>
  <p style="">与维护者交流的时候不要像使用 IM 即时交流工具一样交流，尽可能一次提供完整的信息。</p>
 </li>
 <li>
  <p style="">如果问题已经解决，一定要及时在 Issue 中告知，必要的时候也可以提供后续的解决方式。</p>
 </li>
</ol>
<h2 style="text-align: start; " id="%E7%A4%BC%E4%BB%AA%E7%9B%B8%E5%85%B3">礼仪相关</h2>
<ol>
 <li>
  <p style="">不要包含任何的抱怨、吐槽、隐晦等不舒适的话语。即便你说的都对，但可以换一种更加友好的表达方式，仅表述问题，不输出情绪。</p>
 </li>
 <li>
  <p style="">不要要求项目或维护者做任何事。</p>
 </li>
</ol>
<h2 style="text-align: start; " id="%E5%AF%B9%E4%BA%8E%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%91%98%E6%88%96%E7%BB%B4%E6%8A%A4%E8%80%85">对于项目管理员或维护者</h2>
<h3 style="text-align: start; " id="%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%B4%A1%E7%8C%AE%E8%80%85%E6%96%87%E6%A1%A3"><strong>提供一个贡献者文档</strong></h3>
<p style="text-align: start; ">详尽地描述提交 Issue 和 PR 的步骤和要求。</p>
<h3 style="text-align: start; " id="%E6%8F%90%E4%BE%9B-issue-%E6%8F%90%E4%BA%A4%E6%A8%A1%E6%9D%BF"><strong>提供 Issue 提交模板</strong></h3>
<p style="text-align: start; ">目前 GitHub 已经提供了基于 yaml 描述表单的功能，可以提供更加直观的 Issue 提交表单，并支持表单验证，这能更加规范所有 Issue 的内容。你可以针对项目的特点，列出提交 Issue 所需的信息，如：服务器环境、浏览器版本、所使用的设备等。</p>
<p style="text-align: start; ">相关链接：</p>
<ul>
 <li>
  <p style="">GitHub 官方文档：<u><hyperlink-inline-card target="_blank" href="https://docs.github.com/cn/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms" theme="inline"><a href="https://docs.github.com/cn/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms" target="_blank">https://docs.github.com/cn/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms</a></hyperlink-inline-card></u></p>
 </li>
 <li>
  <p style="">使用案例：<a rel="nofollow" href="https://github.com/halo-dev/halo/issues/new?assignees=&amp;labels=bug&amp;template=bug_report.zh.yml" class="external-link"><u>New Issue · halo-dev/halo (</u></a><a href="http://github.com"><u>github.com</u></a><a rel="nofollow" href="https://github.com/halo-dev/halo/issues/new?assignees=&amp;labels=bug&amp;template=bug_report.zh.yml" class="external-link"><u>)</u></a></p>
 </li>
 <li>
  <p style="">使用案例：<a rel="nofollow" href="https://github.com/metersphere/metersphere/issues/new?assignees=youliyuan-fit2cloud&amp;labels=%E7%B1%BB%E5%9E%8B%3A+%E7%BC%BA%E9%99%B7&amp;template=bug.yml&amp;title=%5BBUG%5D" class="external-link"><u>New Issue · metersphere/metersphere (</u></a><a href="http://github.com"><u>github.com</u></a><a rel="nofollow" href="https://github.com/metersphere/metersphere/issues/new?assignees=youliyuan-fit2cloud&amp;labels=%E7%B1%BB%E5%9E%8B%3A+%E7%BC%BA%E9%99%B7&amp;template=bug.yml&amp;title=%5BBUG%5D" class="external-link"><u>)</u></a></p>
 </li>
 <li>
  <p style="">使用案例：<a rel="nofollow" href="https://github.com/home-assistant/core/issues/new?assignees=&amp;labels=&amp;template=bug_report.yml" class="external-link"><u>New Issue · home-assistant/core (</u></a><a href="http://github.com"><u>github.com</u></a><a rel="nofollow" href="https://github.com/home-assistant/core/issues/new?assignees=&amp;labels=&amp;template=bug_report.yml" class="external-link"><u>)</u></a></p>
 </li>
</ul>
<h3 style="text-align: start; " id="%E8%B7%9F%E8%B8%AA-issue"><strong>跟踪 Issue</strong></h3>
<ol>
 <li>
  <p style="">及时排查问题，给 Issue 打上对应的标签。</p>
 </li>
 <li>
  <p style="">不建议将 issue 当做工单使用。</p>
 </li>
 <li>
  <p style="">不要回复之后立马关闭 issue，可以等待提交 issue 的作者回复。</p>
 </li>
 <li>
  <p style="">不要在解决这个 issue 之前就关闭 issue。</p>
 </li>
 <li>
  <p style="">在任何时候，都不建议直接删除 issue 或者 issue 回复。除非十分有必要，比如留下了敏感信息。</p>
 </li>
 <li>
  <p style="">在 Issue 中交流的时候不要像使用 IM 即时交流工具一样交流。</p>
 </li>
 <li>
  <p style="">如果在后续的代码提交中已经修复了此问题，建议在 PR 中关联此 Issue（ 可以使用 <code>Fixes #xxx</code>），并在 Issue 中回复。</p>
 </li>
</ol>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/open-source-repo-issue</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fpraveen-thirumurugan-KPAQpJYzH0Y-unsplash.jpg&amp;size=m" type="image/jpeg" length="33869"/><category>分享</category><pubDate>Fri, 22 Mar 2024 02:51:00 GMT</pubDate></item><item><title><![CDATA[我的 2023]]></title><link>https://ryanc.cc/archives/2023</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E6%88%91%E7%9A%84%202023&amp;url=/archives/2023" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%85%B3%E4%BA%8E%E5%B7%A5%E4%BD%9C">关于工作</h2>
<p style="">今年是全职维护 Halo 的第二年， 也是 Halo 快速成长的两年，这两年 Halo 的成长可以说比以前任何时间段都要快（虽然一共才 5 年），这如果要放在以前非全职的时候，当前 Halo 的状态我们可能花个三四年都达不到，虽然目前 Halo 依旧有很多可优化的空间。</p>
<p style="">关于 Halo 2023 年的一些<strong>总结</strong>：</p>
<ol>
 <li>
  <p style="">一共发布了 12 个版本，截止到当前编写此文，即将发布 2.12。</p>
 </li>
 <li>
  <p style="">Star 达到 30k。</p>
 </li>
 <li>
  <p style="">终于上线了可用的应用市场，包括内置版，用户可以非常方便的下载和更新插件/主题。</p>
 </li>
 <li>
  <p style="">应用市场支持付费应用，这为我们带来了一部分收益，但远远没有达到养活我们自己的地步。</p>
 </li>
 <li>
  <p style="">插件和主题数量得到提升，也有了越来越多的社区开发者。</p>
 </li>
</ol>
<p style="">今年的一些<strong>规划</strong>：</p>
<ol>
 <li>
  <p style="">应用市场支持开发者入驻，包括发布付费应用。</p>
 </li>
 <li>
  <p style="">通过第一点，继续推进 Halo 的生态发展。</p>
 </li>
 <li>
  <p style="">尝试一些其他的商业模式，争取达到一个正向循环。</p>
 </li>
 <li>
  <p style="">对 Halo 本身的功能和稳定性进行持续优化。</p>
 </li>
 <li>
  <p style="">重视文档。</p>
 </li>
</ol>
<p style="">虽然 Halo 需要推进一些商业模式来得以存活，但我们最大的坚持就是保证 Halo 本身的纯净和优雅，可以从我们应用市场看出，即便是我们提供了服务的应用市场内置版本，也是通过插件的形式集成到 Halo 的，如果用户不喜欢，完全可以直接卸载，不会有任何影响。</p>
<p style="">一些<strong>反思</strong>，以下是我们需要一直反思的事情：</p>
<ol>
 <li>
  <p style="">Halo 的定位和面向群体。</p>
 </li>
 <li>
  <p style="">Halo 是否真的满足了<strong>建站</strong>的需求，还是仅仅是内容管理。</p>
 </li>
 <li>
  <p style="">基于第二点，为什么 Slogan 是突出的建站，但我们直到 2.12 还是没有针对页面进行一些有用的功能开发，比如可视化编辑网页内容。</p>
 </li>
 <li>
  <p style="">可持续的商业模式。</p>
 </li>
</ol>
<p style="">好了，以上是关于 Halo 的一些总结和反思，以下是我个人的一些工作终结，直接看图吧。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fgithub-wrapped-2023%2520%281%29.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">来年对我自己的期望：</p>
<ol>
 <li>
  <p style="">继续深耕技术，需要对巩固很多基础知识。</p>
 </li>
 <li>
  <p style="">改变对产品某些地方的思维方式，尝试一些新的东西。</p>
 </li>
 <li>
  <p style="">提高效率，需要加强对工作优先级的判断。</p>
 </li>
 <li>
  <p style="">开始了解一些运营和商业的知识，对于一个做技术的人来做，这一步真的很难迈出。</p>
 </li>
</ol>
<h2 style="" id="%E5%85%B3%E4%BA%8E%E7%94%9F%E6%B4%BB">关于生活</h2>
<p style="">依旧蓉飘中，这应该是是在成都的第 5 年了，成都虽然比不上北上广深那样的快节奏，但这主要还是分人吧，这两年基本上都投入到工作上了。</p>
<p style="">生活依旧很平淡，希望今年可以稍微改变一下。</p>
<p style="">今年最大的事应该就是结婚啦~</p>
<h2 style="" id="%E4%B8%80%E4%BA%9B%E5%88%86%E4%BA%AB">一些分享</h2>
<p style="">在这里分享一下在 2023 年用过的一些好软件、好的日用产品等。</p>
<h3 style="" id="app">App</h3>
<h4 style="" id="1.-raycast">1. Raycast</h4>
<hyperlink-card target="_blank" href="https://www.raycast.com/" theme="regular"><a href="https://www.raycast.com/" target="_blank">https://www.raycast.com/</a></hyperlink-card>
<p style=""></p>
<p style="">Raycast 其实已经用了很久了，从最开始发布的时候就一直在用，已经成为了日常必备，不管是用来启动 App，还是利用其中的插件来完成一些其他 App 的集成，都非常好用。尤其是这个产品的细节，非常打动我，这个产品是我学习的对象。</p>
<p style="">贴一张来自 Raycast 的年终使用报告吧。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fraycast-2023.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="2.-orbstack">2. OrbStack</h4>
<hyperlink-card target="_blank" href="https://orbstack.dev/" theme="regular"><a href="https://orbstack.dev/" target="_blank">https://orbstack.dev/</a></hyperlink-card>
<p style=""></p>
<p style="">一个 Docker 以及 Linux 虚拟机的管理软件，非常轻量，没有官方 Docker Desktop 那样臃肿和那么多问题。这目前也是我的日常必备了，尤其是 Linux 虚拟机，如果我需要在 Linux 上测试一些东西，直接创建一个新的 Linux 实例即可，非常快速。</p>
<h3 style="" id="%E5%BC%80%E6%BA%90%E8%BD%AF%E4%BB%B6%2F%E5%BA%93">开源软件/库</h3>
<h4 style="" id="1.-tanstack-query">1. TanStack Query</h4>
<hyperlink-card target="_blank" href="https://github.com/tanstack/query" theme="regular"><a href="https://github.com/tanstack/query" target="_blank">https://github.com/tanstack/query</a></hyperlink-card>
<p style=""></p>
<p style="">一个非常好用的数据状态管理库，在此之前，我在页面上获取数据基本都只是通过 Axios 请求，然后手动管理数据的状态，一旦页面复杂一点，数据的获取和管理将会比较复杂，代码也不够简洁。用了这个库之后，可以非常容易的做到：</p>
<ol>
 <li>
  <p style="">简化数据请求的逻辑，可以使用声明式（配置式）的写法完成数据的请求和管理。</p>
 </li>
 <li>
  <p style="">数据缓存、数据共享（组件之间）、重复请求去重。</p>
 </li>
 <li>
  <p style="">监听设备网络状态、窗口聚焦监听。</p>
 </li>
 <li>
  <p style="">请求重试，支持自行编写逻辑。</p>
 </li>
 <li>
  <p style="">定时请求，支持自行编写逻辑，这对异步接口（需要定时重新刷新数据）非常友好。</p>
 </li>
 <li>
  <p style="">分页和滚动加载。</p>
 </li>
 <li>
  <p style="">依赖请求。</p>
 </li>
</ol>
<p style="">类似的库还有 <hyperlink-inline-card target="_blank" href="https://github.com/vercel/swr" theme="inline"><a href="https://github.com/vercel/swr" target="_blank">https://github.com/vercel/swr</a></hyperlink-inline-card>，知道这个库是看了《<a rel="noopener noreferrer nofollow" href="https://blog.skk.moe/post/why-you-should-not-fetch-data-directly-in-use-effect/" target="_blank">为什么你不应该在 React 中直接使用 useEffect 从 API 获取数据</a>》，非常好的一篇文章，有兴趣的话，建议详细阅读一遍。此外，我也在公司技术分享上浅浅的介绍了一下这个库，反响还不错。</p>
<h4 style="" id="2.-vueuse">2. VueUse</h4>
<hyperlink-card target="_blank" href="https://github.com/vueuse/vueuse" theme="regular"><a href="https://github.com/vueuse/vueuse" target="_blank">https://github.com/vueuse/vueuse</a></hyperlink-card>
<p style=""></p>
<p style="">来自 Vue 和 Vite 的核心开发人员 <hyperlink-inline-card target="_blank" href="https://github.com/antfu" theme="inline"><a href="https://github.com/antfu" target="_blank">https://github.com/antfu</a></hyperlink-inline-card> 的库，这个库利用 Vue 3 的 Composition API 封装了一系列的工具，非常好用。在 Halo 中我们用的比较多的：</p>
<ol>
 <li>
  <p style=""><a rel="noopener noreferrer nofollow" href="https://vueuse.org/core/useLocalStorage/#uselocalstorage" target="_blank">useLocalStorage</a>：通常用于在浏览器临时保存一些用户偏好设置。</p>
 </li>
 <li>
  <p style=""><a rel="noopener noreferrer nofollow" href="https://vueuse.org/router/useRouteQuery/#useroutequery" target="_blank">useRouteQuery</a>：通常用于在地址栏保存数据列表的筛选项，方便在用户切换路由或者刷新页面之后可以回到之前的查询状态。</p>
 </li>
 <li>
  <p style=""><a rel="noopener noreferrer nofollow" href="https://vueuse.org/core/useFileDialog/#usefiledialog" target="_blank">useFileDialog</a>：用于简化上传文件的逻辑。</p>
 </li>
 <li>
  <p style=""><a rel="noopener noreferrer nofollow" href="https://vueuse.org/core/useEventListener/#useeventlistener" target="_blank">useEventListener</a>：再也不用担心在组件 unmounted 的时候取消注册事件。</p>
 </li>
</ol>
<p style="">非常钦佩 Anthony Fu 在开源社区做出的贡献，尤其是 Vue 和 Vite 生态。</p>
<h4 style="" id="3.-unplugin-icons">3. unplugin-icons</h4>
<hyperlink-card target="_blank" href="https://github.com/unplugin/unplugin-icons" theme="regular"><a href="https://github.com/unplugin/unplugin-icons" target="_blank">https://github.com/unplugin/unplugin-icons</a></hyperlink-card>
<p style=""></p>
<p style="">同样来自 <hyperlink-inline-card target="_blank" href="https://github.com/antfu" theme="inline"><a href="https://github.com/antfu" target="_blank">https://github.com/antfu</a></hyperlink-inline-card>，这是一个图标库，可以通过组件的方式引入，可以通过非常简单的配置上手。底层依赖于 <hyperlink-inline-card target="_blank" href="https://iconify.design/" theme="inline"><a href="https://iconify.design/" target="_blank">https://iconify.design/</a></hyperlink-inline-card> 作为图标集，基本涵盖了开源的常见图标集。除此之外，也可以通过配置来加载项目内的图标。</p>
<p style="">如果你使用 Tailwindcss，也可以尝试使用 <a rel="noopener noreferrer nofollow" href="https://github.com/jcamp-code/tailwindcss-plugin-icons" target="_blank">tailwindcss-plugin-icons</a>，同样依赖于 Iconify，这个库可以将 SVG 编译到最终的 CSS 产物中。这个库会在我写 Halo 主题的时候用到。</p>
<h3 style="" id="%E5%A5%BD%E7%89%A9">好物</h3>
<h4 style="" id="1.-huadn-%E8%8D%9E%E9%BA%A6%E8%88%92%E9%A2%88%E6%9E%95">1. Huadn 荞麦舒颈枕</h4>
<p style="">近两年饱受颈椎问题困扰，经常会感到颈椎痛和甚至头晕，严重的时候早上起床之后都无法低头，一低头就非常刺痛。已经记不清去了多少次医院了，中医和西医都去过，还做了好几次针灸，但都没有根本解决问题，一段时间之后又会复发。于是开始怀疑是枕头的问题，于是在京东上东找西找发现了这个枕头，已经使用了大概五个月了，已经完全没有再颈椎痛过，只是偶尔会在久坐之后感到颈椎稍微有点僵硬。</p>
<p style="">原来这么久的颈椎问题是因为睡眠！可能是因为之前的枕头睡着会让颈部悬空，而这个枕头会刚好拖住我的颈椎。</p>
<p style="">如果你也有颈椎问题，不妨试着换个枕头。</p>
<h4 style="" id="2.-%E4%BD%B3%E8%83%BD-eos-rp-%E7%9B%B8%E6%9C%BA">2. 佳能 EOS RP 相机</h4>
<p style="">在此之前，我一直以为我的 iPhone 拍照已经足够好，完全不需要相机，但购买之后发现相机拍出的质感是手机不能比的。不过 23 年没有太多机会出去玩，希望今年可以有空多出去走走。</p>
<h4 style="" id="3.-%E5%BE%95%E8%8A%AC%E5%90%B9%E9%A3%8E%E6%9C%BA">3. 徕芬吹风机</h4>
<p style="">非常佩服这家公司的产品，这个吹风机颜值高，转速快，可以让我早上早出门两分钟。</p>
<p style=""></p>
<p style="">以上就是我 2023 的一些总结，希望 2024 更好！</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/2023</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2023.png&amp;size=m" type="image/jpeg" length="73229"/><category>关于 Halo</category><category>日志</category><pubDate>Sat, 20 Jan 2024 15:58:00 GMT</pubDate></item><item><title><![CDATA[新起点丨开源建站工具 Halo 发布 2.0 版本]]></title><link>https://ryanc.cc/archives/halo-200-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E6%96%B0%E8%B5%B7%E7%82%B9%E4%B8%A8%E5%BC%80%E6%BA%90%E5%BB%BA%E7%AB%99%E5%B7%A5%E5%85%B7%20Halo%20%E5%8F%91%E5%B8%83%202.0%20%E7%89%88%E6%9C%AC&amp;url=/archives/halo-200-released" width="1" height="1" alt="" style="opacity:0;">
<p>2022 年 12 月 1 日，FIT2CLOUD 飞致云旗下开源建站项目 Halo（ <a href="http://github.com/halo-dev">github.com/halo-dev</a> ）正式发布 v2.0 版本。这是 Halo 项目继 v1.0 版本后的第二个里程碑版本，研发团队采用全新架构进行项目重写，实现了从单用户机制向多用户体系的转变，提供全新设计的插件机制和主题机制，改进了附件管理方式，为用户提供富文本编辑器，同时提供后台全局搜索能力。</p>
<p>Halo 是一款好用又强大的开源建站工具，它让你无需太多的技术知识就可以快速搭建一个博客、网站或者内容管理系统。</p>
<p>截至目前（2022 年 12 月 1 日），Halo 已经在 Docker Hub 获得了超过 150 万次下载，GitHub Star 数突破 24 k，并拥有一百多名社区贡献者。我们在此对所有参与到 Halo 产品及社区建设的朋友们表达由衷的感谢。</p>
<h2 id="%E4%B8%BB%E8%A6%81%E4%BA%AE%E7%82%B9%E5%8A%9F%E8%83%BD" tabindex="-1">主要亮点功能</h2>
<h3 id="%E5%A4%9A%E7%94%A8%E6%88%B7%E4%B8%8E%E6%9D%83%E9%99%90%E4%BD%93%E7%B3%BB" tabindex="-1">多用户与权限体系</h3>
<p>Halo 1.0 版本仅支持单管理员机制，这极大限制了 Halo 的多人使用场景。在 Halo 2.0 中，我们引入了多用户及 RBAC 权限体系，支持多用户同时登录管理 Halo，并且支持用户权限的细粒度控制，可以为不同的用户分配不同的权限，从而实现不同的用户角色。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E7%2594%25A8%25E6%2588%25B7%25E7%25AE%25A1%25E7%2590%2586.png&amp;size=m" alt="Halo 2.0 用户管理"></p>
<h3 id="%E7%81%B5%E6%B4%BB%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6" tabindex="-1">灵活可扩展的插件机制</h3>
<p>Halo 2.0 带来了全新设计的插件机制，这也是 2.0 版本底层架构变动的主要原因。在 Halo 1.0 时，我们因为无法对功能进行拓展，所以随着版本不断迭代会导致系统越来越臃肿。比如我们在 1.0 集成了市面上常见的云存储方案，但绝大部分用户都不会使用到所有的存储方案，这些功能对于 Halo 来说就变成了一种负担。在 Halo 2.0 中，我们将这些功能抽离出来，通过插件的形式进行集成，这样用户可以根据自己的需求自由选择，不再会因为一些不需要的功能而导致系统的臃肿。同时也可以实现不更新整个 Halo 应用，对插件进行单独更新，降低用户更新使用新功能的代价。</p>
<p>我们为插件机制提供了以下能力：</p>
<ul>
 <li>插件的动态加载、卸载、升级；</li>
 <li>自定义模型的接入，方便插件的数据存储；</li>
 <li>接入 Halo 的配置模块，方便插件的配置管理；</li>
 <li>以及拓展后端、管理端、主题端的能力。</li>
</ul>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E6%258F%2592%25E4%25BB%25B6%25E7%25AE%25A1%25E7%2590%2586.png&amp;size=m" alt="Halo 2.0 插件管理"></p>
<p>从上图可以看到：</p>
<ul>
 <li>我们将评论组件和搜索组件做成了插件，这是为了方便主题直接使用，而不需要主题单独为这两个功能进行开发；</li>
 <li>1.0 的友情链接管理功能也被抽离出来，成为了一个独立的通用插件；</li>
 <li>阿里云 OSS 的存储策略也使用插件提供，安装了这个插件即可将附件上传至阿里云 OSS；</li>
 <li>Umami 插件提供了对 Umami 的集成，可以在 Halo 控制台直接查看网站访问情况；</li>
 <li>Unsplash 插件提供了对 Unsplash 的集成，可以在编写内容的时候从 Unsplash 选择图片。</li>
</ul>
<p>相信随着我们对 Halo 的持续迭代和生态建设的持续投入，Halo 的插件生态会越来越丰富。</p>
<p>目前已支持 Halo 2.0 的插件可以访问 <a href="https://github.com/halo-sigs/awesome-halo">https://github.com/halo-sigs/awesome-halo</a> 查阅。</p>
<h3 id="%E5%85%A8%E6%96%B0%E7%9A%84%E4%B8%BB%E9%A2%98%E6%9C%BA%E5%88%B6" tabindex="-1">全新的主题机制</h3>
<p>Halo 2.0 在主题机制上，有以下主要改进：</p>
<ol>
 <li>使用 Thymeleaf 作为默认的模板引擎；</li>
 <li>支持主题预览，可以在不启用主题的情况下预览主题效果；</li>
 <li>支持多语言；</li>
 <li>全新的设置表单定义机制，支持更多的输入选项，支持表单验证和条件判断；</li>
 <li>全新设计的主题可视化设置界面，支持预览不同设备的效果，支持保存设置之后实时预览效果。</li>
</ol>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E4%25B8%25BB%25E9%25A2%2598%25E8%25AE%25BE%25E7%25BD%25AE%25E9%25A2%2584%25E8%25A7%2588.png&amp;size=m" alt="Halo 2.0 主题设置预览"></p>
<h3 id="%E5%85%A8%E6%96%B0%E7%9A%84%E9%BB%98%E8%AE%A4%E4%B8%BB%E9%A2%98" tabindex="-1">全新的默认主题</h3>
<p>我们为 Halo 2.0 提供了全新的默认主题，并命名为 Earth，我们计划在未来以太阳系成员的命名方式提供一系列的官方默认主题。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E9%25BB%2598%25E8%25AE%25A4%25E4%25B8%25BB%25E9%25A2%2598.jpg&amp;size=m" alt="Halo 2.0 默认主题"></p>
<h3 id="%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E9%99%84%E4%BB%B6%E7%AE%A1%E7%90%86" tabindex="-1">可扩展的附件管理</h3>
<p>在 Halo 1.0 时，社区用户呼声比较高的需求之一就是改进附件管理方式。在 2.0 版本，我们全新设计了附件的功能，支持分组管理、存储策略等功能。分组管理功能可以帮助用户更好地组织管理不同使用场景的附件。存储策略功能可以让用户定义多个不同的附件存储位置，同时也可以通过插件来拓展外部云存储，比如上面提到的阿里云 OSS。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E9%2599%2584%25E4%25BB%25B6%25E7%25AE%25A1%25E7%2590%2586.png&amp;size=m" alt="Halo 2.0 附件管理">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E9%2599%2584%25E4%25BB%25B6%25E4%25B8%258A%25E4%25BC%25A0.png&amp;size=m" alt="Halo 2.0 附件上传">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E5%25AD%2598%25E5%2582%25A8%25E7%25AD%2596%25E7%2595%25A5.png&amp;size=m" alt="Halo 2.0 存储策略"></p>
<p>此外，选择附件时还可以通过插件支持更多的附件来源，比如上面提到的 Unsplash 插件，可以做到在编辑内容的时候直接从 Unsplash 网站选择配图。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520Unsplash%2520%25E6%258F%2592%25E4%25BB%25B6.png&amp;size=m" alt="Halo 2.0 Unsplash 插件"></p>
<h3 id="%E5%BC%BA%E5%A4%A7%E7%9A%84%E5%AF%8C%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%99%A8" tabindex="-1">强大的富文本编辑器</h3>
<p>在 2.0，为了解决 Markdown 编辑器无法对一些复杂的排版场景进行支持的问题，我们默认提供了富文本编辑器，它能够很好地支持图片插入、表格、任务列表等。不过考虑到部分用户对 Markdown 有较强的需求，我们将在后续版本中提供对 Markdown 格式编辑的支持。除此之外，你也可以通过安装插件的方式使用你最喜欢的编辑器。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E7%25BC%2596%25E8%25BE%2591%25E5%2599%25A8.png&amp;size=m" alt="Halo 2.0 编辑器"></p>
<h3 id="%E4%BE%BF%E6%8D%B7%E7%9A%84%E5%90%8E%E5%8F%B0%E5%85%A8%E5%B1%80%E6%90%9C%E7%B4%A2" tabindex="-1">便捷的后台全局搜索</h3>
<p>我们在后台提供了全局搜索功能，可以快速搜索后台的页面、文章、附件、主题等资源。并且提供了快捷键（Ctrl + K，Mac 下为 Cmd + K）来快速打开搜索框，提高后台的操作效率。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E5%2590%258E%25E5%258F%25B0%25E5%2585%25A8%25E5%25B1%2580%25E6%2590%259C%25E7%25B4%25A2.png&amp;size=m" alt="Halo 2.0 后台全局搜索"></p>
<h3 id="%E6%94%AF%E6%8C%81%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E" tabindex="-1">支持搜索引擎</h3>
<p>在 Halo 1.0 的时候，我们搜索文章是通过 SQL 模糊匹配的方式来实现的，这种方式的搜索效率和准确性都不够理想。在 2.0 中，我们默认使用了 Apache Lucene 来提供搜索引擎的支持，也为主题端提供了通用的搜索框组件，大大提高了搜索功能的实用性。同样的，搜索引擎我们也支持通过插件来拓展，比如集成 Elasticsearch、MeiliSearch 等。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fwww.halo.run%2Fupload%2F2022%2F12%2FHalo%25202.0%2520%25E6%2590%259C%25E7%25B4%25A2%25E5%25BC%2595%25E6%2593%258E.png&amp;size=m" alt="Halo 2.0 搜索引擎"></p>
<h2 id="%E5%AE%8C%E6%95%B4%E5%8F%98%E6%9B%B4%E6%97%A5%E5%BF%97" tabindex="-1">完整变更日志</h2>
<p>关于 Halo 2.0 的完整特性和变更日志，请访问 GitHub Release 页面（<a href="https://github.com/halo-dev/halo/releases">https://github.com/halo-dev/halo/releases</a>）。</p>
<h2 id="%E6%8E%A5%E4%B8%8B%E6%9D%A5" tabindex="-1">接下来</h2>
<p>在 Halo 2.0 正式版发布之后，我们会继续完善 Halo 的功能和文档，并在每个月按时发布一个功能版本。同时将持续投入对 Halo 生态的建设，让用户能够更加方便的使用 Halo 搭建各式各样的网站，构建心中的理想站点。此外，我们也将在不久之后开启应用市场的开发，让用户获取主题、插件更加便捷。同时我们也会不断加大对前沿技术及用户体验的探索，让 Halo 朝着好用又强大的零代码建站工具的目标持续迈进。</p>
<p>Halo 此前的成绩离不开每一位参与者的贡献与支持，踏上这个新的起点，Halo 的未来也仍需各位共同努力。</p>]]></description><guid isPermaLink="false">/archives/halo-200-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FHalo2.jpg&amp;size=m" type="image/jpeg" length="23520"/><category>关于 Halo</category><pubDate>Thu, 1 Dec 2022 00:20:00 GMT</pubDate></item><item><title><![CDATA[Halo 1.5.0 发布]]></title><link>https://ryanc.cc/archives/halo-150-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Halo%201.5.0%20%E5%8F%91%E5%B8%83&amp;url=/archives/halo-150-released" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>距离我们 2020 年 9 月 24 号发布 1.4.0 已经过去了 545 天了，期间虽然有一些版本更新，但大多数都是 patch 修复版本。终于，在今天正式发布 1.5.0 版本。其中带来了大量的优化更新，下面为大家简单介绍一些亮点功能，详细更新日志可在本文末尾查阅。</p>
</blockquote>
<h2 id="%E7%89%88%E6%9C%AC%E4%BA%AE%E7%82%B9" tabindex="-1">版本亮点</h2>
<h3 id="%E6%96%87%E7%AB%A0%E8%A1%A8%E6%8B%86%E5%88%86" tabindex="-1">文章表拆分</h3>
<p>在此版本中，新增了 <code>contents</code> 表专门用于存储文章内容。因为在以前的版本中，当站点有大量文章的时候会出现明显拖慢页面渲染速度的情况，这是因为在之前的版本中，查询文章列表会将内容字段也包含在其中，这就会导致数据越多就会越慢。所以在这个版本中，原本的 <code>posts</code> 表就不再包含 <code>originalContent</code> 和 <code>formatContent</code> 字段，在列表查询的时候也不会包含。需要注意的是，如果你是从 1.4 版本升级的话，目前我们是没有自动清空这两个字段的内容的。当然，你可以手动清空，以获得更好的效果。</p>
<p>性能对比：</p>
<ul>
 <li>版本：1.4.17 vs 1.5.0</li>
 <li>测试数据：文章数 1000，每篇 4000 字符。</li>
 <li>测试工具：Apache Benchmark</li>
 <li>测试平台：MacBook Pro M1 Pro</li>
 <li>测试参数：<code>ab -c 100 -n 10000 http://localhost:8090/</code></li>
</ul>
<p>1.4.17：</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252015.55.46.png&amp;size=m" alt="Screen Shot 2022-03-23 at 15.55.46"></p>
<p>1.5.0：</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252016.03.46.png&amp;size=m" alt="Screen Shot 2022-03-23 at 16.03.46"></p>
<blockquote>
 <p>注意：此测试可能并不是特别严谨，仅供参考。</p>
</blockquote>
<h3 id="%E6%96%87%E7%AB%A0%E4%BF%9D%E5%AD%98%E9%80%BB%E8%BE%91%E4%BF%AE%E6%94%B9" tabindex="-1">文章保存逻辑修改</h3>
<p>目前后台已经修改为直接保存编辑器渲染的 html 内容，保证编写时和最终发布结果完全一致。另外，数学公式和 mermaid 图表已经针对此特性做了优化。数学公式仅需在前台引入 katex css 即可。mermaid 图表会被渲染为 svg 并保存，所以无需再引入 mermaid 依赖。</p>
<p>数学公式可以在前台引入：</p>
<pre><code class="language-html">&lt;link rel="stylesheet" href="https://unpkg.com/katex@0.12.0/dist/katex.min.css" /&gt;
</code></pre>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.41.51.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.41.51">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.42.22.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.42.22">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.44.14.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.44.14">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.44.24.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.44.24"></p>
<p>目前，我们已经统一使用 <a href="https://github.com/halo-dev/js-sdk/tree/master/packages/markdown-renderer">@halo-dev/markdown-renderer</a> 进行 Markdown 渲染。</p>
<h3 id="%E7%BC%96%E8%BE%91%E5%99%A8%E9%87%8D%E6%9E%84" tabindex="-1">编辑器重构</h3>
<p>编辑器编辑区域修改为 Codemirror 编辑器，支持 Markdown 语法高亮，浏览文章会更加方便。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.53.18.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.53.18"></p>
<p>编辑器可以通过快捷键打开图片选择，目前也已经支持多选图片和选择之后直接插入文章。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.52.41.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.52.41"></p>
<p>预览区域支持代码块高亮。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.51.53.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.51.53"></p>
<p>支持更多 Markdown 标记语法。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.54.15.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.54.15"></p>
<p>表格编辑体验优化。众所周知，使用 Markdown 语法编写表格的体验是非常难受的，不仅编写困难，而且格式会非常难以阅读。所以我们使用了一个叫做 <a href="https://github.com/susisu/mte-kernel">mte-kernel</a> 的库来解决这个问题，不仅可以让编辑体验升级，而且会自动格式化表格。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FCleanShot%25202022-03-23%2520at%252014.54.38.gif&amp;size=m" alt="CleanShot 2022-03-23 at 14.54.38"></p>
<h3 id="%E6%A0%87%E7%AD%BE%E6%94%AF%E6%8C%81%E8%AE%BE%E7%BD%AE%E9%A2%9C%E8%89%B2" tabindex="-1">标签支持设置颜色</h3>
<p>在这个版本中，我们添加了对标签设置颜色的功能，灵感来自于 GitHub Issues 的标签颜色。这样可以比较直观地区分不同的标签。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.13.04.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.13.04">
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.13.17.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.13.17"></p>
<h3 id="%E6%96%87%E7%AB%A0%E7%AE%A1%E7%90%86%E9%87%8D%E6%9E%84" tabindex="-1">文章管理重构</h3>
<p>重构了批量操作的逻辑，目前已经不需要提前通过文章状态筛选文章之后才能批量选择。可以任意批量选择之后进行文章状态的批量操作。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.16.14.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.16.14"></p>
<p>文章列表不再展示回收站的文章，改为单独为回收站提供一个入口。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.16.00.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.16.00"></p>
<p>文章设置弹窗进行了重构，改为更直观地显示方式，并且可以在弹窗中进行上下篇的切换。可以非常快速的预览文章设置并进行对文章设置的修改。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.16.39.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.16.39"></p>
<h3 id="%E9%99%84%E4%BB%B6%E7%AE%A1%E7%90%86%E9%87%8D%E6%9E%84" tabindex="-1">附件管理重构</h3>
<p>目前支持将鼠标放在附件项即可显示勾选图标，不再需要提前进入批量管理功能。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.15.17.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.15.17"></p>
<p>附件详情也和文章设置一样，修改为弹窗的形式，可以进行上下项的快速切换，方便预览以及进行修改删除等操作。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.15.32.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.15.32"></p>
<h3 id="%E4%B8%BB%E9%A2%98%E8%AE%BE%E7%BD%AE%E9%87%8D%E6%9E%84" tabindex="-1">主题设置重构</h3>
<p>主题设置由原来的全屏抽屉形式改为单独的页面。并优化了布局，不再显示主题缩略图，将更多的空间留给设置表单。同时在界面上也展示了主题的相关信息。</p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.17.16.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.17.16"></p>
<p><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2022%2F03%2FScreen%2520Shot%25202022-03-23%2520at%252014.18.44.png&amp;size=m" alt="Screen Shot 2022-03-23 at 14.18.44"></p>
<h2 id="%E7%89%88%E6%9C%AC%E6%97%A5%E5%BF%97" tabindex="-1">版本日志</h2>
<h3 id="breaking-changes" tabindex="-1">Breaking changes</h3>
<ul>
 <li>
  <p>评论表单中的邮箱地址不再作为必填项。 <a href="https://github.com/halo-dev/halo/pull/1535">halo-dev/halo#1535</a> <a href="https://github.com/jerry-shao">@jerry-shao</a></p>
 </li>
 <li>
  <p>重构文章表结构。</p>
  <ul>
   <li>提供单独的 <code>contents</code> 表存储文章内容，将不再使用 <code>posts</code> 表中的 <code>originalContent</code> 和 <code>formatContent</code> 字段。 <a href="https://github.com/halo-dev/halo/pull/1617">halo-dev/halo#1617</a> <a href="https://github.com/guqing">@guqing</a></li>
   <li><code>formatContent</code> 已经废弃，将在下一个大版本中移除。后续使用 <code>content</code> 字段代替。 <a href="https://github.com/halo-dev/halo/pull/1617">halo-dev/halo#1617</a> <a href="https://github.com/guqing">@guqing</a></li>
   <li>提供 <code>content_patch_logs</code> 表用于存储变更记录。 <a href="https://github.com/halo-dev/halo/pull/1617">halo-dev/halo#1617</a> <a href="https://github.com/guqing">@guqing</a></li>
  </ul>
 </li>
 <li>
  <p>后台使用 @halo-dev/admin-api 进行接口请求。 <a href="https://github.com/halo-dev/halo-admin/pull/378">halo-dev/halo-admin#378</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/guqing">@guqing</a></p>
 </li>
 <li>
  <p>修改后台页面标题后缀，由 <code>Halo Dashboard</code> 改为 <code>Halo</code>。 <a href="https://github.com/halo-dev/halo-admin/pull/426">halo-dev/halo-admin#426</a> <a href="https://github.com/ruibaby">@ruibaby</a></p>
 </li>
 <li>
  <p>默认后台布局改为左侧菜单布局。 <a href="https://github.com/halo-dev/halo-admin/pull/441">halo-dev/halo-admin#441</a> <a href="https://github.com/ruibaby">@ruibaby</a></p>
 </li>
 <li>
  <p>重构文章/自定义页面编辑逻辑，改为保存前端渲染内容，放弃后端渲染。 <a href="https://github.com/halo-dev/halo-admin/pull/439">halo-dev/halo-admin#439</a> <a href="https://github.com/halo-dev/halo-admin/pull/440">halo-dev/halo-admin#440</a> <a href="https://github.com/halo-dev/halo-admin/pull/449">halo-dev/halo-admin#449</a> <a href="https://github.com/halo-dev/halo/pull/1668">halo-dev/halo#1668</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/guqing">@guqing</a></p>
 </li>
 <li>
  <p>Content API 的评论列表接口不再返回 <code>ipAddress</code> 和 <code>email</code> 字段。 <a href="https://github.com/halo-dev/halo/pull/1729">halo-dev/halo#1729</a> <a href="https://github.com/guqing">@guqing</a></p>
 </li>
</ul>
<h3 id="features" tabindex="-1">Features</h3>
<ul>
 <li>Content API 添加获取所有图库图片分组的接口（PhotoController#listTeams）。 <a href="https://github.com/halo-dev/halo/pull/1515">halo-dev/halo#1515</a> <a href="https://github.com/fuzui">@fuzui</a></li>
 <li>Content API 添加根据 <code>themeId</code> 获取主题详情和设置的接口。 <a href="https://github.com/halo-dev/halo/pull/1660">halo-dev/halo#1660</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>图库支持点赞。 <a href="https://github.com/halo-dev/halo/pull/1537">halo-dev/halo#1537</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>文章标签支持设置颜色。 <a href="https://github.com/halo-dev/halo/pull/1566">halo-dev/halo#1566</a> <a href="https://github.com/halo-dev/halo-admin/pull/395">halo-dev/halo-admin#395</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>重构后台文章分类管理，支持排序。 <a href="https://github.com/halo-dev/halo/pull/1650">halo-dev/halo#1650</a> <a href="https://github.com/halo-dev/halo/pull/1657">halo-dev/halo#1657</a> <a href="https://github.com/halo-dev/halo-admin/pull/435">halo-dev/halo-admin#435</a> <a href="https://github.com/lan-yonghui">@lan-yonghui</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>文章设置界面支持重新生成别名。 <a href="https://github.com/halo-dev/halo-admin/pull/368">halo-dev/halo-admin#368</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>Admin API 提供批量删除图库图片的接口。 <a href="https://github.com/halo-dev/halo/pull/1680">halo-dev/halo#1680</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>Admin API 提供批量更新图库图片的接口。 <a href="https://github.com/halo-dev/halo/pull/1679">halo-dev/halo#1679</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台评论管理提供日志评论管理的界面。 <a href="https://github.com/halo-dev/halo-admin/pull/480">halo-dev/halo-admin#480</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台菜单分组支持修改分组名。 <a href="https://github.com/halo-dev/halo-admin/pull/499">halo-dev/halo-admin#499</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台个人资料头像修改支持输入链接。 <a href="https://github.com/halo-dev/halo-admin/pull/505">halo-dev/halo-admin#505</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台待审核评论弹框点击评论支持进入评论管理。 <a href="https://github.com/halo-dev/halo-admin/pull/517">halo-dev/halo-admin#517</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>支持配置 Redis 缓存。 <a href="https://github.com/halo-dev/halo/pull/1751">halo-dev/halo#1751</a> <a href="https://github.com/luoxmc">@luoxmc</a> <a href="https://github.com/guqing">@guqing</a></li>
</ul>
<h3 id="improvements" tabindex="-1">Improvements</h3>
<ul>
 <li>更新 Logo。 <a href="https://github.com/halo-dev/halo-admin/pull/366">halo-dev/halo-admin#366</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构附件上传的文件命名逻辑，文件作为第一次上传的时候文件名不添加随机字符串。 <a href="https://github.com/halo-dev/halo/pull/1500">halo-dev/halo#1500</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>优化后台编辑文章时的预览体验，预览文章或临时保存内容不会修改已经发布的内容。 <a href="https://github.com/halo-dev/halo/pull/1617">halo-dev/halo#1617</a> <a href="https://github.com/halo-dev/halo-admin/pull/439">halo-dev/halo-admin#439</a> <a href="https://github.com/guqing">@guqing</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台主题设置界面，提供单独的设置页面，支持展示主题的相关信息。 <a href="https://github.com/halo-dev/halo-admin/pull/380">halo-dev/halo-admin#380</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>弱化后台登录页面的动画效果。 <a href="https://github.com/halo-dev/halo-admin/pull/369">halo-dev/halo-admin#369</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化后台附件管理列表预览图片样式。 <a href="https://github.com/halo-dev/halo-admin/pull/374">halo-dev/halo-admin#374</a> <a href="https://github.com/halo-dev/halo-admin/pull/382">halo-dev/halo-admin#382</a> <a href="https://github.com/623337308">@623337308</a> <a href="https://github.com/cetr">@cetr</a></li>
 <li>重构后台附件详情界面，取消原有抽屉的设计，改为弹窗。 <a href="https://github.com/halo-dev/halo-admin/pull/375">halo-dev/halo-admin#375</a> <a href="https://github.com/halo-dev/halo-admin/pull/381">halo-dev/halo-admin#381</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台文章/自定义页面设置界面，取消原有抽屉的设计，改为弹窗。 <a href="https://github.com/halo-dev/halo-admin/pull/376">halo-dev/halo-admin#376</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台操作日志列表界面，取消原有抽屉的设计，提供单独的页面。 <a href="https://github.com/halo-dev/halo-admin/pull/419">halo-dev/halo-admin#419</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台图片选择弹框组件，支持直接插入到编辑器，支持多选。 <a href="https://github.com/halo-dev/halo-admin/pull/420">halo-dev/halo-admin#420</a> <a href="https://github.com/halo-dev/halo-admin/pull/421">halo-dev/halo-admin#421</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台文章设置选择标签列表改为根据名称排序。 <a href="https://github.com/halo-dev/halo-admin/pull/429">halo-dev/halo-admin#429</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化后台附件管理中批量操作附件的逻辑。 <a href="https://github.com/halo-dev/halo-admin/pull/431">halo-dev/halo-admin#431</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>后台使用重构版本的编辑器，编辑区域支持高亮语法，优化数学公式和图表等渲染，优化表格的编辑体验。 <a href="https://github.com/halo-dev/halo-admin/pull/447">halo-dev/halo-admin#447</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化文章加密和分类加密的逻辑。 <a href="https://github.com/halo-dev/halo/pull/1678">halo-dev/halo#1678</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>优化后台登录页面样式。 <a href="https://github.com/halo-dev/halo-admin/pull/456">halo-dev/halo-admin#456</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台文章评论列表弹窗。 <a href="https://github.com/halo-dev/halo-admin/pull/463">halo-dev/halo-admin#463</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台图库管理页面，支持批量操作图片以及批量从附件库添加图片。 <a href="https://github.com/halo-dev/halo-admin/pull/468">halo-dev/halo-admin#468</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台文章管理页面，文章列表将不再展示回收站状态的文章，提供单独的回收站入口。 <a href="https://github.com/halo-dev/halo-admin/pull/475">halo-dev/halo-admin#475</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化后台文章/自定义页面设置的保存逻辑，提供转为发布/草稿的按钮。保存按钮不再影响到文章状态。 <a href="https://github.com/halo-dev/halo-admin/pull/476">halo-dev/halo-admin#476</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化后台折叠菜单的体验，解决折叠时 Logo 和 菜单动画不同步的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/493">halo-dev/halo-admin#493</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>缓存后台折叠菜单的状态，刷新页面不再会恢复到初始状态。 <a href="https://github.com/halo-dev/halo-admin/pull/493">halo-dev/halo-admin#493</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>优化后台判断是否初始化的逻辑，修改为每次页面加载只请求一次，切换路由不再请求。 <a href="https://github.com/halo-dev/halo-admin/pull/495">halo-dev/halo-admin#495</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构后台主题设置保存预览功能。 <a href="https://github.com/halo-dev/halo-admin/pull/502">halo-dev/halo-admin#502</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>重构日志发布功能，使用 Markdown 编辑器替换 textarea，并支持保存前端渲染的 Markdown 结果。 <a href="https://github.com/halo-dev/halo/pull/1739">halo-dev/halo#1739</a> <a href="https://github.com/halo-dev/halo-admin/pull/506">halo-dev/halo-admin#506</a> <a href="https://github.com/guqing">@guqing</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
</ul>
<h3 id="bug-fixes" tabindex="-1">Bug Fixes</h3>
<ul>
 <li>修复修改加密文章或分类时没有清除用户访问权限的问题。 <a href="https://github.com/halo-dev/halo/pull/1540">halo-dev/halo#1540</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>修复重置密码没有校验密码长度的问题。 <a href="https://github.com/halo-dev/halo/pull/1636">halo-dev/halo#1636</a> <a href="https://github.com/halo-dev/halo-admin/pull/403">halo-dev/halo-admin#403</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>修复后台文章设置中无法仅选择父级分类的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/367">halo-dev/halo-admin#367</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>修复后台菜单管理中移动菜单项到其他分组的时候，导致子菜单丢失的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/422">halo-dev/halo-admin#422</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>更新默认主题的 submodule 提交，修复模板中部分因为数字中带逗号导致的渲染异常。 <a href="https://github.com/halo-dev/halo/pull/1682">halo-dev/halo#1682</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>修复评论默认头像因为修改了默认类型但 options 接口没有返回字段导致评论头像无法显示的问题。 <a href="https://github.com/halo-dev/halo/pull/1692">halo-dev/halo#1692</a> <a href="https://github.com/lan-yonghui">@lan-yonghui</a></li>
 <li>修复使用 leveldb 的情况下，解析错误而没有清空缓存导致无法正常使用系统的问题。 <a href="https://github.com/halo-dev/halo/pull/1695">halo-dev/halo#1695</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>修复后台在文章编辑页面切换左侧菜单收缩的时候出现的样式异常。 <a href="https://github.com/halo-dev/halo-admin/pull/465">halo-dev/halo-admin#465</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>修复当前版本如果为 <code>alpha</code> 版本，安装主题无法通过版本验证的问题。 <a href="https://github.com/halo-dev/halo/pull/1705">halo-dev/halo#1705</a> <a href="https://github.com/JohnNiang">@JohnNiang</a></li>
 <li>修复评论部分因为没有添加事务，导致批量删除评论等操作时报错的问题。 <a href="https://github.com/halo-dev/halo/pull/1716">halo-dev/halo#1716</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li>修复后台点击后台 Halo Logo 进入开发者选项过快可能会导致计数为负的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/492">halo-dev/halo-admin#492</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>修复后台附件列表分页之后，可能会导致无法正常更新图片 dom 导致图片显示为上一页图片的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/496">halo-dev/halo-admin#496</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li>修复后台分类/标签/友情链接修改表单切换修改对象之后没有清除表单验证的问题。 <a href="https://github.com/halo-dev/halo-admin/pull/501">halo-dev/halo-admin#501</a> <a href="https://github.com/halo-dev/halo-admin/pull/503">halo-dev/halo-admin#503</a> <a href="https://github.com/Yorksh1re">@Yorksh1re</a></li>
 <li>修复当前版本如果为 <code>alpha</code> 版本，安装主题无法通过版本验证的问题。 <a href="https://github.com/halo-dev/halo/pull/1747">halo-dev/halo#1747</a> <a href="https://github.com/JohnNiang">@JohnNiang</a></li>
</ul>
<h3 id="dependencies" tabindex="-1">Dependencies</h3>
<ul>
 <li>升级 Spring Boot 版本。 <a href="https://github.com/halo-dev/halo/pull/1635">halo-dev/halo#1635</a> <a href="https://github.com/halo-dev/halo/pull/1677">halo-dev/halo#1677</a> <a href="https://github.com/ruibaby">@ruibaby</a> <a href="https://github.com/JohnNiang">@JohnNiang</a></li>
 <li>升级 Gradle 版本到 7.4。 <a href="https://github.com/halo-dev/halo/pull/1697">halo-dev/halo#1697</a> <a href="https://github.com/guqing">@guqing</a></li>
 <li><code>halo-dev/halo-admin</code> 常规依赖升级。 <a href="https://github.com/halo-dev/halo-admin/pull/453">halo-dev/halo-admin#453</a> <a href="https://github.com/halo-dev/halo-admin/pull/513">halo-dev/halo-admin#513</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
 <li><code>halo-dev/halo-admin</code> 修改用于切换后台样式的 less 依赖 CDN 为 unpkg。</li>
 <li>后台更新 @halo-dev/editor 版本。 <a href="https://github.com/halo-dev/halo-admin/pull/507">halo-dev/halo-admin#507</a> <a href="https://github.com/ruibaby">@ruibaby</a></li>
</ul>
<hr>
<p><a href="https://github.com/halo-dev/halo/compare/v1.4.17...v1.5.0">halo-dev/halo@<tt>v1.4.17…v1.5.0</tt></a></p>
<h2 id="%E7%9B%B8%E5%85%B3%E4%BF%A1%E6%81%AF" tabindex="-1">相关信息</h2>
<ul>
 <li>GitHub Release：<a href="https://github.com/halo-dev/halo/releases/tag/v1.5.0">https://github.com/halo-dev/halo/releases/tag/v1.5.0</a></li>
 <li>升级文档：<a href="https://docs.halo.run/getting-started/upgrade">https://docs.halo.run/getting-started/upgrade</a></li>
 <li>论坛交流：<a href="https://bbs.halo.run/d/2227-halo-150">https://bbs.halo.run/d/2227-halo-150</a></li>
</ul>]]></description><guid isPermaLink="false">/archives/halo-150-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2022%2F03%2F1.5.0.png&amp;size=m" type="image/jpeg" length="207314"/><category>关于 Halo</category><pubDate>Fri, 25 Mar 2022 14:19:00 GMT</pubDate></item><item><title><![CDATA[Halo 1.4.10 发布]]></title><link>https://ryanc.cc/archives/halo-1410-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Halo%201.4.10%20%E5%8F%91%E5%B8%83&amp;url=/archives/halo-1410-released" width="1" height="1" alt="" style="opacity:0;">
<h2 id="features" tabindex="-1">Features</h2>
<ul>
 <li>编辑器支持脚注语法。<a href="https://github.com/halo-dev/halo/pull/1406">halo-dev/halo#1406</a> <a href="https://github.com/halo-dev/halo-admin/pull/341">halo-dev/halo-admin#341</a></li>
</ul>
<h2 id="improvements" tabindex="-1">Improvements</h2>
<ul>
 <li>优化文章字数计算。<a href="https://github.com/halo-dev/halo/pull/1354">halo-dev/halo#1354</a></li>
 <li>Content Api 中的获取文章列表支持传入关键字和分类 id 筛选项。<a href="https://github.com/halo-dev/halo/pull/1373">halo-dev/halo#1373</a></li>
 <li>优化导入 Markdown 时，对多级分类的处理。<a href="https://github.com/halo-dev/halo/pull/1380">halo-dev/halo#1380</a></li>
</ul>
<h2 id="security-fixes" tabindex="-1">Security Fixes</h2>
<ul>
 <li>修复 Freemarker SSTI 漏洞。<a href="https://github.com/halo-dev/halo/pull/1402">halo-dev/halo#1402</a> <a href="https://github.com/halo-dev/halo/pull/1427">halo-dev/halo#1427</a> Thanks <a href="https://github.com/LazyMaple">@LazyMaple</a> <a href="https://github.com/5wimming">@5wimming</a></li>
</ul>
<h2 id="bug-fixes" tabindex="-1">Bug Fixes</h2>
<ul>
 <li>修复在分类文章列表可以显示私密文章的问题。<a href="https://github.com/halo-dev/halo/pull/1379">halo-dev/halo#1379</a></li>
 <li>修复使用后台的小工具数据导出迁移后分类密码消失的问题。<a href="https://github.com/halo-dev/halo/pull/1390">halo-dev/halo#1390</a></li>
 <li>修复在站点初始化的时候，<code>全局绝对路径</code> 选项设置错误的问题。<a href="https://github.com/halo-dev/halo/pull/1396">halo-dev/halo#1396</a></li>
 <li>修复 Content Api 的文章点赞接口限流没有按照文章 id 做处理的问题。<a href="https://github.com/halo-dev/halo/pull/1410">halo-dev/halo#1410</a></li>
 <li>修复回收站的文章可以访问的问题。<a href="https://github.com/halo-dev/halo/pull/1414">halo-dev/halo#1414</a></li>
 <li>修复后台评论回复时，输入框无法输入空格的问题。<a href="https://github.com/halo-dev/halo-admin/pull/322">halo-dev/halo-admin#322</a></li>
 <li>修复后台菜单管理中菜单项的链接过长会导致挡住操作按钮的问题。<a href="https://github.com/halo-dev/halo-admin/pull/328">halo-dev/halo-admin#328</a></li>
 <li>修复后台日志管理中长文本无法换行的问题。<a href="https://github.com/halo-dev/halo-admin/pull/330">halo-dev/halo-admin#330</a></li>
 <li>修复后台在登录页面无法通过回车键进行登录的问题。<a href="https://github.com/halo-dev/halo-admin/pull/332">halo-dev/halo-admin#332</a></li>
</ul>
<h2 id="upgrade-guide" tabindex="-1">Upgrade guide</h2>
<p><a href="https://docs.halo.run/install/upgrade">https://docs.halo.run/install/upgrade</a></p>]]></description><guid isPermaLink="false">/archives/halo-1410-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1.4.10.png&amp;size=m" type="image/jpeg" length="406768"/><category>关于 Halo</category><pubDate>Tue, 20 Jul 2021 11:13:00 GMT</pubDate></item><item><title><![CDATA[生态公园 ~ 2021-03-27]]></title><link>https://ryanc.cc/archives/eco-park-2021-03-27</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E7%94%9F%E6%80%81%E5%85%AC%E5%9B%AD%20~%202021-03-27&amp;url=/archives/eco-park-2021-03-27" width="1" height="1" alt="" style="opacity:0;">
<div data-type="gallery" data-group-size="3" data-layout="auto" data-gap="0">
 <div style="display: grid; gap: 0px;">
  <div data-type="gallery-group" style="display: flex; flex-direction: row; justify-content: center; gap: 0px;">
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9552-d22d2ef8d6fc4fe9812f036092607564.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9553-b170b0d509744929b56689344e08af57.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9558-f640b9698db3423f96b49f77a91fefdc.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
  </div>
  <div data-type="gallery-group" style="display: flex; flex-direction: row; justify-content: center; gap: 0px;">
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9565-b73ec13bf8994a68b1982753e87f418b.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9573-ed2803136b5d427b83b3af3078e19b79.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9572-6d7ebd6d199547d8b9395cd64db9b3be.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
  </div>
  <div data-type="gallery-group" style="display: flex; flex-direction: row; justify-content: center; gap: 0px;">
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9582-956a40487ee6453bbd973277f8c26903.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9584-205978133a7e446a9cd39d23acaee00b.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9588-303d8ffb10934401abab89a6ade98b89.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
  </div>
  <div data-type="gallery-group" style="display: flex; flex-direction: row; justify-content: center; gap: 0px;">
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9586-f6242467a25b4e8e8a6719ad34945aeb.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9598-d4fa4c2dc2e643a78653623c0b2cc9ce.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
   <div style="flex: 1.3333333333333333 1 0%;" data-aspect-ratio="1.3333333333333333">
    <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2021%2F03%2FIMG_9599-b3296c0b098d496faf8827101f2a6e82.jpeg&amp;size=m" data-type="gallery-image" style="width: 100%; height: 100%; margin: 0; object-fit: cover;">
   </div>
  </div>
 </div>
</div>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/eco-park-2021-03-27</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2021%2F03%2FIMG_9573-ed2803136b5d427b83b3af3078e19b79.jpeg&amp;size=m" type="image/jpeg" length="14792"/><category>日常</category><pubDate>Sat, 27 Mar 2021 11:23:00 GMT</pubDate></item><item><title><![CDATA[Halo 1.4.6 发布]]></title><link>https://ryanc.cc/archives/halo-146-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Halo%201.4.6%20%E5%8F%91%E5%B8%83&amp;url=/archives/halo-146-released" width="1" height="1" alt="" style="opacity:0;">
<h2 id="features">Features</h2>
<ul>
 <li>主题编写支持继承以及 block 特性。<a href="https://github.com/halo-dev/halo/pull/1295">halo-dev/halo#1295</a></li>
</ul>
<h2 id="improvements">Improvements</h2>
<ul>
 <li>重构初始化页面，目前分为全新安装和数据导入。从这个版本开始，数据导入将导入所有数据。<a href="https://github.com/halo-dev/halo-admin/pull/296">halo-dev/halo-admin#296</a></li>
 <li>优化后台菜单管理的排序拖动体验。<a href="https://github.com/halo-dev/halo-admin/issues/291">halo-dev/halo-admin#291</a></li>
 <li>移除后台设置项字符数的限制。<a href="https://github.com/halo-dev/halo/pull/1287">halo-dev/halo#1287</a></li>
</ul>
<h2 id="bug-fixes">Bug Fixes</h2>
<ul>
 <li>修复后台上传组件格式限制不生效的问题。<a href="https://github.com/halo-dev/halo-admin/pull/296">halo-dev/halo-admin#296</a></li>
 <li>修复后台新建文章页面，点击浏览器回退键不提示保存的问题。<a href="https://github.com/halo-dev/halo-admin/pull/302">halo-dev/halo-admin#302</a></li>
 <li>修复后台备份无法下载的问题。<a href="https://github.com/halo-dev/halo/pull/1278">halo-dev/halo#1278</a></li>
 <li>修复 1.4.5 版本中前台页面渲染不完整的问题。<a href="https://github.com/halo-dev/halo/pull/1301">halo-dev/halo#1301</a></li>
 <li>修复上传某些格式的图片因为缩略图生成失败导致上传错误的问题。<a href="https://github.com/halo-dev/halo/pull/1298">halo-dev/halo#1298</a></li>
 <li>修复 Swagger 文档分页数据字段和实际不一致的问题。<a href="https://github.com/halo-dev/halo/pull/1277">halo-dev/halo#1277</a></li>
 <li>修复 1.4.5 版本中通过主题包升级主题失败的问题。<a href="https://github.com/halo-dev/halo/pull/1284">halo-dev/halo#1284</a></li>
</ul>
<h2 id="upgrade-guide">Upgrade guide</h2>
<p><a href="https://docs.halo.run/install/upgrade">https://docs.halo.run/install/upgrade</a></p>]]></description><guid isPermaLink="false">/archives/halo-146-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F6f9b1e9e-2a34-4b8c-9e75-0fb8dcc04276.png&amp;size=m" type="image/jpeg" length="224799"/><category>关于 Halo</category><pubDate>Sun, 7 Mar 2021 15:18:00 GMT</pubDate></item><item><title><![CDATA[Halo 1.4.3 发布]]></title><link>https://ryanc.cc/archives/halo-143-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Halo%201.4.3%20%E5%8F%91%E5%B8%83&amp;url=/archives/halo-143-released" width="1" height="1" alt="" style="opacity:0;">
<p>随着这个版本的发布，我们的官网和文档也发生了一些变更。变更之后和 Halo 相关的站点：</p>
<ul>
 <li>官网：<a href="https://halo.run">https://halo.run</a></li>
 <li>文档：<a href="https://docs.halo.run">https://docs.halo.run</a></li>
 <li>论坛：<a href="https://bbs.halo.run">https://bbs.halo.run</a></li>
 <li>主题仓库：<a href="https://halo.run/themes.html">https://halo.run/themes.html</a></li>
</ul>
<h2 id="breaking-changes">Breaking changes</h2>
<ul>
 <li>此版本不再支持 JRE 1.8，更新到此版本需要 JRE 11 或以上版本。#1184</li>
 <li>移除 Redis Cache Store，如有配置该缓存方式，请先修改为其他方式再进行升级。#1190</li>
</ul>
<h2 id="features">Features</h2>
<ul>
 <li>支持 <code>ID 别名型</code> 文章路径类型。#1173</li>
 <li>支持自定义页面的路径层级设置。#1177</li>
 <li>Content API 新增 <code>日志点赞</code>，<code>文章上下页</code> 接口。#1176</li>
 <li>支持导出所有文章为 Markdown 文件。#1199</li>
</ul>
<h2 id="improvements">Improvements</h2>
<ul>
 <li>更友好的异常日志追踪。#1191</li>
 <li>日志发布不再限制字数。#1203</li>
 <li>优化后台菜单拖动排序体验。halo-dev/halo-admin#267</li>
 <li>优化后台文章编辑页面的布局。halo-dev/halo-admin#286</li>
</ul>
<h2 id="bug-fixes">Bug Fixes</h2>
<ul>
 <li>修复定时删除回收站文章功能无效的问题。#1207</li>
 <li>修复上传主题包更新主题失败的问题。#1209</li>
 <li>修复自动转化文章标题为别名时，将非中文字符分割的问题。halo-dev/halo-admin#273</li>
 <li>修复设置 MFA 登录校验器之后，登录页面偶发无法显示 MFA 验证码输入框的问题。halo-dev/halo-admin#276</li>
</ul>
<h2 id="upgrade-guide">Upgrade guide</h2>
<p><a href="https://docs.halo.run/install/upgrade">https://docs.halo.run/install/upgrade</a></p>]]></description><guid isPermaLink="false">/archives/halo-143-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fd031fc1f-84fd-4058-9222-9a772d94ab1e.png&amp;size=m" type="image/jpeg" length="623694"/><category>关于 Halo</category><pubDate>Wed, 3 Feb 2021 04:01:00 GMT</pubDate></item><item><title><![CDATA[Halo 1.4.0 发布]]></title><link>https://ryanc.cc/archives/halo-140-released</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Halo%201.4.0%20%E5%8F%91%E5%B8%83&amp;url=/archives/halo-140-released" width="1" height="1" alt="" style="opacity:0;">
<h2 id="feature">Feature</h2>
<ul>
 <li>支持静态存储重命名和修改文件内容。#819</li>
 <li>所有附件列表均支持右键复制图片链接。halo-dev/halo-admin#180</li>
 <li>开发者选项中的实时日志支持自动滚动到最新的日志。</li>
 <li>在线下载主题支持选择分支和 release。#515 #592 #835</li>
 <li>评论内容支持显示 html 文本。halo-dev/halo-admin#222</li>
 <li>文章新增 <code>wordCount</code> 字段，用于统计字数。#965</li>
 <li>文章编辑支持自动将文章标题的拼音设置为别名。halo-dev/halo-admin#235</li>
 <li>重构登录页面，并且支持在登录状态失效后弹出登录框，而不是直接跳转到登录页面，防止正在编辑中的文章丢失。halo-dev/halo-admin#238</li>
 <li>预览草稿的时候，不再会增加访问量。#834</li>
 <li>Content API 支持使用文章或者页面的 slug 获取文章信息。#1044</li>
</ul>
<h2 id="change">Change</h2>
<ul>
 <li>为部分表单添加表单验证。</li>
 <li>发布文章时采用实际点击发布按钮时的时间。halo-dev/halo-admin#160</li>
 <li>添加 renderer meta 标签，让部分双核浏览器强制使用新一代内核，而不是 IE 内核导致页面无法正常渲染。halo-dev/halo-admin#207</li>
 <li>减弱所有动画效果。halo-dev/halo-admin#213</li>
 <li>移除部分操作的吐司提示，改为直接在按钮上显示操作结果。halo-dev/halo-admin#216</li>
 <li>优化大量不合理的代码。halo-dev/halo-admin#213 halo-dev/halo-admin#215</li>
 <li>移除 fastjson 依赖。#871</li>
 <li>重构主题目录扫描，允许当前没有激活中的主题。#869</li>
 <li>移除在开发者选项中重启应用的功能。#917</li>
 <li>移除 Token 不存在时抛出的异常。#962</li>
 <li>优化 Markdown 导入功能。#977</li>
 <li>修复文章管理页面刷新后分页显示不正确的问题。halo-dev/halo-admin#231</li>
 <li>修复文件上传组件无法同时上传多个文件的问题。halo-dev/halo-admin#234</li>
 <li>修复异常图片上传的时候，没有捕获异常的问题。#1025</li>
 <li>优化文章编辑提示未保存弹窗的时机。halo-dev/halo-admin#242</li>
 <li>移除开发者选项中修改配置文件和重启服务的功能。halo-dev/halo-admin#244</li>
 <li>优化主题管理页面的布局。halo-dev/halo-admin#245</li>
 <li>优化远程下载主题的体验。halo-dev/halo-admin#249</li>
 <li>优化博客设置页面的布局。halo-dev/halo-admin#251</li>
</ul>
<h2 id="fixed">Fixed</h2>
<ul>
 <li>修复取消全局绝对路径导致加密文章无法正确查看的问题。#785 #854</li>
 <li>修复 token 无法正确失效的问题。halo-dev/halo-admin#129</li>
 <li>修复附件不存在时调用删除接口抛异常的问题。#951</li>
 <li>修复 content api 中查询单篇文章或页面时，没有发出浏览量增加事件的问题。#981</li>
 <li>修复自动生成的文章摘要中清除了空格的问题。#1003</li>
 <li>修复文章页面渲染耗时过长的问题。#1008</li>
 <li>修复主题版本校验没有处理 beta 形式的版本号，从而导致无法更新或者安装主题的问题。#1011</li>
 <li>修复文章管理页面刷新后分页显示不正确的问题。halo-dev/halo-admin#231</li>
 <li>修复文件上传组件无法同时上传多个文件的问题。halo-dev/halo-admin#234</li>
 <li>修复异常图片上传的时候，没有捕获异常的问题。#1025</li>
 <li>修复退出登录和初始化引导页面的部分问题。halo-dev/halo-admin#239 halo-dev/halo-admin#240</li>
 <li>修复网站备份的时候，上级目录不存在导致备份异常的问题。#1056</li>
 <li>修复无法上传 <code>tar.gz</code> 类型文件的问题。#1057</li>
 <li>修复某些情况下主题设置保存失败的问题。#1070</li>
 <li>修复上传附件或者主题时，由于部分系统会定时清理临时目录，导致上传失败的问题。</li>
</ul>
<h2 id="升级步骤">升级步骤</h2>
<ol>
 <li>停止运行 Halo：<code>service halo stop</code>。</li>
 <li>备份数据：<code>cp -r ~/.halo ~/.halo.bak</code>。</li>
 <li>重命名（备份）旧运行包：<code>mv halo-latest.jar halo-latest.jar.bak</code>。</li>
 <li>下载新运行包：<code>wget https://dl.halo.run/release/halo-1.4.0.jar -O halo-latest.jar</code>。</li>
 <li>运行：<code>service halo start</code>。</li>
</ol>
<h2 id="注意事项">注意事项</h2>
<ol>
 <li>更新前不要忘了备份数据，不管你是以什么方式部署的，都请备份 <code>~/.halo</code>，当然，如果你使用 docker 部署，并修改了映射路径的话，就备份你的映射路径。</li>
 <li>如果有使用 CDN 全站加速，请更新完毕后，刷新全站缓存，并清空浏览器缓存。</li>
</ol>
<h2 id="相关链接">相关链接</h2>
<ul>
 <li><a href="https://github.com/halo-dev/halo/releases/tag/v1.4.0">https://github.com/halo-dev/halo/releases/tag/v1.4.0</a></li>
 <li><a href="https://bbs.halo.run/d/999-halo-140">https://bbs.halo.run/d/999-halo-140</a></li>
 <li><a href="https://halo.run/archives/halo-140-released.html">https://halo.run/archives/halo-140-released.html</a></li>
</ul>]]></description><guid isPermaLink="false">/archives/halo-140-released</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2F2af74b43c3dc5b2015ac495c6b0a0bdf-4aed85ce6f1f4119955038723a425d3f.jpg&amp;size=m" type="image/jpeg" length="32181"/><category>关于 Halo</category><pubDate>Wed, 23 Sep 2020 16:55:50 GMT</pubDate></item><item><title><![CDATA[分享一个教科书式的提问方式]]></title><link>https://ryanc.cc/archives/textbook-style-questioning</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E5%88%86%E4%BA%AB%E4%B8%80%E4%B8%AA%E6%95%99%E7%A7%91%E4%B9%A6%E5%BC%8F%E7%9A%84%E6%8F%90%E9%97%AE%E6%96%B9%E5%BC%8F&amp;url=/archives/textbook-style-questioning" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">今天，收到一封教科书式的提问邮件。坦率地讲，这还是我第一次收到这种逻辑清晰、信息提供到位的提问。所以非常激动，特意水一篇文来和大家分享一下。</p>
</blockquote>
<h2 style="" id="%E9%82%AE%E4%BB%B6%E5%8E%9F%E6%96%87">邮件原文</h2>
<p style="">Ryan Wang：</p>
<p style="">你好，我是刚开始使用Halo的程序员。Halo是我接触过最优秀的作品之一，难以置信能够遇到如此优秀的博客系统，在我第一次了解到它的时候，我就决定用它来搭建一个博客。</p>
<p style="">在我安装好，顺利运行Halo之后，我迫不及待的尝试了几个主题，最后我觉得Journal非常符合我的个人喜好，同时惊喜的发现原来您也在用这款主题作为自己的主页。但随之而来我在这个主题上遇到了一个小问题。</p>
<p style="">【问题】</p>
<p style=""><code>当在移动端显示时，博客主题的div向左偏移了一部分，同时最上方的折叠菜单栏无法正确显示，导致上下的中轴线不一样，页面总是左右滑动。</code></p>
<p style="">我尝试了自己调试，但在chrome上调整为手机显示后，却是正常，同时这个问题也不是在所有手机上都有问题，我使用了多款手机，发现个别手机上可以正常显示。</p>
<p style="">【截图】</p>
<p style="">以下截图为您和主题作者主页的正常截图，标题可居中显示，同时在下滑后可以显示在在折叠菜单栏。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fb5870420fb005448b13521e0cb9bfa15-27880ac9b09e43268bcab8135e77283b.png&amp;size=m" alt="b5870420fb005448b13521e0cb9bfa15" width="100%" height="100%" style="display: inline-block"></p>
<p style="">以下是我的博客异常显示截图，标题不居中，下滑后折叠菜单栏也无法正确显示。</p>
<ul>
 <li>
  <p style="">图1的空白处还有一个隐藏的链接，也是链接到我的主页，标题div整体偏左。</p>
 </li>
 <li>
  <p style="">图2我将标题div边界显示出来了，确实偏左。</p>
 </li>
 <li>
  <p style="">图3下滑后折叠菜单栏没有置顶显示。</p>
 </li>
 <li>
  <p style="">图4是在chrome的调试模式下能够正确显示。</p>
 </li>
</ul>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Faf6aa871277596b107d4596d9eccabd8-1b2565c6e49d43ad882d873d427a9513.jpg&amp;size=m" alt="af6aa871277596b107d4596d9eccabd8" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fb5870420fb005448b13521e0cb9bfa15-3bed5dee00f140abb381c7f6badae962.png&amp;size=m" alt="b5870420fb005448b13521e0cb9bfa15" width="100%" height="100%" style="display: inline-block"></p>
<p style="">【资源】</p>
<ul>
 <li>
  <p style="">Halo版本：v1.1.1</p>
 </li>
 <li>
  <p style="">Journal主题：当前halo-dev/halo-theme-Journal仓库master最新版本，commit id：2747364e50880d44ae4ac8fcb1513352aa199039</p>
 </li>
 <li>
  <p style="">尝试过的浏览器：</p>
  <ul>
   <li>
    <p style="">iphone xs: safari、chrome、微信内置浏览器 [均异常]</p>
   </li>
   <li>
    <p style="">iPhone xs max: safari [异常]</p>
   </li>
   <li>
    <p style="">Huawei mate30 pro: 自带浏览器、百度浏览器 [均异常]</p>
   </li>
   <li>
    <p style="">Huawei p20: chrome [正常]</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">主页地址：<a href="http://xxxx">http://xxxx</a>(文章中就不写地址了)</p>
 </li>
</ul>
<p style="">由于我对前端相关技术非常不熟悉，自己调试很久都无法使其正确，特此咨询博主是如何解决的，若博主有时间，可否看下之前是否也遇到过类似问题，或是做过何种修改使其正确显示了。</p>
<p style="">期待您的回信，十分感谢！！</p>
<p style="">Your fans：xxxx(文章中就不写名字了)
 <br>
 2020年1月5日</p>
<h2 style="" id="%E4%B8%BA%E4%BB%80%E4%B9%88%E5%86%99%E5%BE%97%E5%A5%BD%EF%BC%9F">为什么写得好？</h2>
<h3 style="" id="%E9%82%AE%E4%BB%B6%E6%A0%BC%E5%BC%8F%E6%A0%87%E5%87%86">邮件格式标准</h3>
<p style="">虽说现在很多人（包括我），在发邮件的时候都不会太刻意在乎邮件格式，但是这种格式我看起来就是舒服，也有了阅读下去的欲望。</p>
<h3 style="" id="%E6%9C%89%E9%97%AE%E9%A2%98%E7%AE%80%E8%BF%B0">有问题简述</h3>
<p style="">可以看到，上面的 <code>【问题】</code> 板块中，他有把问题简写为一小段，并特意标注，这样的话我就有可能能一下子了解问题所在，在我脑子里搜索一下是否有遇到过类似的问题。</p>
<h3 style="" id="%E6%9C%89%E9%97%AE%E9%A2%98%E8%AF%A6%E7%BB%86%E6%8F%8F%E8%BF%B0">有问题详细描述</h3>
<p style="">紧接着 <code>问题简述</code>，随后就是问题的详细描述，附有多张截图。并把所有导致的问题全部描述了一遍。</p>
<h3 style="" id="%E6%9C%89%E8%87%AA%E5%B7%B1%E5%B0%9D%E8%AF%95%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98">有自己尝试解决问题</h3>
<p style="">从上面的描述就可以知道，他有在桌面版的 Chrome 中调试过主题，也在多款手机的浏览器中调试过，说明他有尝试通过自己解决问题，比如：是否和浏览器有关？是否和设备有关？都很显而易见。</p>
<h3 style="" id="%E6%8F%90%E4%BE%9B%E4%BA%86%E7%8E%AF%E5%A2%83%E4%BF%A1%E6%81%AF">提供了环境信息</h3>
<p style="">他在最后面提供了 Halo 的版本，主题的版本以及 <code>commit id</code>。这是最方便我们定位问题的信息，有可能在某些情况，我们已经在最新版本修复了此问题，所以当你没有提供这些信息的时候，我们往往会问你软件的版本或者主题的版本。</p>
<h3 style="" id="%E6%8F%90%E4%BE%9B%E4%BA%86%E7%BA%BF%E4%B8%8A%E5%9C%B0%E5%9D%80">提供了线上地址</h3>
<p style="">这就不用我多说了，这是最直接了当方便我们调试的信息。</p>
<h2 style="" id="%E6%80%BB%E7%BB%93">总结</h2>
<p style="">我就希望以后有同学抛出问题的时候，能多附带一些信息，比如详细日志，线上地址等等。真的，不为其他的，就为了不耽搁我们双方的时间，从而更加迅猛的帮你解决问题。某些时候真的不是不想解决你们遇到的问题，而且你们给出的信息实在过少，难以判断，而往往这时候我们又要向你问一些详细情况，这样一去一来不就耽搁时间了吗？</p>
<p style="">另外，感谢这位朋友愿意提供该邮件供我水这篇文章，我认为比我以前水的一些文章有价值多了，感谢。</p>]]></description><guid isPermaLink="false">/archives/textbook-style-questioning</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2F940e8e7cd54d6cacdc9583c593a33c16-1114ac21cd384bd8b0fbfed67b2fd5f1.jpg&amp;size=m" type="image/jpeg" length="46849"/><category>关于 Halo</category><category>分享</category><pubDate>Fri, 18 Sep 2020 13:02:02 GMT</pubDate></item><item><title><![CDATA[优雅的让 Halo 支持 webp 图片输出]]></title><link>https://ryanc.cc/archives/halo-with-webp</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%A9%20Halo%20%E6%94%AF%E6%8C%81%20webp%20%E5%9B%BE%E7%89%87%E8%BE%93%E5%87%BA&amp;url=/archives/halo-with-webp" width="1" height="1" alt="" style="opacity:0;">
<p style="">原文地址：<a href="https://halo.run/archives/halo-and-webp">https://halo.run/archives/halo-and-webp</a></p>
<h2 style="" id="%E6%98%AF%E4%BB%80%E4%B9%88">是什么</h2>
<blockquote>
 <p style="">WebP的有损压缩算法是基于VP8视频格式的帧内编码[17]，并以RIFF作为容器格式。[2] 因此，它是一个具有八位色彩深度和以1:2的比例进行色度子采样的亮度-色度模型（YCbCr 4:2:0）的基于块的转换方案。[18] 不含内容的情况下，RIFF容器要求只需20字节的开销，依然能保存额外的 元数据(metadata)。[2] WebP图像的边长限制为16383像素。</p>
</blockquote>
<p style="">在 WebP 的官网中，我们可以发现 Google 是这样宣传 WebP 的：</p>
<blockquote>
 <p style="">WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller than comparable JPEG images at equivalent SSIM quality index.</p>
</blockquote>
<p style="">简单来说，WebP 图片格式的存在，让我们在 WebP 上展示的图片体积可以有较大幅度的缩小，也就带来了加载性能的提升。（摘自 <a href="https://nova.moe/re-introduce-webp-server%EF%BC%89">https://nova.moe/re-introduce-webp-server）</a></p>
<h2 style="" id="%E6%80%8E%E4%B9%88%E5%81%9A">怎么做</h2>
<p style="">那么如何优雅的在不替换图片地址的情况下，将图片转为 webp 格式然后输出呢？</p>
<p style="">这时候就可以使用 <a href="https://github.com/webp-sh">webp-sh</a> 组织最新开源的 <a href="https://github.com/webp-sh/webp_server_go">webp_server_go</a> 了，它的大概原理就是：当我们请求一张图片的时候使用 web 代理工具转发到 <code>webp_server_go</code> 应用进行处理，处理完成之后返回 webp 格式的图片，并且会保留处理后的图片以供后面的访问。&#x8;</p>
<p style="">目前大部分主流浏览器都已经支持了 webp 图片的显示，除了 Safari，但是不必担心，webp_server_go 会自动判断请求来源是否为 Safari，如果是，那么会返回原图。</p>
<p style="">下面将提供两种 web 服务器的代理方法。</p>
<blockquote>
 <p style="">此教程以 CentOS 7.x 为例，其他发行版本大同小异。另外，此教程只针对于 Halo，其他 web 程序可能在 config.json 部分有所不同，建议参考仓库的 README。</p>
</blockquote>
<h3 style="" id="%E9%83%A8%E7%BD%B2-webp_server_go">部署 webp_server_go</h3>
<p style="">在 <a href="https://github.com/webp-sh/webp_server_go">仓库</a> 的 README 中已经大致讲解了部署方法，在这里针对 Halo 详细说明一下。</p>
<h4 style="" id="%E4%B8%8B%E8%BD%BD%E5%AE%98%E6%96%B9%E7%BC%96%E8%AF%91%E5%A5%BD%E7%9A%84-webp_server_go-%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6">下载官方编译好的 webp_server_go 二进制文件</h4>
<blockquote>
 <p style="">如果你有能力，也可以自行编译。</p>
</blockquote>
<p style="">新建一个存放二进制文件和 config.json 文件的目录（可自定义）：</p>
<pre><code class="language-bash">mkdir /opt/webps

cd /opt/webps</code></pre>
<p style="">下载二进制文件（最新版本请访问 <a href="https://github.com/webp-sh/webp_server_go/releases">releases</a>）：</p>
<pre><code class="language-bash">wget https://github.com/webp-sh/webp_server_go/releases/download/0.1.0/webp-server-linux-amd64 -O webp-server</code></pre>
<p style="">给予执行权限：</p>
<pre><code class="language-bash">chmod +x webp-server</code></pre>
<h4 style="" id="%E5%88%9B%E5%BB%BA-config.json">创建 config.json</h4>
<pre><code class="language-json">{
        "HOST": "127.0.0.1",
        "PORT": "3333",
        "QUALITY": "80",
        "IMG_PATH": "/root/.halo",
        "EXHAUST_PATH": "/root/.halo/cache",
        "ALLOWED_TYPES": ["jpg","png","jpeg"]
}</code></pre>
<p style="">参数解释：</p>
<ul>
 <li>
  <p style="">HOST：一般不修改。</p>
 </li>
 <li>
  <p style="">PORT：webp_server_go 的运行端口。</p>
 </li>
 <li>
  <p style="">QUALITY：转换质量，默认为 80%。</p>
 </li>
 <li>
  <p style="">IMG_PATH：固定格式，/运行 Halo 的用户名/.halo</p>
 </li>
 <li>
  <p style="">EXHAUST_PATH：固定格式，/运行 Halo 的用户名/.halo/cache</p>
 </li>
 <li>
  <p style="">ALLOWED_TYPES：需要转换的格式</p>
 </li>
</ul>
<h4 style="" id="%E4%BD%BF%E7%94%A8-systemd-%E8%BF%9B%E8%A1%8C%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86">使用 systemd 进行状态管理</h4>
<p style="">创建 service 文件：</p>
<pre><code class="language-bash">vim /etc/systemd/system/webps.service</code></pre>
<p style="">写入：</p>
<pre><code class="language-bash">[Unit]
Description=WebP Server
Documentation=https://github.com/n0vad3v/webp_server_go
After=nginx.target

[Service]
Type=simple
StandardError=journal
AmbientCapabilities=CAP_NET_BIND_SERVICE
WorkingDirectory=/opt/webps
ExecStart=/opt/webps/webp-server --config /opt/webps/config.json
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=3s


[Install]
WantedBy=multi-user.target</code></pre>
<p style="">需要注意的是，ExecStart 命令中的程序路径和配置文件路径一定要正确，结合你的实际情况填写。</p>
<p style="">然后执行：</p>
<pre><code class="language-bash">systemctl daemon-reload
systemctl enable webps.service
systemctl start webps.service</code></pre>
<p style="">查看运行状态：</p>
<pre><code class="language-bash">systemctl status webps.service</code></pre>
<p style="">如果没有问题，那么会输出以下日志：</p>
<pre><code class="language-bash">WebP Server is running at 127.0.0.1:3333</code></pre>
<h3 style="" id="%E4%BD%BF%E7%94%A8-nginx-%E8%BF%9B%E8%A1%8C%E4%BB%A3%E7%90%86">使用 Nginx 进行代理</h3>
<blockquote>
 <p style="">如果你的 Halo 是使用 Nginx 反向代理的话。</p>
</blockquote>
<h4 style="" id="%E4%BF%AE%E6%94%B9-halo.conf">修改 halo.conf</h4>
<p style="">在 server 节点添加：</p>
<pre><code class="language-nginx">location ^~ /upload/ {
        proxy_pass http://127.0.0.1:3333;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_hide_header X-Powered-By;
        proxy_set_header HOST $http_host;
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}</code></pre>
<p style="">重载 Nginx 配置：</p>
<pre><code class="language-bash"># 检查配置文件是否有问题
nginx -t 

nginx -s reload</code></pre>
<h3 style="" id="%E4%BD%BF%E7%94%A8-caddy-%E8%BF%9B%E8%A1%8C%E4%BB%A3%E7%90%86">使用 Caddy 进行代理</h3>
<blockquote>
 <p style="">如果你的 Halo 是使用 Caddy 反向代理的话。</p>
</blockquote>
<h4 style="" id="%E4%BF%AE%E6%94%B9-caddyfile">修改 Caddyfile</h4>
<p style="">在你域名节点下添加：</p>
<pre><code class="language-bash">proxy /upload/ localhost:3333 {
  transparent
}</code></pre>
<p style="">重启 Caddy：</p>
<pre><code class="language-bash">service caddy restart</code></pre>
<p style="">教程完毕，下面讲一下如何验证是否生效。</p>
<h3 style="" id="%E9%AA%8C%E8%AF%81%E6%98%AF%E5%90%A6%E7%94%9F%E6%95%88">验证是否生效</h3>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fhalo.run%2Fupload%2F2020%2F3%2FScreen%2520Shot%25202020-03-02%2520at%252011.47.08%2520PM-045cd797c4e3464a9d6919447a7d9e7f.png&amp;size=m" alt="Screen Shot 2020-03-02 at 11.47.08 PM" width="100%" height="100%" style="display: inline-block"></p>
<p style="">注意看 <code>Type</code> 列，图片的返回格式已经变成了 webp，而且图片大小已经远远降低，那么说明你的配置已经成功了。have fun!</p>
<p style="">如果用的开心，请关注一下 <a href="https://github.com/webp-sh/webp_server_go">https://github.com/webp-sh/webp_server_go</a> 哦！另外，他们还有其他语言的版本，请查看 <a href="https://github.com/webp-sh">https://github.com/webp-sh</a>。</p>
<h3 style="" id="%E9%93%BE%E6%8E%A5%EF%BC%9A">链接：</h3>
<ul>
 <li>
  <p style=""><a href="https://github.com/webp-sh/webp_server_go">https://github.com/webp-sh/webp_server_go</a></p>
 </li>
 <li>
  <p style=""><a href="https://nova.moe/re-introduce-webp-server/">让站点图片加载速度更快——引入 WebP Server 无缝转换图片为 WebP</a></p>
 </li>
 <li>
  <p style=""><a href="https://www.bennythink.com/flying-webp.html">个人网站无缝切换图片到 webp</a></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/halo-with-webp</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fwebp-server.png&amp;size=m" type="image/jpeg" length="198403"/><category>关于 Halo</category><pubDate>Mon, 2 Mar 2020 16:00:00 GMT</pubDate></item><item><title><![CDATA[记录优化 Vue 应用的首次加载速度]]></title><link>https://ryanc.cc/archives/vue-dist-jsdelivr-cdn</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E8%AE%B0%E5%BD%95%E4%BC%98%E5%8C%96%20Vue%20%E5%BA%94%E7%94%A8%E7%9A%84%E9%A6%96%E6%AC%A1%E5%8A%A0%E8%BD%BD%E9%80%9F%E5%BA%A6&amp;url=/archives/vue-dist-jsdelivr-cdn" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="2024-%E5%B9%B4%E6%9B%B4%E6%96%B0">2024 年更新</h2>
<p style="">不再推荐用这种偏门的方式去优化网络加载，这非常不可靠。Halo 2.x 也已经完全没有使用这种方式。正确的做法应该是在工程和代码层面优化，比如分包、异步加载路由、缓存策略、SSR 等。一定要上 CDN，也应该是从基础设施运维上面考虑。</p>
<p style="">感谢评论区的 @Yttrium 提醒更新。</p>
<hr>
<blockquote>
 <p style=""><a href="https://halo.run">Halo</a> 的管理端使用的是 <a href="https://vuejs.org/">Vue</a> 来构建的，随之收到不少反馈后台加载过于缓慢，其主要原因就是打包好的 <code>Vue</code> 应用的静态资源又多又大，可能同时就几十个请求，这对于一些小水管的服务器来说简直是致命的打击。</p>
</blockquote>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2F2e4f1d9d99910ce0933230d0c79d309c-1decedccbc4f46aeb8165c2b50d948b1.png&amp;size=m" alt="2e4f1d9d99910ce0933230d0c79d309c" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95">解决办法</h2>
<p style="">使用公共 cdn，我们只需要把打包好的静态资源丢在公共 cdn 上引用就好了。那么如何优雅的上传到公共 cdn 呢？</p>
<p style="">创建 <code>.env</code> 和 <code>.env.development</code>。</p>
<p style="">.env</p>
<pre><code class="language-properties">NODE_ENV=production
// VERSION 需要和 package.json 的 version 一致。
PUBLIC_PATH=https://cdn.jsdelivr.net/npm/halo-admin@VERSION/dist/</code></pre>
<p style="">.env.development</p>
<pre><code class="language-properties">NODE_ENV=development
PUBLIC_PATH=/</code></pre>
<p style="">修改 <code>vue.config.js</code></p>
<pre><code class="language-javascript">module.exports = {
	publicPath: process.env.PUBLIC_PATH,
}</code></pre>
<p style="">打包测试</p>
<blockquote>
 <p style="">可以在 <code>dist/index.html</code> 看到，所有静态资源的根路径已经变成了 <code>https://cdn.jsdelivr.net/npm/halo-admin@VERSION/dist/</code>。</p>
</blockquote>
<p style="">上传到 <a href="https://www.npmjs.com/">npmjs</a></p>
<pre><code class="language-bash">npm login

npm init

npm publish</code></pre>
<h2 style="" id="%E8%AF%B4%E6%98%8E">说明</h2>
<ol>
 <li>
  <p style="">每次发布版本前，需要修改 <code>.env</code> 和 <code>package.json</code> 的版本号，且需要保持一致。</p>
 </li>
 <li>
  <p style="">发布到 <code>npmjs</code> 前，需要先 <code>npm run build</code>。</p>
 </li>
 <li>
  <p style="">经过这些操作之后，只需要部署 <code>dist/index.html</code> 即可，其他静态资源无用，因为是走的 <code>jsdelivr</code> 的 cdn。</p>
 </li>
 <li>
  <p style="">只建议个人小应用使用这种方式，其他类型应用请自行斟酌，毕竟上传到 <code>npmjs</code> 会上传项目代码。</p>
 </li>
 <li>
  <p style="">过程描述的过于简单，仅做为记录，非教程。</p>
 </li>
</ol>
<h2 style="" id="%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5">相关链接</h2>
<ul>
 <li>
  <p style=""><a href="https://www.npmjs.com">https://www.npmjs.com</a></p>
 </li>
 <li>
  <p style=""><a href="https://www.jsdelivr.com">https://www.jsdelivr.com</a></p>
 </li>
 <li>
  <p style=""><a href="https://www.jsdelivr.com/features">https://www.jsdelivr.com/features</a></p>
 </li>
 <li>
  <p style=""><a href="https://cli.vuejs.org/zh/config/#publicpath">https://cli.vuejs.org/zh/config/#publicpath</a></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/vue-dist-jsdelivr-cdn</guid><dc:creator>Ryan Wang</dc:creator><category>学习记录</category><pubDate>Mon, 13 Jan 2020 04:32:20 GMT</pubDate></item><item><title><![CDATA[博客迁移到 Raspberry Pi]]></title><link>https://ryanc.cc/archives/blog-migration-to-raspberry-pi</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E5%88%B0%20Raspberry%20Pi&amp;url=/archives/blog-migration-to-raspberry-pi" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong>注：经过一段时间的折腾，已经放弃运行到树莓派了，其主要原因是因为家里网络质量一言难尽。</strong></p>
<blockquote>
 <p style="">本着爱折腾之心，今天将博客搬到了吃灰已久的 <code>Raspberry Pi 3B+</code>。好吧，其实也是之前一个热心的网友送了我一年<a href="http://www.zhexi.tech">哲西云</a>的内网穿透，一直没用，所以秉承着中华人民拒绝浪费的美好传统，折腾开始了…</p>
</blockquote>
<h2 style="" id="%E9%85%8D%E7%BD%AE">配置</h2>
<pre><code class="language-bash">    .',;:cc;,'.    .,;::c:,,.    root@raspberrypi
   ,ooolcloooo:  'oooooccloo:    OS: Raspbian 10 buster
   .looooc;;:ol  :oc;;:ooooo'    Kernel: armv7l Linux 4.19.75-v7+
     ;oooooo:      ,ooooooc.     Uptime: 4h 4m
       .,:;'.       .;:;'.       Packages: 588
       .... ..'''''. ....        Shell: 5295
     .''.   ..'''''.  ..''.      CPU: ARMv7 rev 4 (v7l) @ 1.4GHz
     ..  .....    .....  ..      RAM: 386MiB / 926MiB
    .  .'''''''  .''''''.  .
  .'' .''''''''  .'''''''. ''.
  '''  '''''''    .''''''  '''
  .'    ........... ...    .'.
    ....    ''''''''.   .''.
    '''''.  ''''''''. .'''''
     '''''.  .'''''. .'''''.
      ..''.     .    .''..
            .'''''''
             ......</code></pre>
<p style="">也就那样儿吧~</p>
<h2 style="" id="%E7%83%A7%E5%BD%95%E9%95%9C%E5%83%8F">烧录镜像</h2>
<p style="">这次搭建博客使用的镜像是官方的 <code>Raspbian Buster Lite</code>，之所以没选 <code>Desktop</code>，因为那玩意儿就是个玩具，没有实际用途，鹅且也不会用它，倒不如节省点内存。</p>
<p style="">进入官网 <a href="https://www.raspberrypi.org/downloads/raspbian">https://www.raspberrypi.org/downloads/raspbian</a> ,找到 <code>Raspbian Buster Lite</code>，选择 <code>Download ZIP</code>。</p>
<p style="">解压下载好的镜像得到 <code>xxxx-xx-xx-raspbian-buster-lite.img</code>。</p>
<p style="">使用 <a href="https://github.com/balena-io/etcher">Etcher</a> 烧录镜像。当然，也有很多其他的烧录工具。</p>
<h2 style="" id="%E5%BC%80%E5%90%AF-ssh">开启 SSH</h2>
<p style="">这一步很简单，在烧录好的 SD 卡中，新建一个空白的 <code>ssh</code> 文件即可，需要注意的是，这个文件没有后缀，别搞个 <code>ssh.txt</code>、<code>ssh.avi</code> 啥的。</p>
<h2 style="" id="%E5%90%AF%E5%8A%A8">启动</h2>
<p style="">插上 SD 和电源直接启动即可，默认用户名 <code>pi</code>，默认密码 <code>raspberry</code>，切换到 root 账户，<code>sudo su root</code>。</p>
<h2 style="" id="%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE">环境配置</h2>
<p style="">Java</p>
<pre><code class="language-bash">sudo apt-get install openjdk-8-jre-headless
sudo apt-get install openjdk-8-jre</code></pre>
<p style="">Vim</p>
<pre><code class="language-bash">sudo apt-get install vim</code></pre>
<p style="">Git</p>
<pre><code class="language-bash">sudo apt-get install git</code></pre>
<h2 style="" id="%E8%BF%81%E7%A7%BB%E6%95%B0%E6%8D%AE">迁移数据</h2>
<p style="">下载安装包</p>
<pre><code class="language-shellscript">wget http://halo.ryanc.cc/release/halo-latest.jar -O halo-latest.jar</code></pre>
<p style="">拉取备份的数据</p>
<pre><code class="language-shellscript">git clone git@github.com:ruibaby/blog-data.git .halo</code></pre>
<h2 style="" id="%E5%90%AF%E5%8A%A8-halo">启动 Halo</h2>
<pre><code class="language-shellscript">java -jar halo-latest.jar</code></pre>
<p style="">测试没问题，再配置 <code>systemd</code> 进行管理，教程：<a href="https://halo.run/guide/install/install-with-linux.html#%E8%BF%9B%E9%98%B6%E9%85%8D%E7%BD%AE">https://halo.run/guide/install/install-with-linux.html#%E8%BF%9B%E9%98%B6%E9%85%8D%E7%BD%AE</a>。</p>
<h2 style="" id="%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90">域名解析</h2>
<p style="">由于我是直接是用的 <a href="https://www.upyun.com">又拍云 CDN</a> 进行回源，所以也不需要安装 Nginx 啥的了，这个内网穿透服务提供了一个 <code>CNAME</code>，去解析一下就完事了。</p>
<h2 style="" id="%E5%93%B2%E8%A5%BF%E4%BA%91">哲西云</h2>
<p style="">如有需要，可以去 <a href="http://www.zhexi.tech">http://www.zhexi.tech</a> 体验体验。优惠码 <code>GRRVFM</code></p>
<h2 style="" id="%E5%B1%95%E7%A4%BA">展示</h2>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2F56ffb0f522663785f4b031d595c1f6a0-0e0a0b1fe65d40c4b6ebcbe0da5db173.png&amp;size=m" alt="56ffb0f522663785f4b031d595c1f6a0" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2Fd15419a817ec66336297bd8e08157508-ab4bc29fabea4929a7bc466fc6d8bade.png&amp;size=m" alt="d15419a817ec66336297bd8e08157508" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2F758cfbd207c38b9db27a6bba295618a2-2ae58b4612c7420ca49c1c8efd27ee26.png&amp;size=m" alt="758cfbd207c38b9db27a6bba295618a2" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2F9e22bcea8ac55cd0c8a82d2c49b083c8-185c5f42d5da4db2bcd9ae4e3a057b17.png&amp;size=m" alt="9e22bcea8ac55cd0c8a82d2c49b083c8" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fryanc.cc%2Fupload%2F2020%2F12%2F87e60eb23320ee7c00fb5ed66942f631-07ad78b94d104589a2c5bdbed7bf4e2d.png&amp;size=m" alt="87e60eb23320ee7c00fb5ed66942f631" width="100%" height="100%" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/blog-migration-to-raspberry-pi</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fjainath-ponnala-jvHymbpto1E-unsplash.jpg&amp;size=m" type="image/jpeg" length="28424"/><category>学习记录</category><pubDate>Mon, 23 Dec 2019 11:40:02 GMT</pubDate></item><item><title><![CDATA[Flarum 的安装与配置]]></title><link>https://ryanc.cc/archives/flarum-install-and-config</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=Flarum%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE&amp;url=/archives/flarum-install-and-config" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style=""><a href="https://flarum.org">Flarum</a> 是一款非常棒的开源论坛程序，<a href="https://bbs.halo.run">Halo 的论坛</a> 就是用 Flarum 搭建的。之前有人问过我 Flarum 如何搭建，所以下面讲一下 Flarum 的搭建过程（btw，官方的搭建教程实在草率）。</p>
</blockquote>
<h2 style="" id="%E5%89%8D%E6%8F%90">前提</h2>
<ul>
 <li>
  <p style="">域名需要提前解析。</p>
 </li>
 <li>
  <p style="">注意服务器是否需要备案，如果没备案，会被 x 掉。</p>
 </li>
 <li>
  <p style="">有一定的 Linux 基础。</p>
 </li>
</ul>
<h2 style="" id="%E7%8E%AF%E5%A2%83%E8%AF%B4%E6%98%8E">环境说明</h2>
<ul>
 <li>
  <p style="">Linux Server（本文是用的 CentOS 7.6）</p>
 </li>
 <li>
  <p style="">Apache 或者 Nginx（本文是用的 Nginx）</p>
 </li>
 <li>
  <p style="">PHP 7.1+</p>
 </li>
 <li>
  <p style="">PHP 拓展： curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip, fileinfo</p>
 </li>
 <li>
  <p style="">MySQL 5.6+ 或者 MariaDB 10.0.5+</p>
 </li>
</ul>
<h2 style="" id="%E9%83%A8%E7%BD%B2%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85">部署环境安装</h2>
<h3 style="" id="%E6%9B%B4%E6%96%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BD%AF%E4%BB%B6%E5%8C%85">更新服务器软件包</h3>
<pre><code class="language-bash">yum update -y</code></pre>
<h3 style="" id="%E5%AE%89%E8%A3%85-nginx%2Fphp%2Fmysql">安装 Nginx/PHP/MySQL</h3>
<blockquote>
 <p style="">这里我们使用 <a href="https://oneinstack.com">OneinStack</a> 一键安装，人生苦短，懒得自己编译了。当然，如果有时间，根据需求自己编译安装更好。</p>
</blockquote>
<blockquote>
 <p style=""><strong>2024 年更新，OneinStack 被曝包含恶意代码，请谨慎使用。</strong>
  <br>
  <a href="https://github.com/oneinstack/oneinstack/issues/511">https://github.com/oneinstack/oneinstack/issues/511</a></p>
</blockquote>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-e7330e557abe4d54955e5b518f8d7432.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如上图，选择好需要的软件以及版本后，复制安装命令到服务器执行就行了，安装过程可能会有点慢，耐心等待就行了。</p>
<blockquote>
 <p style="">需要注意的是，PHP 扩展中的 <code>fileinfo</code> 一定要勾选，Flarum 官方文档居然没有写需要这个扩展。(没错，这里我被坑了，嘤嘤嘤嘤~)</p>
</blockquote>
<p style="">安装完成应该会打印出这些东西：
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-22ecf004c71a4f72b931d6e7e870b36a.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E5%AE%89%E8%A3%85-composer">安装 Composer</h3>
<pre><code class="language-bash">php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"

php composer-setup.php

php -r "unlink('composer-setup.php');"

mv composer.phar /usr/local/bin/composer</code></pre>
<p style="">由于 <code>Composer</code> 的服务器在国外，可能导致下载 Flarum 已经依赖包会很慢，所以我们需要更换一下源地址。至于 <code>Composer</code> 是啥，其实就是 PHP 的一个包管理，类似 Java 的 <code>Maven</code> 和 <code>Gradle</code> 工具。</p>
<pre><code class="language-bash">composer config -g repo.packagist composer https://packagist.phpcomposer.com</code></pre>
<h2 style="" id="%E5%AE%89%E8%A3%85-flarum">安装 Flarum</h2>
<p style="">进入到 oneinstack 目录，执行 <a href="http://vhost.sh">vhost.sh</a> 脚本新建一个网站
 <br>
 <img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-3bf767890a86461d8163dc30ab21bf1b.png&amp;size=m" alt="image.png" width="893px" height="595px" style="display: inline-block"></p>
<p style="">然后会提示 SSL 证书选项，网站目录之类的东西，按照自己的需求选择即可。</p>
<p style="">创建完成后应该是这样子。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-0bb012858b9643ef87b7bf0a8e0f4a7f.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后进入网站目录执行：</p>
<pre><code class="language-bash">composer create-project flarum/flarum . --stability=beta</code></pre>
<p style="">更新，现在 Flarum 已经发布正式版，请使用下面的命令安装：</p>
<pre><code class="language-bash">composer create-project flarum/flarum .</code></pre>
<p style="">然后等待下载 Flarum 以及对应的依赖即可，安装完成应该是这个样子的：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-3311ca92b9744355995e567a6a6bc3b0.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="%E9%85%8D%E7%BD%AE%E8%BF%90%E8%A1%8C">配置运行</h2>
<p style="">上面其实就已经安装好了 Flarum，但是还需要进一步配置才能正确运行。</p>
<h3 style="" id="%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93">创建数据库</h3>
<p style="">登陆 MySQL：</p>
<pre><code class="language-bash">mysql -u root -p密码</code></pre>
<p style="">创建数据库：</p>
<pre><code class="language-sql">create database 数据库名 character set utf8mb4 collate utf8mb4_bin;</code></pre>
<blockquote>
 <p style="">这里的字符集一定要是 <code>utf8mb4</code>，至于为什么是 <code>utf8mb4</code>，参考：<a href="https://www.jianshu.com/p/6967ce16a202">https://www.jianshu.com/p/6967ce16a202</a>。</p>
</blockquote>
<h3 style="" id="%E4%BF%AE%E6%94%B9-nginx-%E9%85%8D%E7%BD%AE">修改 Nginx 配置</h3>
<p style="">进入 Nginx 配置文件目录：</p>
<pre><code class="language-shellscript">cd /usr/local/nginx/conf/vhost</code></pre>
<p style="">修改网站的配置文件：</p>
<pre><code class="language-shellscript">vim xxx.conf</code></pre>
<p style="">需要修改的地方：</p>
<ol>
 <li>
  <p style="">root：需要在路径后面加上 <code>public</code>，比如我的原本是 <code>root /data/wwwroot/bbs.ryanwang.me;</code>，需要修改为 <code>root /data/wwwroot/bbs.ryanwang.me/public;</code>。</p>
 </li>
 <li>
  <p style="">引入 Flarum 提供的配置，在 server 大括号中任意位置加上 <code>include /data/wwwroot/xxx/.nginx.conf;</code>，<code>xxx</code> 为网站目录名。比如我的是 <code>include /data/wwwroot/bbs.ryanwang.me/.nginx.conf;</code></p>
 </li>
</ol>
<p style="">最后的配置示例：</p>
<pre><code class="language-nginx">server {
  listen 80;
  listen 443 ssl http2;
  ssl_certificate /usr/local/nginx/conf/ssl/bbs.ryanwang.me.crt;
  ssl_certificate_key /usr/local/nginx/conf/ssl/bbs.ryanwang.me.key;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
  ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 10m;
  ssl_session_cache builtin:1000 shared:SSL:10m;
  ssl_buffer_size 1400;
  add_header Strict-Transport-Security max-age=15768000;
  ssl_stapling on;
  ssl_stapling_verify on;
  server_name bbs.ryanwang.me;
  access_log /data/wwwlogs/bbs.ryanwang.me_nginx.log combined;
  index index.html index.htm index.php;
  root /data/wwwroot/bbs.ryanwang.me/public;
  if ($ssl_protocol = "") { return 301 https://$host$request_uri; }

  include /usr/local/nginx/conf/rewrite/other.conf;
  #error_page 404 /404.html;
  #error_page 502 /502.html;

  location ~ [^/]\.php(/|$) {
    #fastcgi_pass remote_php_ip:9000;
    fastcgi_pass unix:/dev/shm/php-cgi.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
  }

  location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
    expires 30d;
    access_log off;
  }
  location ~ .*\.(js|css)?$ {
    expires 7d;
    access_log off;
  }
  location ~ /(\.user\.ini|\.ht|\.git|\.svn|\.project|LICENSE|README\.md) {
    deny all;
  }
  include /data/wwwroot/bbs.ryanwang.me/.nginx.conf;
}</code></pre>
<p style="">最后我们需要检查 Nginx 配置是否有误并重载 Nginx 配置：</p>
<pre><code class="language-shellscript">nginx -t

nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful</code></pre>
<pre><code class="language-shellscript">nginx -s reload</code></pre>
<h3 style="" id="flarum-%E5%AE%89%E8%A3%85%E5%BC%95%E5%AF%BC">Flarum 安装引导</h3>
<p style="">如果出现下面的情况：</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-f43b6eef0fd04c25b58bee16e502c1dc.png&amp;size=m" alt="image" width="893px" height="599px" style="display: inline-block"></p>
<p style="">是因为没有对网站目录写入的权限，我们加一下权限即可：</p>
<pre><code class="language-bash"># xxx 为网站目录名称
chmod -R 777 /data/wwwroot/xxx</code></pre>
<p style="">然后刷新页面就可以看到安装表单了。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-7729b40e4502441e9d1a4c10f030838a.png&amp;size=m" alt="image.png" width="893px" height="531px" style="display: inline-block"></p>
<p style="">然后填写数据库信息以及管理员信息，点击安装即可。</p>
<p style="">安装部署部分到此结束。</p>
<h2 style="" id="%E5%B8%B8%E7%94%A8%E6%8F%92%E4%BB%B6%E5%AE%89%E8%A3%85">常用插件安装</h2>
<blockquote>
 <p style="">安装完成后会发现不支持中文，所以我们需要安装中文语言包。还有一些常用的插件。</p>
</blockquote>
<p style="">进入网站目录：</p>
<pre><code class="language-bash"># xxx 为网站目录名称
cd /data/wwwroot/xxx</code></pre>
<pre><code class="language-bash"># 简体中文语言包
composer require csineneo/lang-simplified-chinese

# 繁体中文语言包
composer require csineneo/lang-traditional-chinese

# 编辑器 Emoji 表情选择框
composer require clarkwinkelmann/flarum-ext-emojionearea

# Sitemap 生成器
composer require flagrow/sitemap

# Fancybox 插件
composer require squeevee/flarum-ext-fancybox </code></pre>
<p style="">安装完成后去后台启用即可（后台地址：网址/admin）。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-fa14ce072de942af86f2525230674061.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/flarum-install-and-config</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fflarum.png&amp;size=m" type="image/jpeg" length="125502"/><category>学习记录</category><pubDate>Mon, 18 Nov 2019 12:30:22 GMT</pubDate></item><item><title><![CDATA[使用 Caddy 自动部署 Gridea 博客]]></title><link>https://ryanc.cc/archives/deploy-gridea-with-caddy</link><description><![CDATA[<img src="https://ryanc.cc/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8%20Caddy%20%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%20Gridea%20%E5%8D%9A%E5%AE%A2&amp;url=/archives/deploy-gridea-with-caddy" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p style="">Gridea 是一个静态博客客户端，也可以称之为博客生成器，和 Hexo 之类的静态博客生成器很类似，唯一不同的是，Gridea 提供了一个非常好的可视化界面，非常容易就可以完成一个博客的搭建，而且配置都是可视化的，所以对小白来说非常友好。但是目前 Gridea 仅支持部署到 Github 和 Coding，由于众所周知的原因，Github Pages 会偶尔出现访问不了的情况（而且访问相对较慢），而且百度是不收录 Github Pages 的站点的。Coding 的话，抽风就更严重了。所以下面介绍一种方式，可以自动部署到自己的服务器。</p>
</blockquote>
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">在开始之前，你至少需要了解 Gridea 如何部署到 Github，并且需要熟悉 Linux 的一些基本命令。对了，此方法基于 Github Pages，所以最好先在 Github Pages 上部署好博客。</p>
<h2 style="" id="gridea-%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%85%8D%E7%BD%AE">Gridea 客户端配置</h2>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-8c474adcf59f4ae8adcc42c024c83dc3.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<p style="">需要注意的是，这里的域名一定要填写为解析到自己服务器 ip 的地址。</p>
<h2 style="" id="%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE">服务器环境配置</h2>
<blockquote>
 <p style="">在这里我使用 Ubuntu 16.04 作为演示，其他发行版大同小异。</p>
</blockquote>
<h3 style="" id="%E5%AE%89%E8%A3%85-git">安装 Git</h3>
<pre><code class="language-bash">sudo apt-get install git</code></pre>
<h3 style="" id="%E5%AE%89%E8%A3%85-caddy">安装 Caddy</h3>
<blockquote>
 <p style="">Caddy 是一个使用 Go 语言编写的 web 服务器。如果需要详细了解，请访问：<a href="https://caddyserver.com">https://caddyserver.com</a> 在这里就不在赘述。</p>
</blockquote>
<pre><code class="language-bash"># 安装 Caddy
curl https://getcaddy.com | bash -s personal http.git</code></pre>
<pre><code class="language-bash"># 配置 systemd，方便管理 Caddy 的运行状态
wget https://raw.githubusercontent.com/caddyserver/caddy/master/dist/init/linux-systemd/caddy.service
sudo cp caddy.service /etc/systemd/system/
sudo chown root:root /etc/systemd/system/caddy.service
sudo chmod 644 /etc/systemd/system/caddy.service
sudo systemctl daemon-reload
sudo systemctl start caddy.service</code></pre>
<h3 style="" id="%E7%BC%96%E5%86%99-caddyfile">编写 Caddyfile</h3>
<blockquote>
 <p style="">Caddyfile 即 Caddy 的站点配置文件，类似于 Nginx 的 nginx.conf。</p>
</blockquote>
<pre><code class="language-bash">touch /etc/caddy/Caddyfile

vim /etc/caddy/Caddyfile</code></pre>
<pre><code>https://[DOMAIN] {
    tls [EMAIL]
    gzip
    root [ROOT]
    git {GITHUB} {
        path [ROOT]
        hook /webhook [SECRET]
        hook_type github
        clone_args --recursive
        pull_args --recurse-submodules
    }
}</code></pre>
<p style="">配置详解：</p>
<ol>
 <li>
  <p style="">[DOMAIN]：即博客地址，需要注意是否已经将服务器 ip 以 A 记录的类型解析到域名。</p>
 </li>
 <li>
  <p style="">[EMAIL]：SSL 证书申请邮箱，填写自己的即可。</p>
 </li>
 <li>
  <p style="">[ROOT]：静态页面存放地址，如：/data/wwwroot/gridea</p>
 </li>
 <li>
  <p style="">[SECRET]：Github 的 webhook secret key，下面会详解如何获取。</p>
 </li>
</ol>
<p style="">根据自己的实际情况填写完成之后应该是类似这个样子的：</p>
<pre><code>https://gridea.ryanc.cc {
    tls i@ryanc.cc
    gzip
    root /data/wwwroot/gridea.ryanc.cc
    git github.com/ruibaby/ruibaby.github.io {
        path /data/wwwroot/gridea.ryanc.cc
        hook /webhook 123456
        hook_type github
        clone_args --recursive
        pull_args --recurse-submodules
    }
}</code></pre>
<h3 style="" id="github-%E7%9A%84-webhook-%E8%AE%BE%E7%BD%AE">Github 的 Webhook 设置</h3>
<blockquote>
 <p style="">为啥需要设置这个？因为我们需要做到每次更新到 Github Pages 的时候，让 Caddy 知道有更新，然后会自动拉取最新的静态页面资源，完成自动更新网站内容。</p>
</blockquote>
<p style="">找到部署的仓库，然后选择 Settings -&gt; Webhooks。点击 Add Webhook。</p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-5677b60c32f24f9daeffab7f35284ea1.png&amp;size=m" alt="image" width="893px" height="562px" style="display: inline-block"></p>
<p style=""><img src="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-34773975cac4420ea82c303c39077ec5.png&amp;size=m" alt="image.png" width="100%" height="100%" style="display: inline-block"></p>
<ol>
 <li>
  <p style="">Payload URL 的填写规则为：博客地址/webhook。</p>
 </li>
 <li>
  <p style="">Secret 自行设置。</p>
 </li>
 <li>
  <p style="">然后点击 Add webhook 即可。</p>
 </li>
 <li>
  <p style="">将填写好的 Secret 填写到上面 Caddyfile 的 [SECRET]。</p>
 </li>
</ol>
<h3 style="" id="%E5%90%AF%E5%8A%A8-caddy">启动 Caddy</h3>
<p style="">进行到这里，我们只需要启动 Caddy 服务即可。</p>
<pre><code class="language-bash"># 开机自启 Caddy
sudo systemctl enable caddy.service</code></pre>
<pre><code class="language-bash"># 启动 Caddy
sudo service caddy start</code></pre>
<pre><code class="language-bash"># 重启 Caddy
sudo service caddy restart</code></pre>
<pre><code class="language-bash"># 停止 Caddy
sudo service caddy stop</code></pre>
<h2 style="" id="%E7%BB%93%E5%B0%BE">结尾</h2>
<p style="">其实这种方式适用于任何部署在 Github Pages 的静态博客，有兴趣的小伙伴可以自己去折腾试试。</p>]]></description><guid isPermaLink="false">/archives/deploy-gridea-with-caddy</guid><dc:creator>Ryan Wang</dc:creator><enclosure url="https://ryanc.cc/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2020%2F12%2Fimage-8c474adcf59f4ae8adcc42c024c83dc3.png&amp;size=m" type="image/jpeg" length="58627"/><pubDate>Sun, 13 Oct 2019 13:05:00 GMT</pubDate></item></channel></rss>