openresty+Lua+GraphicsMagick进行类似淘宝的图片处理

一.应用背景

在大多数网站中都会用到图片进行裁剪处理,特别是电商网站都会必须都用到图片进行裁剪,在不同位置都会有不同尺寸进行展示。

在通常情况下大多数采用上传后,程序把图片进行裁剪不同的缩略图。这样在单点得站点是没有什么问题,当访问量大了,服务器从一台变为多台时,问题就很明显了,很不利于扩展。

我们可以采用以下几种方式去解决:

  1. 可以使用七牛、又拍云提供的云存储及数据处理服务,解决图片的处理、存储、多节点访问速度的问题,这种方式优点是方案成熟,相应的有一定费用和开发工作,另外有一些小概率的风险,比如云服务挂掉影响本站访问。
  2. 使用第三方的图片处理程序,比如zimg,点击查看使用手册,@招牌疯子开发。zimg的性能和扩展性不错,文档也很完善,会继续保持关注。
  3. 自己撸出一套代码出来

我选择第三种方式,说起openresty是无意中发现的,发现后一发不可收拾的爱上了,图片上传删除缩略全部交由openresty配合Lua单独模块出来处理,现在来说说缩略模块的实现模式,后续可以通过GraphicsMagick来实现更多功能。

二.相关规范

  1. 链接地址
    原图访问地址:http://blog.linsongzheng.com/5/4/63c299422dfb1b80a6ba6502db5b1254.jpg
    缩略图访问地址:http://blog.linsongzheng.com/5/4/63c299422dfb1b80a6ba6502db5b1254.jpg_100x100.jpg(不同尺寸将由后面的100×100来控制,意思就是width x height)
  2. 访问流程
    先判断缩略图文件是否存在,如果存在直接显示缩略图
    如不存在则执行以下流程

    1. 判断缩略图链接与规则是否匹配,如不匹配,则404退出;如匹配跳至2
    2. 判断原图是否存在,如原图存在则跳至3,如不存在则进入下一步;
    3. 拼接Graphicsmagick命令,生成并显示缩略图

三.相关代码配置

  1. openresty的配置
    server{
        server_name blog.linsongzheng.com;
        listen 80;
        index index.html;
        root /var/www/images;
        location ~* ^(.+\.(jpg|jpeg|gif|png))_(\d+)x(\d+)(c|t|b)\.(jpg|jpeg|png|gif)$ {
            set $origin_file $document_root$1;
            set $width $3;
            set $height $4;
            set $flag $5;
            set $ext $6;
            if (!-f $origin_file) {
                return 404;
            }
            if (!-f $request_filename) {
                access_by_lua_file lua/thumbnail.lua;
            }
        }
    }
  2. Lua代码
    -- nginx thumbnail module
    -- last update : 2015/11/26
    -- version : 0.0.1
    --[[
        uri       :链接地址,如/5/4/63c299422dfb1b80a6ba6502db5b1254.jpg_328x328.jpg
        img_width :缩略图宽度
        img_width :缩略图高度
        gm_path   :gm命令路径
        flag      :标记
    ]]
    local uri = ngx.var.uri
    local img_width = ngx.var.width
    local img_height = ngx.var.height or img_width
    local cur_uri_reg = '_[0-9]+x[0-9]+'
    local gm_path = '/usr/bin/gm'
    local flag = ngx.var.flag
    
    local left,top,rw,rh,r = 0,0,img_width,img_height,img_width/img_height
    
    local p = io.popen(gm_path .. ' identify ' .. ngx.var.origin_file)
    local info = p:read('*all')
    p:close()
    
    local types = {['jpg'] = 'image/jpeg', ['jpeg'] = 'image/jpeg', ['png'] = 'image/png'}
    if info then
        _,_,origin_width,origin_height = string.find(info, cur_uri_reg)
        origin_width,origin_height = tonumber(origin_width),tonumber(origin_height)
        if r > origin_width / origin_height then
            rh = math.ceil((origin_height / origin_width) * rw)
            top = math.ceil((rh - img_height) / 2)
        else
            rw = math.ceil((origin_width / origin_height) * rh)
            left = math.ceil((rw - img_width) / 2)
        end
    end
    
    cmd = gm_path .. ' convert -quality 95 ' .. ngx.var.origin_file .. " -resize \"" .. rw .. 'x' .. rh .. ">\" +profile \"*\""
    if flag == 't' then
        top = 0
        cmd = cmd .. " -crop " .. img_width .. 'x' .. img_height .. "+" .. left .. "+0 "
    elseif flag == 'b' then
        top = rh - img_height
        cmd = cmd .. " -crop " .. img_width .. 'x' .. img_height .. "+" .. left .. "+" .. top .. " "
    else
        cmd = cmd .. " -gravity center -extent " .. img_width .. "x" .. img_height .. " "
    end
    cmd = cmd .. ngx.var.request_filename
    
    os.execute(cmd)
    ngx.header.content_type = types[ngx.var.ext]
    local f = io.open(ngx.var.request_filename)
    ngx.print(f:read('*a'))
    f:close()
    ngx.flush()
    return ngx.eof()

发表评论

电子邮件地址不会被公开。 必填项已用*标注