以前一直用又拍云、腾讯云的图片处理服务,功能挺丰富,效果也不错,但基本都是按量付费,依赖他们的存储和处理功能也挺重。我的图片都存在又拍云那边整整五年了,感觉蛮方便的,但最近随着业务慢慢往本地化迁移,发现一个问题——网络带宽真挺吃的,尤其是上传带宽,毕竟大图上传还是挺耗资源的。
所以就想着自己搭个灵活点的动态缩略图处理服务,既能减轻存储压力,又能在带宽控制上更自如点。上个月搞了个这样的功能,用 Nginx 配合 Lua 脚本,底层用 libvips 这个图片处理库,整体实现下来挺顺利,今天就和大家聊聊我的思路和实现细节。
需求背景
平时公司的网站图片多,尤其上传大的或者频繁访问不同尺寸的图,预先生成一堆缩略图,空间和管理都挺烦的。想做个服务,能根据访问的 URL 直接生成对应尺寸的缩略图,缓存起来,下次访问直接用缓存,省事又高效。
整体架构
这套方案在架构上参考了某云的处理思路:
- Nginx 拦截请求,判断 URL 有没有类似 !、_ 这样的参数标识,决定需不需要处理。
- 需要处理的请求交给 Lua 脚本,脚本负责解析参数、判断缓存是否存在,没有就调用 libvips 生成缩略图。
- 生成的 WebP 缩略图存放在内部缓存目录,只允许 Nginx 内部访问,保证安全,外部无法直接访问缓存文件。
怎么触发处理的?
怎么触发处理的?
主要就是请求路径里带上 ! 或 _,后面跟参数,比如:
主要就是请求路径里带上 ! 或 _,后面跟参数,比如:
/usr/img/2025/xxx.png!blog —— 用我写好的 blog 模板,默认宽度300,质量80
/usr/img/2025/xxx.png!fw/400 —— 直接指定宽度400,生成缩略图
/usr/img/2025/xxx.png_fw/500 —— 用下划线也行,宽度500
如果参数没意义,或者没带参数,就直接返回原图。
核心 Lua 脚本
脚本思路:
- 从 URI 里分离出原始图片路径和参数
- 参数没给或者参数非法,返回原图
- 生成缓存路径,路径里把参数里的斜杠 / 替成 _,避免文件名冲突
- 看缓存文件存不存在,存在就直接返回缓存
- 不存在,就用 libvips 命令生成 WebP 缩略图,存缓存
- 生成完,返回缓存图片
下面这段是核心片段:
local orig_path, variant = uri:match("^(/usr/img/.+%.%w+)([!_%-].*)$")
if not variant or variant == "" then
local args = ngx.var.args
if args and args:match("^[a-zA-Z0-9/_]+$") then
variant = "!" .. args
else
return ngx.exec(uri)
end
end
if variant == "!" or variant == "_" then
return ngx.exec(orig_path)
end
local safe_variant = variant:gsub("/", "_")
local cache_path = root_dir .. "/cache" .. orig_path .. safe_variant .. ".webp"
local f = io.open(cache_path, "rb")
if f then
f:close()
return ngx.exec("/cache" .. orig_path .. safe_variant .. ".webp")
end
-- 生成缩略图命令
local cmd = string.format(
"/usr/bin/vips thumbnail '%s' '%s[Q=%d]' %d --size=both",
src,
cache_path,
quality,
width
)
os.execute(cmd)
nginx配置
location ^~ /cache/ {
internal;
root /www/web;
}
location ~ ^(/usr/img/.+\.(jpg|jpeg|png|webp))([!_%-].*)$ {
default_type text/html;
content_by_lua_file /www/web/chuli/tc.lua;
}
用法举几个例子
| 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缩略图 |
总结
用这个方案,不仅图片访问响应速度提升不少,存储空间也能省很多。以后想扩展更多参数、支持裁剪、旋转什么的也不难。最重要的是,这套搭配够灵活,Lua 脚本改一改就行。
这几天折腾下来,感觉 Nginx + Lua + libvips 配合还是挺给力的,推荐给大家参考。


没有回复内容