上个月遇到了云图片处理服务的使用体验和一些痛点,今天咱们换个思路,采用 Nginx + PHP 搭一套动态图片处理服务。
用 Nginx + Lua + libvips 搭建图片动态处理服务
以前一直用又拍云、腾讯云的图片处理服务,功能挺丰富,效果也不错,但基本都是按量付费,依赖他们的存储和处理功能也挺重。我的图片都存在又拍云那边整整五年了,感觉蛮方便的,但最近随着业务慢慢往本地化迁移,发现一个问题——网络带宽真挺吃的,尤其是上传带宽,毕竟大图上传还是挺耗资源的。 所以就想着自己...
建站相关 , 生活日记
162
为什么要做这个?
说实话,用云服务确实省心,但有几个问题一直困扰着我:
- 成本问题:按量付费虽然灵活,但被打的时候就头大。
- 依赖性:过于依赖某个服务商,文件资源长时间存储在平台。
- 灵活性:自定义处理逻辑受限于平台。
所以我就想,能不能自己搞一套?前面就尝试了纯 Nginx + Lua + libvips 搭建方案,但是有些容器化部署的服务器不方便本地装 libvips,只能想办法用原生 PHP 尝试。
整体架构思路
这套方案的核心思路:
- Nginx 负责路由和静态文件处理
- PHP 负责动态生成缩略图
- Imagick 作为底层图片处理引擎
整个流程是这样的:用户请求图片 → Nginx 判断是否需要处理 → 需处理就交给 PHP → PHP 调用 Imagick 生成 WebP 格式缩略图 → 返回给用户
# ---------------- 图片处理 ----------------
location /usr/img/ {
root /www/test/;
access_log off;
try_files $uri /go.php?$args;
}配置说明:
- root 指定了网站的根目录,所有图片都在这个目录下
- access_log off 关闭了访问日志,减少 I/O 消耗,毕竟图片访问量大
- try_files 是关键指令,它会先尝试直接访问 $uri 对应的文件,如果不存在就交给 /go.php
如果原图已经存在,Nginx 会直接返回,不会经过 PHP 处理,性能最优。只有当需要生成变体时,才会触发 PHP 处理逻辑。这种"懒加载"的方式既节省资源,又能保证响应速度。
PHP 处理脚本
<?php
$uri = $_SERVER['REQUEST_URI'];
$uri_path = parse_url($uri, PHP_URL_PATH);
// 匹配原始图片路径和变体参数
if (preg_match('#^(/usr/img/.+\.(jpg|jpeg|png|webp))([!_%-].*)?$#', $uri_path, $matches)) {
$orig_path = $matches[1];
$variant = $matches[3] ?? '';
} else {
$orig_path = $uri_path;
$variant = '';
}
// 模板定义
$templates = [
'blog' => ['width' => 300, 'quality' => 80],
'small' => ['width' => 150, 'quality' => 70],
'large' => ['width' => 800, 'quality' => 90],
];
// 默认参数
$width = 300;
$quality = 80;
// 如果 URI 带模板名,例如 !blog !large !small
if (preg_match('#!([a-zA-Z0-9_]+)#', $variant, $m)) {
$key = $m[1];
if (isset($templates[$key])) {
$width = $templates[$key]['width'];
$quality = $templates[$key]['quality'];
}
}
// 如果 URI 带 fw 参数,如 !fw/500
if (preg_match('#fw/(\d+)#', $variant, $m)) {
$width = max(20, min(5000, (int)$m[1]));
}
// 原图绝对路径
$root_dir = __DIR__ . '/../';
$src = realpath($root_dir . $orig_path);
if (!$src || strpos($src, realpath($root_dir . '/usr/img/')) !== 0) {
header("HTTP/1.0 404 Not Found");
exit;
}
// 使用 Imagick 处理图片
$im = new Imagick($src);
$im->setImageFormat('webp');
// 保持透明
if ($im->getImageAlphaChannel()) {
$im->setImageAlphaChannel(Imagick::ALPHACHANNEL_ACTIVATE);
}
// 按宽度缩放,高度自适应
$im->resizeImage($width, 0, Imagick::FILTER_LANCZOS, 1);
// 设置质量
$im->setImageCompressionQuality($quality);
// 输出图片
header("Content-Type: image/webp");
// 浏览器缓存 30 天
header("Cache-Control: public, max-age=2592000");
echo $im;
$im->destroy();
php 代码逐段解析
1. URI 解析与参数提取
if (preg_match('#^(/usr/img/.+\.(jpg|jpeg|png|webp))([!_%-].*)?$#', $uri_path, $matches)) {
$orig_path = $matches[1];
$variant = $matches[3] ?? '';
}
这段正则表达式负责从 URL 中提取两个关键信息:
- 原始图片路径:比如
/usr/img/test.jpg - 变体参数:比如
!blog或!fw/500
举个例子:
/usr/img/test.jpg!blog→ 原图路径是/usr/img/test.jpg,参数是!blog/usr/img/test.png!fw/500→ 原图路径是/usr/img/test.png,参数是!fw/500
2. 预设模板系统
$templates = [
'blog' => ['width' => 300, 'quality' => 80],
'small' => ['width' => 150, 'quality' => 70],
'large' => ['width' => 800, 'quality' => 90],
];
我预设了三种常用的图片尺寸模板,这样在使用时就不用每次都手动指定参数了:
- blog:300px 宽,质量 80(适合博客列表缩略图)
- small:150px 宽,质量 70(适合小图标、头像)
- large:800px 宽,质量 90(适合大图展示)
3. 参数解析
// 如果 URI 带模板名,例如 !blog !large !small
if (preg_match('#!([a-zA-Z0-9_]+)#', $variant, $m)) {
$key = $m[1];
if (isset($templates[$key])) {
$width = $templates[$key]['width'];
$quality = $templates[$key]['quality'];
}
}
// 如果 URI 带 fw 参数,如 !fw/500
if (preg_match('#fw/(\d+)#', $variant, $m)) {
$width = max(20, min(5000, (int)$m[1]));
}
这里有两个参数解析逻辑:
1. 模板名匹配:如果参数是 !blog 这样的,就从预设模板中读取配置
2. 自定义宽度:如果参数是 !fw/500 这样的,就直接使用指定的宽度
| URL | 处理情况 | 说明 |
|---|---|---|
/usr/img/xxx.png | 不处理 | 直接返回原图 |
/usr/img/xxx.png! | 不处理 | 参数为空,返回原图 |
/usr/img/xxx.png!blog | 处理 | 用 blog 模板,生成300宽缩略图 |
/usr/img/xxx.png!fw/400 | 处理 | 自定义宽度400px缩略图 |
/usr/img/xxx.png_fw/500 | 处理 | 用下划线触发,宽度500px缩略图 |
总结
这套 Nginx + PHP 动态图片处理方案,经过一个月的实际使用,效果还不错:
有啥问题或者想一起聊聊技术细节,随时留言!