Node.js Lambda调用S3 PutObject无法从EFS上传文件求助
Node.js Lambda将EFS文件迁移至S3无上传问题排查与修复
问题核心
你的Lambda函数能成功在EFS生成压缩文件,但S3上传操作未触发且无报错,循环仅完成压缩步骤就结束。
问题根源
- forEach不支持异步等待:forEach循环不会等待内部async函数执行完成,Lambda主线程会直接走完,导致S3上传的异步操作还没开始就被终止。
- 循环内提前return:符合条件的文件处理到一半就return,直接结束Lambda函数,后续上传逻辑完全没机会执行。
- 同步FS方法阻塞事件循环:
readdirSync、statSync这类同步方法会阻塞Node.js事件循环,导致异步的S3上传请求无法被调度执行。 - S3 Key冗余拼接:你把已经带
.zip的文件名又重复加了一次.zip,导致Key变成xxx.zip.zip,虽然不是上传失败的直接原因,但会导致S3文件命名不符合预期。
修复方案
1. 替换forEach为for...of循环
for...of原生支持async/await,能确保每个文件的异步操作完成后再处理下一个。
2. 移除循环内的return语句
把return放在整个函数末尾,确保所有文件处理完成后再返回响应。
3. 改用异步FS方法
使用fs.promises下的异步方法替代同步方法,避免阻塞事件循环。
4. 修正S3 Key冗余拼接
直接使用生成的zip文件名作为S3 Key,不要重复加.zip。
5. 完善错误捕获
确保文件流和S3上传的错误都能被捕获并打印。
修复后的完整代码
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import fs from 'fs/promises'; import path from 'path'; import ZipLocal from 'zip-local'; const s3Client = new S3Client({ region: 'us-west-2' }); export const lambdaHandler = async (event, context) => { try { const efsDirectory = "/mnt/data/"; const files = await fs.readdir(efsDirectory); for (const file of files) { const filePath = path.join(efsDirectory, file); console.log("当前处理文件:", file); console.log("文件路径:", filePath); const response = await fs.stat(filePath); console.log("文件创建时间:", response.birthtime); const fileBirthTime = response.birthtime; const currentTime = new Date(); const timeDifference = currentTime - fileBirthTime; const timeDifferenceInMin = (timeDifference / 1000) / 60; console.log("文件创建时长(分钟):", timeDifferenceInMin); if (timeDifferenceInMin >= 1) { console.log("文件创建时长≥1分钟,开始处理"); const filePathWithoutExt = path.parse(file).name; const zipFilePath = path.join(efsDirectory, `${filePathWithoutExt}.zip`); console.log("压缩文件路径:", zipFilePath); // 同步压缩文件(如果需要异步可改用zip-local的异步API) ZipLocal.sync.zip(filePath).compress().save(zipFilePath); console.log("文件压缩完成"); // 创建文件流并监听错误 const fileStream = fs.createReadStream(zipFilePath); fileStream.on('error', (error) => { console.error("文件流错误:", error.message); throw error; }); const s3Key = path.basename(zipFilePath); console.log("S3 Key:", s3Key); const bucketName = "matomo-lambda-test-bucket"; const params = { Bucket: bucketName, Key: s3Key, Body: fileStream }; console.log("S3上传参数:", params); const uploadRes = await s3Client.send(new PutObjectCommand(params)); console.log("S3上传成功响应:", uploadRes); const bucketUrl = `https://${bucketName}.s3.us-west-2.amazonaws.com/${s3Key}`; console.log("S3文件URL:", bucketUrl); // 删除原文件 await fs.unlink(filePath); console.log("已删除原文件:", file); // 打印当前EFS目录文件 const remainingFiles = await fs.readdir(efsDirectory); console.log("当前EFS目录剩余文件:", remainingFiles); } else { console.log("文件创建时长<1分钟,跳过处理"); } } return { statusCode: 200, body: JSON.stringify({ message: '所有符合条件的文件处理完成' }) }; } catch (err) { console.error("函数执行错误:", err); return { statusCode: 500, body: JSON.stringify({ error: err.message }) }; } };
额外注意事项
- 调整Lambda超时时间:如果处理大文件,需将Lambda超时时间设置足够长(比如5分钟),避免上传未完成就被终止。
- 检查权限配置:Lambda执行角色需要具备S3的
PutObject权限,以及EFS的读写权限。 - 验证EFS挂载:确保Lambda已正确挂载EFS,
/mnt/data/路径可正常访问。
内容的提问来源于stack exchange,提问作者Rehan CH
相关产品推荐
相关产品推荐

