在当今数字化的时代,图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象,动态图片水印功能显得尤为重要。然而,直接在后端服务中集成水印功能,往往会带来代码复杂度增加、兼容性问题等诸多挑战。为了解决这些问题,我们可以利用 OpenResty 和 Lua 构建一套独立于后端应用的动态水印代理服务,既能大幅降低后端负担,又能增强系统的灵活性。
在实际业务中,我们面临着多方面的需求,主要可以分为功能性需求、性能需求和扩展性需求:
为了实现上述需求,我们采用以下方案:
以下是在 CentOS 系统上安装 OpenResty 和相关依赖的详细步骤:
sudo yum update -y
sudo yum groupinstall -y "Development Tools"
sudo yum install -y readline-devel pcre-devel openssl-devel tar gcc make tree perl curl
这些命令的作用是更新系统软件包,并安装编译和运行 OpenResty 所需的基础工具。
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
sudo yum check-update
sudo yum install -y openresty
通过这些命令,我们添加了 OpenResty 的官方 YUM 仓库,并安装了 OpenResty。
sudo yum install -y epel-release
sudo yum install -y ImageMagick ImageMagick-devel
sudo yum install -y luarocks
luarocks install magick
这里安装了 ImageMagick 及其开发库,用于图片处理。同时,安装了 LuaRocks 并使用它安装了 magick 模块。
sudo /usr/local/openresty/nginx/sbin/nginx
curl -I http://localhost
启动 OpenResty 的 Nginx 服务,并使用 curl
命令检查服务是否正常响应。
luajit -e "local magick = require('magick'); print('Magick module loaded successfully')"
运行此命令,如果输出 Magick module loaded successfully
,则说明 magick 模块安装成功。
以下是核心的图片处理逻辑,封装在 image_handler.lua
文件中:
local _M = {}
local magick = require("magick")
local tmp_dir = "/tmp/"
function _M.process_image_with_watermark(image_data, ext)
local input_path = tmp_dir .. "input_image." .. ext
local output_path = tmp_dir .. "output_image." .. ext
local watermark_path = "/var/www/images/watermark.png"
-- 保存图片到临时路径
local input_file = io.open(input_path, "wb")
if not input_file then
ngx.log(ngx.ERR, "Failed to open file for writing: ", input_path)
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say("Failed to process image")
return
end
input_file:write(image_data)
input_file:close()
-- 添加水印逻辑
local success, err = pcall(function()
local img = magick.load_image(input_path)
local watermark = magick.load_image(watermark_path)
watermark:resize(img:get_width(), img:get_height())
img:composite(watermark, 0, 0, "OverCompositeOp")
img:write(output_path)
img:destroy()
watermark:destroy()
end)
if not success then
ngx.log(ngx.ERR, "Image processing failed: ", err)
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say("Failed to process image")
return
end
-- 返回处理后的图片
local output_file = io.open(output_path, "rb")
if not output_file then
ngx.log(ngx.ERR, "Failed to open processed image")
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say("Failed to process image")
return
end
local output_data = output_file:read("*all")
output_file:close()
ngx.header.content_type = "image/" .. ext
ngx.print(output_data)
-- 清理临时文件
os.remove(input_path)
os.remove(output_path)
end
return _M
该模块定义了一个函数 process_image_with_watermark
,用于处理图片并添加水印。它将图片数据保存到临时文件,添加水印后再读取处理后的图片并返回给客户端,最后清理临时文件。
以下是支持代理服务和 URL 动态图片处理的 Nginx 配置:
worker_processes auto;
error_log logs/error.log debug;
events {
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/share/lua/5.1/?.lua;;";
lua_package_cpath "/usr/lib64/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/openresty/lualib/?.so;;";
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /usr/local/openresty/nginx/logs/access.log main;
resolver 8.8.8.8 8.8.4.4; # 使用 Google Public DNS
server {
listen 80;
server_name example.com;
location /users/ {
content_by_lua_block {
local http = require("resty.http")
local image_handler = require("image_handler")
local backend_url = "http://backend_service" .. ngx.var.request_uri
local httpc = http.new()
local res, err = httpc:request_uri(backend_url, { method = "GET" })
if not res or res.status ~= 200 then
ngx.status = ngx.HTTP_BAD_GATEWAY
ngx.say("Failed to fetch image")
return
end
image_handler.process_image_with_watermark(res.body, "jpg")
}
}
location /process_url_image {
content_by_lua_block {
local http = require("resty.http")
local image_handler = require("image_handler")
local res, err = http.new():request_uri("http://example.com/sample_image.jpg", { method = "GET" })
if not res or res.status ~= 200 then
ngx.status = ngx.HTTP_BAD_GATEWAY
ngx.say("Failed to fetch image")
return
end
image_handler.process_image_with_watermark(res.body, "jpg")
}
}
}
}
在这个配置文件中,我们定义了两个 location
块。/users/
块用于处理根据用户和图片 ID 获取图片的请求,从后端服务获取图片后调用 image_handler
模块添加水印。/process_url_image
块用于处理从指定 URL 加载图片的请求,同样调用 image_handler
模块添加水印。
通过 OpenResty 的灵活性和高性能,我们可以快速实现动态图片处理功能,并显著提升系统的可扩展性和维护性。期待您在实际应用中进行更多的探索与实践!