You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Lambda与Node.js上传生成PDF至S3遇0字节/不上传问题排查

问题分析与解决方案

你的问题核心在于流处理的异步时机控制,以及Lambda执行环境的权限/资源配置问题,下面分点说明并给出修复方案:

1. 流未完成写入就触发S3上传(核心问题)

PDFKit生成PDF是通过流写入文件的,pdfDoc.end()只是标记流结束,但实际写入磁盘的操作是异步的。你在调用pdfDoc.end()后立刻用fs.statSync读取文件,此时文件还没写完,所以得到的文件大小是0,上传到S3自然就是空文件;本地环境因为磁盘IO快,刚好侥幸完成写入,但Lambda的环境IO延迟更高,就会暴露这个问题。

之前你尝试用file.on("finish")但出现卡顿,大概率是因为Promise的resolve/reject没有正确绑定到上传逻辑里,导致Lambda的执行上下文无法正确结束。

2. Lambda执行角色缺少S3权限

你提到本地用的IAM用户有S3全权限,但Lambda是使用自己的执行角色来访问S3的,你的serverless.yml里没有配置对应的权限,这会导致上传失败(虽然你说要么无法上传要么0字节,这可能是两种情况并存:权限不足时上传失败,流没写完时上传空文件)。

修复后的代码与配置

第一步:修正Handler中的流处理逻辑

修改后的handler代码,确保在文件完全写入后再执行S3上传:

const PDFDocument = require('pdfkit');
const fs = require('fs');
const { join } = require('path');
const AWS = require('aws-sdk');
const s3 = new AWS.S3();

module.exports.generatePdf = async (invoiceData, context) => {
  const date = new Date().toISOString();
  const firstFour = (str) => str.split("-")[0];
  const filename = `invoice-${firstFour(String(invoiceData.invoice_id))}-${date}.pdf`;
  const pdfPath = `/tmp/${filename}`;
  
  return new Promise((resolve, reject) => {
    const pdfDoc = new PDFDocument({ margin: 50 });
    const fileStream = fs.createWriteStream(pdfPath);

    // 绑定流的管道,确保PDF内容正确写入文件
    pdfDoc.pipe(fileStream);

    // 写入PDF内容(修正原代码里的变量名错误)
    pdfDoc
      .image(join(__dirname, "/assets/ds_logo.png"), 400, 35, { width: 150, height: 100, align: "right" })
      .fillColor("#444444")
      .fontSize(20)
      .fontSize(10)
      .text(invoiceData.name, 50, 35, { align: "left" })
      .text(`${invoiceData.house_number} ${invoiceData.street_address}`, 50, 45, { align: "left" })
      .text(`${invoiceData.city}, ${invoiceData.state_code}, ${invoiceData.zip}`, 50, 55, { align: "left" })
      .moveDown();

    // 结束PDF流
    pdfDoc.end();

    // 监听文件写入完成事件,此时才执行S3上传
    fileStream.on('finish', async () => {
      try {
        const stats = fs.statSync(pdfPath);
        console.log(`filesize: ${stats.size}`);
        console.log("----- starting s3 upload -----");

        const params = {
          Key: filename,
          Body: fs.createReadStream(pdfPath),
          Bucket: "<bucket>", // 替换为你的实际桶名
          ContentType: "application/pdf"
          // 移除ContentLength参数,让AWS SDK自动识别文件大小,避免提前读取的错误值
        };

        const uploadResult = await s3.upload(params).promise();
        console.log("done");
        console.log(uploadResult);
        resolve(uploadResult);
      } catch (err) {
        console.error(err);
        reject(err);
      }
    });

    // 监听流错误事件,便于排查问题
    fileStream.on('error', (err) => {
      console.error("File write error:", err);
      reject(err);
    });

    pdfDoc.on('error', (err) => {
      console.error("PDF generation error:", err);
      reject(err);
    });
  });
};

关键修改点:

  • 新增pdfDoc.pipe(fileStream)明确绑定流的管道,确保PDF内容正确写入文件
  • 将所有逻辑包裹在Promise中,通过fileStream.on('finish')触发S3上传,保证文件完全写入后再操作
  • 移除ContentLength参数,让AWS SDK自动处理文件大小,避免提前读取的错误值
  • 添加流错误监听,便于快速定位PDF生成或文件写入的问题
  • 修正原代码里的变量名错误(比如datainvoiceDatahouse_number改为invoiceData.house_number)

第二步:配置Lambda执行角色的S3权限

修改serverless.yml,添加IAM权限,确保Lambda能访问目标S3桶:

service: pdfgenerator
frameworkVersion: "2"
provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: us-east-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:PutObject
      Resource: "arn:aws:s3:::<bucket>/*" # 替换为你的桶ARN,末尾加/*表示允许上传桶内所有对象
functions:
  generatePdf:
    handler: handler.generatePdf
    memorySize: 256 # 建议调高内存,128MB可能不足以处理PDF生成+上传
    events:
      - http:
          path: pdf
          method: get

关键修改点:

  • 添加iamRoleStatements配置,赋予LambdaPutObject权限到目标S3桶
  • 将memorySize从128MB调高到256MB(如果后续切换到Puppeteer,建议调到512MB或更高)

额外注意事项

  • Lambda的/tmp目录最大容量是512MB,生成大PDF时要注意不要超过限制
  • 如果后续切换到Puppeteer,需要注意Lambda的层配置(因为Puppeteer依赖Chromium,需要打包成Lambda层),并且要分配足够的内存和CPU
  • 可以在Lambda控制台查看CloudWatch日志,根据错误信息进一步排查问题

内容的提问来源于stack exchange,提问作者aromanarguello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 17:32:49