用 Nginx + Lua + libvips 搭建图片动态处理服务

以前一直用又拍云、腾讯云的图片处理服务,功能挺丰富,效果也不错,但基本都是按量付费,依赖他们的存储和处理功能也挺重。我的图片都存在又拍云那边整整五年了,感觉蛮方便的,但最近随着业务慢慢往本地化迁移,发现一个问题——网络带宽真挺吃的,尤其是上传带宽,毕竟大图上传还是挺耗资源的。
所以就想着自己搭个灵活点的动态缩略图处理服务,既能减轻存储压力,又能在带宽控制上更自如点。上个月搞了个这样的功能,用 Nginx 配合 Lua 脚本,底层用 libvips 这个图片处理库,整体实现下来挺顺利,今天就和大家聊聊我的思路和实现细节。

需求背景

平时公司的网站图片多,尤其上传大的或者频繁访问不同尺寸的图,预先生成一堆缩略图,空间和管理都挺烦的。想做个服务,能根据访问的 URL 直接生成对应尺寸的缩略图,缓存起来,下次访问直接用缓存,省事又高效。

整体架构

这套方案在架构上参考了某云的处理思路:

  1. Nginx 拦截请求,判断 URL 有没有类似 !、_ 这样的参数标识,决定需不需要处理。
  2. 需要处理的请求交给 Lua 脚本,脚本负责解析参数、判断缓存是否存在,没有就调用 libvips 生成缩略图。
  3. 生成的 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 配合还是挺给力的,推荐给大家参考。

请登录后发表评论

    没有回复内容