上周Halo 博客用的阿里云 OSS 自定义域名过期了,所有图片链接还是指向旧的 images.jsdiff.com,得全部换成 OSS 的原生地址 mdanimage.oss-cn-shenzhen.aliyuncs.com。
几百篇文章,几千张图,手动改?不可能。得从数据库层面批量替换。
倒腾了两个小时,记录一下完整思路和操作。给同样用 Halo 的朋友留个参考。
背景
Halo 2.x 是一个 Java 写的开源博客系统,用的人不少。默认内置了 H2 数据库(嵌入式),也支持 MySQL。
之前把 OSS 绑了自定义域名 images.jsdiff.com 做图床,后来域名没续费,OSS 给了个原生地址。需要在数据库里找到所有旧链接,替换成新的。
第一步:找到数据库
Halo 2.x 默认的数据库文件在安装目录下:
ls -la ~/.halo2/db/
# halo-next.mv.db ← 这就是 H2 数据库文件
H2 是 Java 写的嵌入式数据库,文件格式是 .mv.db。你要直接打开?不行,得像连 MySQL 一样用 JDBC 连上去。
好在 Halo 的 JAR 包里自带了 H2 驱动。从 fat jar 里解出来就行:
unzip -o /root/backup/halo-2.20.0.jar 'BOOT-INF/lib/h2*'
# 得到 BOOT-INF/lib/h2-2.3.232.jar
第二步:连上数据库,看结构
用 H2 Shell 连上去看看:
java -cp h2-2.3.232.jar org.h2.tools.Shell \
-url 'jdbc:h2:file:/root/.halo2/db/halo-next;MODE=MySQL' \
-user admin -password 123456 \
-sql 'SHOW TABLES;'
然后你会发现——Halo 2.x 只有一个表,叫 EXTENSIONS。
没错,就一张表。
Halo 把文章、附件、配置、分类、标签……所有数据都存成 JSON 格式的 BLOB,放在这一张表里。数据在 DATA 字段里存着,NAME 是路径标识。
这就是典型的 Extension 模式。好处是灵活,坏处是 SQL 维护起来有点绕。
第三步:找到包含旧链接的记录
H2 的 BLOB 不能直接 LIKE,需要转成字符串:
SELECT NAME FROM EXTENSIONS
WHERE UTF8TOSTRING(DATA) LIKE '%https://images.jsdiff%';
查出来 385 条记录,分布如下:
| 类型 | 数量 |
|---|---|
| 文章正文(snapshots) | 138 |
| 文章元信息(posts) | 122 |
| 附件记录(attachments) | 107 |
| 相册(photos) | 10 |
| 系统配置(configmaps) | 4 |
| 分类/标签(categories/tags) | 4 |
覆盖面很全啊,从文章内容到封面图、系统 Logo、标签图标,全都有。
第四步:批量替换
先做备份,这是在任何数据库操作之前的第一原则:
cp halo-next.mv.db halo-next.mv.db.bak
然后一条 UPDATE 搞定:
UPDATE EXTENSIONS SET DATA = STRINGTOUTF8(
REPLACE(UTF8TOSTRING(DATA),
'https://images.jsdiff.com',
'https://mdanimage.oss-cn-shenzhen.aliyuncs.com')
) WHERE UTF8TOSTRING(DATA) LIKE '%https://images.jsdiff%';
这条 SQL 干了三件事:
- UTF8TOSTRING(DATA) — 把 BLOB 二进制转成字符串
- REPLACE(…) — 全局替换旧域名为新域名
- STRINGTOUTF8(…) — 把替换后的字符串写回 BLOB
385 条记录,1.1 秒跑完。
第五步:验证结果
-- 检查旧链接还有没有残留
SELECT COUNT(*) FROM EXTENSIONS
WHERE UTF8TOSTRING(DATA) LIKE '%https://images.jsdiff%';
-- 结果: 0 ✅
-- 检查新链接是否写入成功
SELECT COUNT(*) FROM EXTENSIONS
WHERE UTF8TOSTRING(DATA) LIKE '%mdanimage.oss-cn-shenzhen.aliyuncs.com%';
-- 结果: 752 ✅(有些页面里同一张图出现了多次)
-- 随手查一条确认
SELECT NAME FROM EXTENSIONS
WHERE UTF8TOSTRING(DATA) LIKE '%mdanimage.oss%' LIMIT 5;
确认无误后,重启 Halo 服务:
systemctl restart halo
踩坑记录
1. BLOB 不能直接作为字符串操作
H2 的 DATA 字段是 BINARY LARGE OBJECT,不能直接用 LIKE、REPLACE。
解决方案:用 UTF8TOSTRING() 和 STRINGTOUTF8() 做类型转换。
开始我试了 CONVERT(DATA, VARCHAR),报了一堆类型转换错误。后来查 H2 文档才发现 UTF8TOSTRING 才是正确的姿势。
2. 注意不要误伤文件名
有个坑:很多图片的文件名叫 xxx_images_xxx.png,里面也包含 “images” 这个词。搜索时不小心会把它们也匹配到。
解决办法:查询条件里加上完整 URL 前缀 https://,避免匹配到纯文件名的记录。
3. Halo 的 EXTENSIONS 模式
Halo 2.x 只用一张表存所有数据,刚开始查表时我一脸懵——“就一张表?”。
后来看了 Halo 的源码才明白,这是借鉴了 Kubernetes 的 Extension 模式。所有实体都序列化成 JSON 存进去。理解了这个模式,后面就顺了。
一些思考
说实话,Halo 用 EXTENSIONS 单表存 JSON 的方式,对运维来说不太友好。
好处是开发时不用关心数据库迁移,加字段直接改 JSON 就行。坏处是你没法用标准的 SQL 关系查询,遇到批量替换这种场景,就得像我这样写裸字符串操作。
不过话说回来,大部分博客运维场景其实不需要复杂查询,这种设计也能理解。
对于这次操作,最值钱的教训就是:做数据库操作前一定先备份。385 条记录,如果update写错了,没有备份就哭了。
写在最后
这次替换花了两小时查资料 + 10 分钟操作。关键步骤其实就几条 SQL,但把 Halo 的存储结构搞清楚花了不少时间。
希望能帮到同样在用 Halo + OSS 图床的朋友。如果你也有类似需求,记得先备份、小心文件名误伤、替换完重启服务。
有什么问题欢迎留言交流。
相关链接:
共同学习,写下你的评论
评论加载中...
作者其他优质文章