因业务需要。调用S3的图片时需要根据参数返回合适的大小。URL地址携带图片宽度参数,高度自适应
方法实现思路
实现思路
要实现“访问某图片时,路径带宽高参数,返回对应尺寸的图片”,查阅资料后一般有以下几种方案:
使用 AWS CloudFront + Lambda@Edge 动态处理
将 S3 作为源,前面用 CloudFront CDN。
用 Lambda@Edge(CloudFront 的边缘函数)拦截请求,解析 URL 中的宽高参数。
Lambda@Edge 调用图像处理库(如 Sharp)对 S3 上的原图进行缩放,返回处理后图片。
这种方式实时处理图片,无需预生成,但会增加延迟和成本。
预生成多尺寸图片存储到 S3
在上传图片时,通过程序预生成不同尺寸版本,比如 100x100、200x200、400x400 等。
不同尺寸图片命名规范,比如 image_100x100.jpg。
前端访问时根据需求请求对应尺寸的图片 URL。
优点是访问快,缺点是存储多,灵活度低。
使用专门的图像处理服务
使用第三方图片服务(如 Imgix、Cloudinary、Thumbor、七牛云图像处理等)。
这些服务能根据 URL 参数动态调整尺寸、裁剪、压缩等。
S3 仍存储原图,图片服务做转码。
这类服务通常通过 CDN 提供高性能访问。
使用 AWS 自家的 Image Handler 解决方案
AWS 提供 Image Handler(基于 Lambda + API Gateway + S3 + CloudFront)的开源解决方案。
可以通过 URL 传入宽高参数动态生成图片。
部署稍复杂,但全托管且弹性伸缩。
这是介于 1 和 4 之间的一种灵活、自定义性更强的方案,并不属于 CloudFront + Lambda@Edge,也不直接使用 AWS 官方的 Image Handler。举例如下
🛠️ 部署步骤概览
- 创建一个用于上传原图的 S3 存储桶
- 配置 Lambda 函数,读取原图、压缩生成缩略图
- 使用 API Gateway 暴露 Lambda 接口(GET 请求处理缩图)
- 使用 CloudFront 绑定 API Gateway 或 S3,并启用缓存策略
🚀 功能亮点
- 支持按需缩放原图,生成如 600px 宽的缩略图
- S3 原图访问触发 Lambda 自动生成缩略图并缓存
- 支持 CloudFront 加速分发,优化全球访问性能
- 使用 API Gateway 控制缩略图访问权限(可选)
🧩 技术栈
- AWS Lambda
- Amazon S3
- API Gateway
- CloudFront
- Node.js + Sharp
#原始地址
https://d319sairpbc9f2.cloudfront.net/gemini_generation_images/2025-07-28/1142365.jpg
#携带参数转化后地址
https://d1hixp1jt1wegi.cloudfront.net/100/start-d319sairpbc9f2-end/gemini_generation_images/2025-07-28/1142365.jpg
原始地址分解
CF分配地址1 | 路径 | 图片文件 |
---|---|---|
https://d319sairpbc9f2.cloudfront.net | /gemini_generation_images/2025-07-28 | /1142365.jpg |
携带参数转化后地址分解
CF分配地址2 | 图片宽度 | CF分配地址1前缀标识 | 路径 | 图片文件 |
---|---|---|---|---|
https://d1hixp1jt1wegi.cloudfront.net | /100 | /start-d319sairpbc9f2-end | /gemini_generation_images/2025-07-28 | /1142365.jpg |
- 配置网关API,如图
- 创建CloudFront,选择源为刚刚创建的网关,如图
- 用nodejs/paython写Lambda代码。这里以nodejs为例。
index.js
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const sharp = require("sharp");
exports.handler = async (event) => {
try {
console.log("Event received:", JSON.stringify(event));
// 参数处理:默认宽度600,url必填
const width = event.queryStringParameters?.width
? parseInt(event.queryStringParameters.width)
: 600;
const imageUrl = decodeURIComponent(event.queryStringParameters?.url || "");
if (!imageUrl.startsWith("http")) {
return {
statusCode: 400,
body: JSON.stringify({ error: "Invalid URL format" })
};
}
if (!imageUrl) {
return {
statusCode: 400,
body: JSON.stringify({ error: "Missing required parameter: url" })
};
}
const parsedUrl = new URL(imageUrl);
const hostname = parsedUrl.hostname;
const key = parsedUrl.pathname.startsWith("/") ? parsedUrl.pathname.substring(1) : parsedUrl.pathname;
// 根据实际情况 填写YOUR CLOUDFRONT URL & YOUR BUCKETNAME
let bucketName;
if (hostname === "[YOUR CLOUDFRONT URL 1].cloudfront.net") {
bucketName = "[YOUR BUCKETNAME 1]";
} else if (hostname === "[YOUR CLOUDFRONT URL 2].cloudfront.net") {
bucketName = "[YOUR BUCKETNAME 2]";
} else {
return {
statusCode: 400,
body: JSON.stringify({ error: `Unsupported CDN hostname: ${hostname}` })
};
}
console.log(`Fetching from bucket: ${bucketName}, key: ${key}, width: ${width}`);
// 从S3获取图片,根据实际情况填写区域region的值,这里举例“us-west-1”
const client = new S3Client({ region: "us-west-1" });
const { Body: imageStream } = await client.send(
new GetObjectCommand({
Bucket: bucketName,
Key: key
})
);
// 流式转换为Buffer
const chunks = [];
for await (const chunk of imageStream) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
// 使用Sharp处理图片
const thumbnail = await sharp(buffer)
.resize({
width: width,
withoutEnlargement: true
})
.jpeg({
quality: 80,
mozjpeg: true
})
.toBuffer();
return {
statusCode: 200,
headers: {
"Content-Type": "image/jpeg",
"access-control-allow-methods":"GET, POST, OPTIONS",
"X-Content-Type-Options": "nosniff",
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Cache-Control",
"Cache-Control": "public, max-age=604800"
},
body: thumbnail.toString("base64"),
isBase64Encoded: true
};
} catch (error) {
console.error("Error:", error);
const statusCode = error.name === "NoSuchKey" ? 404 : 500;
return {
statusCode,
body: JSON.stringify({
error: statusCode === 404 ? "Image not found" : "Internal server error222",
...(process.env.DEBUG_MODE && { details: error.message })
})
};
}
};
package.json
{
"dependencies": {"sharp": "^0.34.1"}
}
安装依赖 “sharp”,会生成sharp依赖相关的package-lock.js
npm install
准备部署目录结构
ls
├── index.js # 你的 handler 文件
├── package.json
├── package-lock.json
└── node_modules/ # 由 npm install 生成打成 ZIP 包(供 AWS Lambda 上传)
zip -r lambda.zip ./
- 打包完成后,你会得到一个 lambda.zip,可直接上传到 AWS Lambda。
- 必须在与 Lambda 运行环境兼容的系统中打包依赖(特别是 sharp)
- AWS Lambda(Node.js)运行环境是 Amazon Linux x64。
- 如果你在 macOS 或 Windows 上打包,会导致 sharp 中的 .node 文件不兼容。
正确方式:用 Docker 或 Linux 环境安装依赖。
示例(使用 Docker 安装 sharp):(因内核/其他依赖问题。这里我安装失败)
docker run -v "$PWD":/var/task -w /var/task node:18 bash -c "npm install"
www@ip-172-31-21-12:~$ uname -a
Linux ip-172-31-21-12 6.8.0-1030-aws #32~22.04.1-Ubuntu SMP Thu Jun 5 08:38:24 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
www@ip-172-31-21-12:~$ cat /proc/version
Linux version 6.8.0-1030-aws (buildd@lcy02-amd64-048) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #32~22.04.1-Ubuntu SMP Thu Jun 5 08:38:24 UTC 2025
www@ip-172-31-21-12:~$ echo "内核版本:$(uname -r)"
内核版本:6.8.0-1030-aws
www@ip-172-31-21-12:~$ echo "系统信息:"; cat /etc/os-release
系统信息:
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy