在做 Web 开发,图片处理通常绕不开这些方案:
阿里云 OSS 图片处理
七牛云(imageView2)
又拍云(云处理)
或者自研图片服务
这些方案确实好用,但也有一些现实问题:
流量费用高,请求量一大账单很难看
处理规则受限制,很多高级能力要额外付费
厂商绑定严重,迁移成本极高
出海 / 私有化困难,数据无法完全自控
今天介绍一个更"可控"的方案:imgproxy(自建图片处理服务)
一、imgproxy 是什么?
imgproxy 是一个通过 URL 实时处理图片的开源服务,由 Evil Martians 团队使用 Go 语言开发,底层依赖性能极强的 libvips 图像库。
你只需要:
提供原图 URL
拼接处理参数到 imgproxy URL
imgproxy 实时拉取原图、处理后返回
http://imgproxy/rs:fit:300:300/plain/https://xxx.com/a.jpg
就能得到一张 300×300、自动适配比例的图片。整个过程无需预先生成缩略图,也无需在后端写任何逻辑。
两个版本:
对于绝大多数团队,开源版已经足够。
二、核心能力详解
支持的图片格式
imgproxy 开源版支持的输入/输出格式相当丰富:
其中 AVIF 支持是一个重大亮点。AVIF 是目前压缩率最高的图片格式,相比 WebP 再节省 20–50% 体积,但国内各大云厂商的免费套餐普遍不包含 AVIF 转码能力。
URL 参数语法速查
imgproxy 的 URL 结构如下:
http://imgproxy/{签名}/{处理参数}/plain/{原图URL}
常用参数组合举例:
# 等比缩放,最长边不超过 500px
/rs:fit:500:500/
# 强制裁剪为 400x300,从中心取
/rs:fill:400:300/g:sm/
# 转换为 WebP 格式
/rs:fit:800:0/f:webp/
# 添加水印(需配置水印图)
/rs:fit:800:0/wm:0.8:soea:10:10/
# 图片质量压缩到 80%
/rs:fit:800:0/q:80/
# 锐化
/rs:fit:800:0/sh:0.5/
# 旋转 90 度
/rs:fit:800:0/rot:90/
# 组合:缩放 + 转 WebP + 压缩质量
/rs:fill:400:300/f:webp/q:85/
imgproxy 参数是可以自由组合的,这正是它比云厂商方案灵活得多的原因。
三、国内常见方案对比
1️⃣ vs 阿里云 OSS 图片处理
OSS 写法示例
# 缩放
https://bucket.oss-cn-hangzhou.aliyuncs.com/a.jpg?x-oss-process=image/resize,w_300
# 裁剪
https://bucket.oss-cn-hangzhou.aliyuncs.com/a.jpg?x-oss-process=image/crop,w_300,h_300
# 转格式 + 压缩
https://bucket.oss-cn-hangzhou.aliyuncs.com/a.jpg?x-oss-process=image/format,webp/quality,q_80
OSS 成本分析
以一个月均 1 亿次图片请求、平均原图 500KB 的业务为例:
换成 imgproxy + 2 核 4G 服务器(约 200 元/月),处理后图片走 CDN 缓存,成本差距显著。
对比总结
结论:
小项目、流量有限:OSS 更省事
日均请求超过 500 万次:imgproxy 开始显现成本优势
有私有化需求:只能选 imgproxy
2️⃣ vs 七牛云(imageView2)
七牛参数示例
# 缩放裁剪
?imageView2/1/w/300/h/300
# 质量压缩
?imageView2/2/w/800/q/80
# 转 WebP
?imageView2/2/w/800/format/webp
七牛的隐藏限制
七牛的图片处理看似免费,但实际上:
免费空间有带宽限制,超出后按流量计费
高级能力(如 AVIF 转码、SVG 渲染)属于增值服务
样式别名(URL 美化)和图片审核均需额外付费
免费套餐中 HTTP 访问免费,HTTPS 流量另计费
3️⃣ vs 又拍云
又拍云的图片处理接口叫"云处理(UPYUN Processing)",能力较为完整,但:
免费额度主要是存储,图片处理按请求另计
配置学习曲线较高,操作台不如 OSS 直观
私有化版本功能受限,价格昂贵
又拍云更适合视频处理场景(其视频转码生态更完善)。纯图片处理场景,性价比不如 imgproxy。
4️⃣ vs 自研方案
很多公司的"图片服务"是这样长的:
// Java + Thumbnailator
Thumbnails.of(inputFile)
.size(300, 300)
.outputFormat("webp")
.toFile(outputFile);
或者:
# Python + Pillow
from PIL import Image
img = Image.open("input.jpg")
img.thumbnail((300, 300))
img.save("output.webp", "WEBP", quality=85)
自研方案的典型问题
性能瓶颈
libvips 的核心优势是流式处理:它不会把整张图片加载到内存,而是按需读取像素块,内存占用仅为同等分辨率 ImageMagick 的 1/5。
架构复杂度
自研方案通常最终演变成:
上传服务 → 消息队列(RabbitMQ/Kafka)
→ 图片处理 Worker(水平扩展)
→ 处理结果存储(OSS/MinIO)
→ CDN 刷新通知
→ 数据库记录处理状态
而 imgproxy 把这条链路压缩成一个无状态的 HTTP 服务,不需要队列,不需要 Worker,不需要提前处理——用户请求时实时生成,CDN 缓存后再也不用处理第二次。
四、为什么推荐 imgproxy?
核心定位:云厂商图片处理的平替 + 自研方案的升级版
性能数据
官方 Benchmark(8 核 CPU,处理 JPEG 缩放到 400×300):
配合 CDN 缓存后,99% 的请求延迟降低到 5ms 以内(直接命中 CDN)。
安全机制
imgproxy 内置了完整的安全防护:
URL 签名(防止恶意构造请求):
# 设置密钥
IMGPROXY_KEY=your_hex_key
IMGPROXY_SALT=your_hex_salt
# 签名计算(HMAC-SHA256)
signature = HMAC-SHA256(key, salt + path)
来源限制:
# 只允许处理特定域名的图片
IMGPROXY_ALLOWED_SOURCES=https://cdn.yourcompany.com,https://oss.yourcompany.com
尺寸限制(防止爆内存攻击):
IMGPROXY_MAX_SRC_RESOLUTION=16.8 # 最大 16.8MP
IMGPROXY_MAX_RESULT_DIMENSION=8192 # 输出最大 8192px
五、快速上手
方式一:Docker 快速启动
docker run -p 8080:8080 \
-e IMGPROXY_KEY="your_key_hex" \
-e IMGPROXY_SALT="your_salt_hex" \
-e IMGPROXY_ALLOWED_SOURCES="https://" \
ghcr.io/imgproxy/imgproxy:latest
访问测试(无签名模式,仅开发用):
http://localhost:8080/rs:fit:300:300/plain/https://picsum.photos/800/600
方式二:docker-compose 生产配置
version: "3.8"
services:
imgproxy:
image: ghcr.io/imgproxy/imgproxy:latest
ports:
- "8080:8080"
environment:
# 安全配置
- IMGPROXY_KEY=your_64_char_hex_key
- IMGPROXY_SALT=your_64_char_hex_salt
# 限制来源,只允许处理自己的 OSS/MinIO
- IMGPROXY_ALLOWED_SOURCES=https://your-bucket.oss-cn-hangzhou.aliyuncs.com
# 性能配置
- IMGPROXY_CONCURRENCY=20
- IMGPROXY_MAX_SRC_RESOLUTION=16.8
- IMGPROXY_JPEG_PROGRESSIVE=true
# 自动转 WebP(如果客户端支持)
- IMGPROXY_AUTO_WEBP=true
- IMGPROXY_AUTO_AVIF=true
# 日志
- IMGPROXY_LOG_FORMAT=json
# 水印
- IMGPROXY_WATERMARK_PATH=https://your-oss.com/watermark.png
restart: always
deploy:
resources:
limits:
cpus: "4"
memory: "2G"
生成签名 URL(Node.js 示例)
const crypto = require('crypto');
function signImgproxyUrl(path, key, salt) {
const keyBin = Buffer.from(key, 'hex');
const saltBin = Buffer.from(salt, 'hex');
const hmac = crypto.createHmac('sha256', keyBin);
hmac.update(saltBin);
hmac.update(Buffer.from(path));
return hmac.digest('base64url');
}
function buildImgproxyUrl(baseUrl, originalUrl, width, height, key, salt) {
const encodedUrl = Buffer.from(originalUrl).toString('base64url');
const path = `/rs:fill:${width}:${height}/plain/${encodedUrl}`;
const signature = signImgproxyUrl(path, key, salt);
return `${baseUrl}/${signature}${path}`;
}
// 使用示例
const url = buildImgproxyUrl(
'https://img.yourcompany.com',
'https://oss.yourcompany.com/products/shoe.jpg',
400, 300,
process.env.IMGPROXY_KEY,
process.env.IMGPROXY_SALT
);
// 返回:https://img.yourcompany.com/{签名}/rs:fill:400:300/plain/...
Python 签名示例
import hmac
import hashlib
import base64
def build_imgproxy_url(base_url, original_url, width, height, key_hex, salt_hex):
key = bytes.fromhex(key_hex)
salt = bytes.fromhex(salt_hex)
encoded_url = base64.urlsafe_b64encode(original_url.encode()).decode().rstrip('=')
path = f"/rs:fill:{width}:{height}/plain/{encoded_url}"
digest = hmac.new(key, salt + path.encode(), hashlib.sha256).digest()
signature = base64.urlsafe_b64encode(digest).decode().rstrip('=')
return f"{base_url}/{signature}{path}"
六、生产环境架构设计
推荐架构(国内)
用户请求
↓
阿里云 CDN / 腾讯云 CDN
↓ (Cache Miss)
imgproxy 集群(2-4 台,ECS/容器)
↓ (回源拉取原图)
OSS / MinIO(存储原图)
CDN 缓存策略建议:
# Nginx 反代 imgproxy,添加缓存头
location /img/ {
proxy_pass http://imgproxy:8080/;
proxy_cache_valid 200 30d;
add_header Cache-Control "public, max-age=2592000";
add_header Vary "Accept"; # 根据 Accept 头区分 WebP/AVIF 缓存
}
Vary: Accept 是关键——同一张图片对支持 WebP 的浏览器和不支持的浏览器,imgproxy 会返回不同格式,CDN 需要分别缓存。
私有化场景架构
用户(内网)
↓
Nginx(内网反代,鉴权)
↓
imgproxy(内网,无公网)
↓
MinIO(本地对象存储)
这个架构完全不依赖任何公网云厂商,适合金融、医疗、政务等数据合规要求高的场景。
七、常见使用场景及 URL 写法
电商商品图
# 列表页缩略图:统一 400x400,填充模式,白底
/rs:fill:400:400/bg:ffffff/f:webp/q:85/
# 详情页大图:宽度 800,保持比例
/rs:fit:800:0/f:webp/q:90/
# 移动端:宽度 375,节省流量
/rs:fit:375:0/f:webp/q:80/
用户头像
# 标准圆形头像(前端 CSS 实现圆形,imgproxy 裁正方形)
/rs:fill:200:200/g:sm/f:webp/
# 缩略头像 40x40
/rs:fill:40:40/g:sm/f:webp/q:90/
内容平台文章图
# 文章封面:1200x630(Open Graph 标准尺寸)
/rs:fill:1200:630/f:jpeg/q:85/
# 文章内图:限宽 800,超长自动截断
/rs:fit:800:0/f:webp/q:85/
水印场景
# 右下角水印,透明度 0.8,距边缘 20px
/rs:fit:800:0/wm:0.8:soea:20:20/f:webp/
# 水印图需要预先配置到 imgproxy
IMGPROXY_WATERMARK_URL=https://your-oss.com/watermark.png
八、监控与运维
健康检查接口
# imgproxy 内置健康检查
curl http://imgproxy:8080/health
# 返回 200 即正常
常见问题排查
九、迁移策略:从 OSS 切换到 imgproxy
迁移不需要一刀切,可以平滑过渡:
第一步:部署 imgproxy,URL 签名验证测试通过
第二步:前端新上传的图片改用 imgproxy URL,存量 OSS URL 保持不变
第三步:在 Nginx 层做 URL 重写,老的 OSS 处理 URL 自动转换为 imgproxy URL:
# 把 OSS 的处理 URL 重写为 imgproxy
rewrite ^/(.+)\?x-oss-process=image/resize,w_(\d+)$
/rs:fit:$2:0/plain/https://your-oss.com/$1 redirect;
第四步:观察监控,确认正常后关掉 OSS 图片处理功能
十、适用场景判断
非常适合:
电商平台:大量 SKU 图,需要多种尺寸(列表/详情/缩略图),imgproxy 按需生成,无需预生成全部变体
SaaS 多租户系统:各租户有不同 Logo、头像、附件,统一走 imgproxy 处理,逻辑集中
内容平台(CMS):文章图片需要适配 PC / 移动端多种布局,动态出图更灵活
数据可视化平台:截图服务生成的图片需要再处理压缩
对数据合规有要求的行业:金融、医疗,图片不能过第三方服务
不推荐的场景:
个人项目 / 小工具:流量小,直接用 OSS 处理省心
团队没有 DevOps 能力:imgproxy 需要维护服务,监控,扩容
需要视频处理:imgproxy 专注图片,视频还是用专门服务
十一、总结与选型建议
一句话总结:
imgproxy = 去云厂商化的图片处理方案,越大的系统越值得用
选型决策树:
业务规模小、没有运维能力?
→ OSS / 七牛,省事第一
有私有化 / 数据合规要求?
→ imgproxy,没得选
日均图片请求 > 500万次?
→ 算一算云处理费用,imgproxy ROI 通常很高
已有自研图片服务、性能是瓶颈?
→ 用 imgproxy 替换,接入成本低
imgproxy 不是"银弹",但在国内技术团队中确实被长期低估。当你的系统规模到了一定程度,图片处理的成本和灵活性都会成为真实痛点——而那正是 imgproxy 最擅长的地方。
参考资料:
imgproxy 官方文档:https://docs.imgproxy.net
imgproxy GitHub:https://github.com/imgproxy/imgproxy
libvips 性能对比:https://github.com/libvips/libvips/wiki/Speed-and-memory-use