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。