• 日期:2026-09-24
  • 站点:https://high33light.cn(控制台 /console)
  • 仓库:github.com/gaogaohang/halo-gao(fork 自 halo-dev/halo)
  • 涉及版本:Halo 2.23.0 → 2.26.1;应用市场插件 1.12.0 → 1.19.0

本文按时间顺序记录当天处理的四个问题:CI 构建失败、应用市场插件不可用、Halo 内核升级、Markdown 编辑/渲染体验修复。每个问题给出证据、根因和处置,最后附可复用的操作要点。


一、CI 构建失败(fork 合并上游后)

现象

将 halo-dev/halo 最新 main 合并推送到 fork 后,GitHub Actions 的 "Halo Workflow → test → Check Halo" 步骤失败,执行命令为 ./gradlew clean check。

排查过程

  1. 通过 GitHub API 拉取 run/job 信息,确认失败步骤是 Check Halo,且只运行了 111 秒(完整 check 正常需要 15~30 分钟),判断是早期失败而非测试用例失败。
  2. 对比上游:halo-dev/halo 同期全部构建通过;fork 的代码树与上游 a88cf4194 的差异仅两个文件(README.md 和 ROLLBACK_NOTICE.md),排除业务代码问题。
  3. 拿到 CI 日志末尾后定位到具体报错:
Execution failed for task ':spotlessFlexmarkCheck'
> The following files had format violations:
      ROLLBACK_NOTICE.md
          ...
          +
  Run './gradlew spotlessApply' to fix all violations.

根因

仓库根 build.gradle 中的 Spotless(flexmark)规则会检查所有 Markdown 文件,要求文件结尾保留一个空行。ROLLBACK_NOTICE.md 是 2026-03-25 手动添加的文件,提交时没有结尾空行。此前该 fork 的推送只触发过 Stale Issues 定时任务,从未跑过完整 check,所以问题一直潜伏,直到这次合并触发。

处置

在 ROLLBACK_NOTICE.md 末尾补一个空行,提交推送(commit e6e50edd0)。修复后需要等 Actions 触发(本次推送 webhook 丢失未触发,需手动关注或补推空提交)。


二、应用市场插件不可用("没法在线装插件/主题")

现象

控制台里插件/主题的"远程下载"只能手动粘贴 URL,没有在线浏览应用市场的界面。此前该功能是正常可用的。

关键认知

控制台内的"应用市场"界面本身是一个插件(app-store-integration,官方闭源,仅通过 Halo 商店分发,商店编号 app-VYJbF)。官方发行版将其作为预置插件打进发行包、启动时自动安装启用(见上游 application/build.gradle 的 downloadPluginPresets 任务与 BuiltInPluginsInitializer)。插件缺失或被删除时,控制台就只剩下核心的"粘贴 URL"安装方式。

排查过程

  1. 通过控制台 API(Bearer 个人令牌)列出已装插件,发现 app-store-integration 已安装(v1.12.0)但状态为 FAILED。
  2. 读取插件 status 中的 Ready condition,报错核心为:
java.lang.NoSuchMethodError:
  'org.springframework.web.util.UriComponentsBuilder
   org.springframework.web.util.UriComponentsBuilder.fromHttpUrl(java.lang.String)'
  at run.halo.appstore.license.LicenseServiceImpl.<init>

即插件编译时依赖的 Spring 方法 UriComponentsBuilder.fromHttpUrl(String) 在当前内核的 Spring 版本中已不存在(该方法在 Spring 6.1 弃用、后续版本移除),插件启动即崩溃。

  1. 尝试升级官方最新预设版本 1.18.1 时,API 返回:
插件要求一个最小的系统版本为 >=2.25.0, 但当前版本为 2.23.0。

由此确认线上内核实际为 2.23.0(部署于 2026-03-25,此后未再更新),与预期"已是新版"不符。

  1. 通过商店 API(https://www.halo.run/apis/api.store.halo.run/v1alpha1/applications/app-VYJbF/releases)拉取全部 release 列表,下载 1.13.1~1.17.0 五个候选 jar,逐一检查 plugin.yaml 的 requires 字段和 class 字节码中是否仍引用 fromHttpUrl:
版本 要求 Halo 版本 是否已移除 fromHttpUrl 引用
1.13.1 / 1.14.0 >=2.22.0 否(仍会崩)
1.15.0 / 1.16.0 >=2.22.0 是
1.17.0 >=2.24.0 是
1.18.x 及以上 >=2.25.0 是
  1. 选择 1.16.0(满足 2.23.0 且已修复 Spring 兼容),通过 upgrade-from-uri 接口在线升级,插件恢复 STARTED。

根因

内核在 3 月升级到 2.23.0 时 Spring 大版本升级,旧版应用市场插件(1.12.0)不兼容而崩溃;官方修复版又要求 ≥2.25.0。属于典型的"插件版本落后于内核"问题。临时用 1.16.0 兼容过渡,根治依赖内核升级(见第三节)。


三、Halo 内核升级:2.23.0 → 2.26.1

部署形态(升级前摸底)

通过 ps 与 systemd 确认(服务器内部路径已脱敏为 <数据目录>):

  • 非 Docker,裸 jar 部署:<数据目录>/halo-latest.jar
  • systemd 服务 halo.service,JVM 参数 -Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200
  • 额外配置 --spring.config.additional-location=<数据目录>/config/application.yaml,端口 8090
  • 数据目录 <数据目录>/work(含 H2 数据库、插件、主题、上传文件)

是否无损

无损。数据全部在数据目录中,与程序 jar 分离;数据库结构由内置 r2dbc-migrate 在启动时自动迁移。风险点仅在于:旧插件可能因内核升级不兼容(与第二节同类问题),表现为插件 FAILED,不影响主站,可单独处理。

操作步骤

# 1. 停服务并备份数据
systemctl stop halo
tar czf <备份文件> -C <数据目录> work config

# 2. 下载 2.26.1 发行 jar(国内服务器经 ghfast.top 镜像拉 GitHub Release)
curl -L -o /tmp/halo-2.26.1.jar \
  "https://ghfast.top/https://github.com/halo-dev/halo/releases/download/v2.26.1/halo-2.26.1.jar"

# 3. 校验后替换(保留旧 jar 以便回滚)
file /tmp/halo-2.26.1.jar                 # 应为 Zip archive,约 125MB
cp <数据目录>/halo-latest.jar <数据目录>/halo-old.jar.bak
\cp /tmp/halo-2.26.1.jar <数据目录>/halo-latest.jar
md5sum /tmp/halo-2.26.1.jar <数据目录>/halo-latest.jar   # 两个值必须一致

# 4. 启动并跟踪日志,确认 Version: 2.26.1
systemctl start halo
journalctl -u halo -f

过程中的两个小坑

  1. cp 被 alias 成 cp -i:覆盖时提示 overwrite?,直接回车等于回答"否",必须输 y 或用 \cp 绕过 alias。曾因此出现"以为换好了、实际还是旧包"(启动日志仍显示 Version: 2.23.0),靠启动日志版本号 + md5 双验证发现。
  2. 替换后启动仍为 2.23.0:即上一条的后果,重新覆盖并以 md5 校验后恢复正常。

升级后验证

  • 启动日志 Starting Application v2.26.1,数据库迁移约 1~3 分钟。
  • 通过 API 轮询插件状态:升级后所有插件被 reconciler 逐个重启,最终 10 个插件全部 STARTED,无一个因内核升级挂掉。
  • 将应用市场升级到 1.19.0(要求 >=2.26.0,此时满足)。
  • 补装此前缺失的 plugin-ai-foundation v1.1.0(要求 >=2.26.0)。注意:让服务器从 GitHub 直链下载会 504(国内服务器到 GitHub 链路不稳定,openresty 网关先超时),改为在本机下载 jar 后通过 POST /plugins/install(multipart)上传安装,成功。

回滚方案(未使用,留档)

systemctl stop halo
cp <数据目录>/halo-old.jar.bak <数据目录>/halo-latest.jar
systemctl start halo

数据未变更时无需恢复数据备份;如数据库迁移后需回滚,则用数据备份整体还原数据目录(H2 结构不向下兼容)。

四、Markdown 写作与渲染修复

背景

写作习惯:Typora(纯 Markdown)→ 复制 → 粘贴进 Halo 控制台。控制台内置的是 ProseMirror 块状富文本编辑器,不具备 Markdown 源码编辑能力,粘贴 Typora 富文本后格式退化,体验差。

处置一:安装 Vditor Markdown 编辑器

  • 插件:vditor-mde v1.10.3(justice2001/halo-plugin-vditor,要求 >=2.14.0)。
  • 安装方式同上(本机下载 + multipart 上传),启用后 STARTED。
  • 效果:文章编辑页右上角可切换编辑器为 Vditor,纯 Markdown 编辑 + 实时预览,Typora 内容可直接粘贴;选择会被记住。

处置二:修复已被写坏的一篇文章

文章:《LangChain 官方开源了个"AI 软件工厂",我把源码扒了个底朝天》。

问题定位:通过 API 读取该文章正文(GET /posts/{name}/head-content),发现整篇文章的 raw 是被一个巨型代码块包裹的 HTML:

<pre><code class="language-markdown"># LangChain 官方开源了个…
…(全文 Markdown 原文,引号被转义为 ")…
</code></pre>

rawType 为 HTML。这解释了为什么编辑器里满屏裸露的 **、|---|、``` 符号:内容整体退化成了一个"代码块"。

修复步骤:

  1. 用正则拆掉 <pre><code class="language-markdown">…</code></pre> 包裹层,还原 HTML 实体(&quot; &#39; &lt; &gt; &amp;)得到纯净 Markdown(3674 字符)。
  2. PUT /posts/{name}/content,body {raw, rawType, snapshotName}。
    • 注意:第一次误用 rawType: "text/markdown",导致前台正文渲染为空。对照站内其他正常文章,其 rawType 均为 markdown,改回 markdown 后正常。Halo 对两种值的行为不一致,markdown 是主题实际识别的值。
  3. 内容更新后 releaseSnapshot 不会自动同步,必须再调用一次 PUT /posts/{name}/publish(注意是 PUT 不是 POST)重新发布,前台才切换到新内容。
  4. 验证时的一个易误判点:修复后前台页面由 Vditor 插件改为浏览器端渲染(服务端 HTML 里正文为空,取而代之的是 vditor-render 脚本和 data-type 占位容器)。用 curl/脚本抓 HTML 会误以为"正文没了",需以浏览器实际显示为准,或检查 release-content 的 rawType 与内容。
  5. 用户最后重新发布一次,前台显示正常(标题、目录、代码块、表格、ASCII 架构图均正确)。

结论

  • 内核/插件层面已具备完整的 Markdown 工作流:Vditor 编辑(后台)+ 主题 Markdown 渲染(前台)。
  • 建议此后新文章统一使用 Vditor 编辑器录入,避免块状编辑器对 Markdown 粘贴的退化问题。

附:可复用的操作要点

商店 API(公开,无需认证)

  • 应用信息:GET https://www.halo.run/apis/api.store.halo.run/v1alpha1/applications?names=app-xxxxx
  • 应用全部 release:GET https://www.halo.run/apis/api.store.halo.run/v1alpha1/applications/app-xxxxx/releases(含 asset 名、版本、spec.artifact.requires 最低内核版本)
  • 下载直链格式:https://www.halo.run/store/apps/{appId}/releases/download/{releaseName}/assets/{assetName}

经验

  1. 插件兼容性排查:插件 FAILED 先看 status.conditions[type=Ready].message;跨版本选择时用 jar 内 plugin.yaml 的 requires + 字节码方法引用做双验证,比翻 release note 可靠。
  2. 国内服务器拉 GitHub 不稳定:服务器侧 install-from-uri 指向 GitHub 直链容易 504;改为"本机下载(可走 ghfast.top 等镜像)+ multipart 上传"最稳。
  3. 换包操作必须双重验证:启动日志版本号 + 文件 md5,防止 cp -i alias、下载不完整等静默失败。
  4. Halo 正文更新 ≠ 前台更新:PUT content 只改 head 快照,需再 publish 同步 release 快照;且前台可能有 page-cache 静态缓存,验证时用浏览器强刷。
  5. rawType 值敏感:实测主题识别 markdown,text/markdown 会导致正文渲染为空。