编辑器上传图片不压缩怎么办,配置里那个开关是摆设

9 月 30 号晚上九点多,我盯着一个付款页看加载。首屏 1.67MB,图片占了 1.46MB —— 87%。服务器负载 0.00,CPU 闲着,时间全花在等图下载。

先说结论:UEditor 的 config.json 里有两个压缩开关,imageCompressEnable 和 imageCompressBorder。这两个键在 PHP 版里从来没被读过,所以写 true 只是写着。想让它压,得自己在文件落盘之后压。

先怀疑是配置没开

我第一反应是后台没开压缩。这个站点用的是 UEditor,配置在 ueditor/php/config.json。

打开一看,压缩相关的两行都在:

"imageCompressEnable": true,
"imageCompressBorder": 1600

true。长边 1600。这配置看着一点毛病没有。

去代码里找这个开关

配置开了还不压,那就找读这个键的代码。

grep -rn "compress\|resize\|imagecreate" ueditor/php/Uploader.class.php
ls ueditor/php/upload/image/

第一条命令的输出是空的。零命中。

不是大小写问题,也不是文件找错 —— 上面那条 ls 证明目录在、文件在。就是这个 Uploader.class.php 里,连 compress 这个词都没出现过。

也就是说:配置里的开关,上传代码一句都没读过。这个功能在 PHP 版里压根没实现。至于为什么没实现,我没去翻它的版本历史,说不准是移植的时候把压缩那段漏了。

顺手把另一个入口也看了

这个站只有两个上传入口,另一个是群图上传,在 Upload.php 里。三个模块各放了一份,代码一模一样,核心就一行:

move_uploaded_file($tmp_arr["tmp_name"], $UP_file.$tmp_name);

临时文件搬过去,结束。没有尺寸限制,没有质量重编码。

所以问题不在某一个开关,是整条上传链路都没有压缩这一步。数据能说明白:

两个上传入口的压缩体检:图文编辑器读不读配置、群图上传是否重编码、图片实际显示尺寸与浪费倍数
两个上传入口的压缩体检:图文编辑器读不读配置、群图上传是否重编码、图片实际显示尺寸与浪费倍数

群主图原始 2688×3584、548KB,模板里直接 <img src="...">,没写尺寸,实际显示宽度只有 375px。13 张群成员头像加起来 445KB,显示尺寸 36px。服务器上有 GD 2.1.0(PHP 自带),JPEG/PNG/WebP 都支持,没有 ImageMagick —— 也就是说,压得动,只是没人让它压。

先动手压存量

改上传口之前,先把已经躺在服务器上的图压一遍,这样立刻能看到效果。

写了个 PHP 脚本,用 GD 重编码,按目录分别给长边和质量。跑完的结果:53.85MB → 20.72MB,省 61.5%,274 张,失败 0。

这一步踩了个坑,跟压缩本身没关系,跟"按什么边缩"有关系。

脚本默认按长边缩到 750px。有一张 1080×11576 的长截图 —— 长边是 11576,缩到 750 之后,宽度只剩 70px。整张图变成一条细线,上面的字一个都认不出。

破法是把长宽比先算出来:

$ratio = $h / $w;
if ($ratio > 2.5) {                    // 长截图:按宽度缩
    $nw = $maxW;
    $nh = (int)round($h * $maxW / $w);
} else {                               // 普通图:按长边缩
    $scale = min($maxW / $w, $maxH / $h, 1);
    $nw = (int)round($w * $scale);
    $nh = (int)round($h * $scale);
}

还有一个配套的雷:默认 memory_limit 是 128M,解这种超长图直接 OOM 挂掉。脚本开头必须 ini_set('memory_limit','1024M'),不然跑到长截图就断。

第三个坑跟 PHP 无关,是 shell 的。脚本是拿 ssh 远端执行的,写成 ssh 'php -r "...$var..."' 的时候,单引号里的 PHP 变量会先被本地 shell 吃掉,传过去变成空字符串。要么转义成 \$var,要么干脆别拼变量,直接写死成 PHP 数组。

上传口加上压缩

存量压完,回来改入口。三个模块的 Upload.php 各改一处,UEditor 的 Uploader.class.php 在文件顶部内联一个独立函数 —— 它不走框架,common.php 不会被自动加载,所以不能指望复用框架里的函数。

改完用真实 HTTP 上传测了两张:

3024x4032   10.16MB  ->  563x750   227.6KB   -97.8%
2400x3200    563.4KB ->  563x750    32.0KB   -94.3%

第一张是手机原图。第二张是平滑渐变,压完肉眼看不出来。

页面侧的账也对得上:179 个页面的首屏从 1.67MB 掉到 584KB,群主图 548KB / 0.74s 变成 105KB / 0.18s。

压缩前后对照:首屏总量、群主图体积、头像总量、单张手机原图
压缩前后对照:首屏总量、群主图体积、头像总量、单张手机原图

后缀也是猜的

改这段代码时看到另一个问题。原来的写法是 explode("/", $tmp_arr['type']) 取后缀 —— type 是浏览器发过来的 MIME,客户端想填什么就填什么。

结果就是目录里躺着后缀写 .png、打开其实是 JPEG 照片的文件。压缩函数要是按后缀选编码器,这种文件会被压成一张坏图,而且不报错。

所以顺手改成不信后缀:

$info = @getimagesize($tmp);
$ext  = image_type_to_extension($info[2], false);   // 按真实内容判
if ($ext === 'jpeg') { $ext = 'jpg'; }              // 归一化

改之前先把这几个文件备份了,图片也另备了一份 —— 55MB,三个子目录,文件数逐个对过。回滚只还原代码,不动数据库、不删图。

两个我没做的决定

PNG 没转 JPG。实测 PNG 缩小之后无损重编码反而变大,531KB 变成 714.9KB。全站真实 PNG 最大才 165KB,占文件总数 5%,为它冒画质的险不划算。

WebP 也没上。服务器 GD 支持,能再省三四成,但老安卓和部分微信内核有裂图风险 —— 这是付费页,图裂了比慢几秒更伤。

压参数的取法我之前单独写过一篇实测表,群图长边 750 / q82、长截图按宽 750 / q80 就是那么定下来的,这里不重复:WebP 压缩参数,我实测出这张表。

顺带一句,同一条上传链路上还有个更隐蔽的问题 —— 一次选多张图,最后只落了一张,那是文件名互相覆盖,跟压缩没关系:批量上传图片只传了一张怎么办。判断体积问题先分清是哪一类占的,这个思路我在 PDF 那篇也用过:pdf文件太大上传不了怎么办。

改完我又去翻了一遍那个付款页。首屏 584KB。

发表评论