先放资源
背景与目标:免费额度要精打细算
博客的图片存储用的是七牛和Cloudflare R2两个免费渠道,各约 10G 存储,速度都不错(七牛尤其快)。但七牛有流量限制,图片处理服务还要收费;Cloudflare 虽然大方,也只有 10G,经不起原图堆。免费额度是有限资源,本就不打算在这些方面增加成本,唯一的出路就是精打细算。
痛点:手机传大图,三头花钱
存储空间:手机随手拍的原图动辄 5~10MB,一张顶多张小图十几张的量,10G 很快见底。
流量:上传走的是自己的流量,页面每次加载还要再消耗访客与 CDN 的流量,七牛超量后直接按量计费。
处理时间:大图上传慢,服务器端处理慢,页面加载更慢。
目标:节约三要素
围绕一个词——节约:节约存储空间、节约流量、节约处理时间。三者其实指向同一个动作:在图片到达服务器之前,把它变小。
核心思路:把转换放到浏览器端
服务器端转码的两条死路
七牛图片处理(imageView2 等):功能成熟,但要收费,且上传流量照样按原图算,只省了存储不省上传。
本地服务器转码(存储工具箱等插件):服务器只有 2 核 1.9G 内存、JVM 堆 512M,解码大图直接 OutOfMemoryError,实测传一张照片就挂。
浏览器端转换是优解
现代浏览器原生支持 WebP 编码(Chrome Edge Firefox 96+ / Safari 16.4+),完全可以在上传之前,在浏览器里把图片解码、缩放、转成 WebP 再发出去。转换算力由用户设备承担,不占用服务器任何资源,0 成本;上传流量、存储空间、页面加载体积同步下降,一次改造三处受益。
三条实现路径
Halo 是打包好的程序,前端逻辑注入有三条路可走。本项目的实际演进是:油猴验证逻辑 → 注入 jar 作兜底 → 插件正式落地,转换脚本三版共用同一条代码主线。
取舍结论
油猴脚本在浏览器端把转换逻辑完整跑通(端到端 11 项验证全过),但实际使用中发现浏览器被策略禁用了扩展(Tampermonkey 图标带禁止符号),且每台电脑都要装一遍,否决。注入 jar 免扩展但每次 Halo 升级都会失效,只作兜底。最终选定插件方案:按 Halo 官方插件机制打包,后台装一次,任何浏览器、任何设备登录控制台都自动生效,Halo 升级不失效,还能随站点搬到别处。
转换规则(固定口径)
转换与否由几个判断决定,命中判断全部在浏览器端完成:
以上参数(长边、质量、跳过规则)为固定口径,集中在脚本顶部配置区,需要调整时改一处重新打包即可。
插件方案的技术实现
插件构成
插件 jar 极小(约 5.8KB),包含三部分:plugin.yaml(插件声明,Halo 安装校验用)、console/main.js(转换脚本,Halo 控制台页面加载时自动注入执行)、主类(继承 BasePlugin 的空插件入口)。正因为转换逻辑全在控制台前端脚本里,所以不依赖任何打包后的内部函数,Halo 升级也不会失效。
转换原理
拦截上传:全局拦截控制台的上传请求,XHR 和 fetch 两条路径都拦,在发出前截获文件。
解码:优先用
createImageBitmap(imageOrientation: from-image)解码,自动处理手机照片的 EXIF 方向;不支持时回退<img>加载。缩放转码:Canvas 等比缩放到长边 2048,
toBlob('image/webp', 0.75)编码。动图识别:GIF 看文件头;动图 WebP 读 RIFF 的 ANIM chunk(只读前 1024 字节),避免把动画压成静态图。
效果与收益
实测行为:3000×2000 的 PNG 大图,上传时被缩放为 2048×1365 的 WebP,控制台会打印「已转换并替换上传」及节省百分比,附件 URL 以 .webp 结尾。手机端传大图的收益最大:上传更省流量、存储占得更少、访客加载更快,同一个 10G 免费额度能存下更多图片。
安装与使用
后台 → 插件 → 安装 → 选「远程下载」页签,粘贴 jar 的下载链接(让 Halo 服务器直接下载,可绕开浏览器下载环节对 jar 文件的包装问题)。
安装完成后在插件页启用「控制台上传自动转 WebP」。
Ctrl+F5 强刷控制台,F12 控制台出现
[HaloWebP] 已启用即生效。传一张大图验证:URL 以 .webp 结尾、控制台打印节省百分比即为成功。
与存储工具箱共存
如果同时开着存储工具箱的格式转换,图片会被处理两次(浏览器转一次、服务器端再转一次),浪费服务器 CPU。二选一即可:保留插件版,关闭存储工具箱的转换功能。插件版转换在浏览器端完成,不占服务器内存,存储工具箱此前的大图 OOM 问题也不会再出现。
踩坑实录
油猴被浏览器策略禁用:Tampermonkey 图标带禁止符号,脚本安装后不生效——企业/浏览器策略层面禁用了扩展,油猴方案在这台电脑不可行。
存储工具箱 OOM:服务器 2 核 1.9G、Halo JVM 堆 512M,服务器端解码大图内存峰值超堆,传一张照片就报 OutOfMemoryError。
插件安装失败排查:上传 jar 报「缺少 plugin.yaml 配置文件或配置文件不符合规范」。用 Halo 2.22.11 与 2.27.0 官方运行包的真实校验代码测试,jar 均校验通过,排除了插件本身问题;最终定位为下载链接把 jar 包成了 zip,更名成 .jar 上传导致解析失败——改用「远程下载」让服务器直接下载原始 jar 后一次成功。
存档与延伸
本次改造的三版产物均归档在站内知识库存储,可随时取用:油猴脚本版(逻辑验证基准)与插件 jar 版(正式落地件)。插件源码与构建环境(JDK 21 + Gradle,官方 plugin-starter 模板)完整保留,调整参数可重新构建。 同一思路可以延伸到其它博客系统(WordPress 等支持自定义上传处理器的平台)、图床直传工具,以及未来任何「图片上传 → 存储」场景——原则不变:能在浏览器端完成的大小优化,就不要交给服务器和付费服务。
Halo 图片上传「浏览器端转 WebP」改造实录
https://lalalulu.cn/archives/wei-ming-ming-wen-zhang-jsBdwYnp
评论