AWS CloudFront + Lambda@Edge 动态处理 S3上的图片缩放

AWS CloudFront + Lambda@Edge 动态处理 S3上的图片缩放

2025年07月29日 作者头像 作者头像 点点墨迹 编辑

评论

0 Count

2025-07-29T06:24:31.png
因业务需要。调用S3的图片时需要根据参数返回合适的大小。URL地址携带图片宽度参数,高度自适应

方法实现思路

实现思路

要实现“访问某图片时,路径带宽高参数,返回对应尺寸的图片”,查阅资料后一般有以下几种方案:

  1. 使用 AWS CloudFront + Lambda@Edge 动态处理

将 S3 作为源,前面用 CloudFront CDN。

用 Lambda@Edge(CloudFront 的边缘函数)拦截请求,解析 URL 中的宽高参数。

Lambda@Edge 调用图像处理库(如 Sharp)对 S3 上的原图进行缩放,返回处理后图片。

这种方式实时处理图片,无需预生成,但会增加延迟和成本。

  1. 预生成多尺寸图片存储到 S3

在上传图片时,通过程序预生成不同尺寸版本,比如 100x100、200x200、400x400 等。

不同尺寸图片命名规范,比如 image_100x100.jpg。

前端访问时根据需求请求对应尺寸的图片 URL。

优点是访问快,缺点是存储多,灵活度低。

  1. 使用专门的图像处理服务

使用第三方图片服务(如 Imgix、Cloudinary、Thumbor、七牛云图像处理等)。

这些服务能根据 URL 参数动态调整尺寸、裁剪、压缩等。

S3 仍存储原图,图片服务做转码。

这类服务通常通过 CDN 提供高性能访问。

  1. 使用 AWS 自家的 Image Handler 解决方案

AWS 提供 Image Handler(基于 Lambda + API Gateway + S3 + CloudFront)的开源解决方案。

可以通过 URL 传入宽高参数动态生成图片。

部署稍复杂,但全托管且弹性伸缩。

这是介于 1 和 4 之间的一种灵活、自定义性更强的方案,并不属于 CloudFront + Lambda@Edge,也不直接使用 AWS 官方的 Image Handler。举例如下

🛠️ 部署步骤概览

  1. 创建一个用于上传原图的 S3 存储桶
  2. 配置 Lambda 函数,读取原图、压缩生成缩略图
  3. 使用 API Gateway 暴露 Lambda 接口(GET 请求处理缩图)
  4. 使用 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
  1. 配置网关API,如图

2025-07-29T07:03:27.png
2025-07-29T07:03:40.png

  1. 创建CloudFront,选择源为刚刚创建的网关,如图

2025-07-29T07:05:54.png
2025-07-29T07:06:00.png
2025-07-29T07:06:05.png

  1. 用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"}
}
  1. 安装依赖 “sharp”,会生成sharp依赖相关的package-lock.js

    npm install

  2. 准备部署目录结构

    ls
    ├── index.js # 你的 handler 文件
    ├── package.json
    ├── package-lock.json
    └── node_modules/ # 由 npm install 生成

  3. 打成 ZIP 包(供 AWS Lambda 上传)

    zip -r lambda.zip ./

  4. 打包完成后,你会得到一个 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


文件代码如下

2025-07-29T06:37:48.png

本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!
猜你喜欢

0:00