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

NestJS StreamableFile传输PDF时字节异常问题求助

NestJS流式返回S3 PDF文件损坏问题排查与解决

问题场景

我需要从S3存储桶下载PDF并流式传输到Angular客户端,当前方案是先获取S3下载链接,再用NestJS的StreamableFile包装got.stream返回,但客户端收到的Blob长度和原文件一致,却无法打开,约三分之二的字节存在差异。调试确认got.stream生成的流是正确的,但Swagger返回的文件字节错误。

控制器代码如下:

@Post('download')
@ApiOkResponse({
  schema: {
    type: 'string',
    format: 'binary'
  }
})
@ApiProduces('application/pdf')
async documentDownload(@Body() body: DownloadURLRequest) {
  const result = await this.service.getS3DownloadURL(body);
  const got = require('got');
  return new StreamableFile(got.stream(result.downloadUrl));
}

可能的原因与解决方案

1. 改用AWS SDK直接获取S3流

第三方HTTP库got可能在处理流时隐含了编码转换,导致二进制内容损坏。直接用AWS SDK获取S3对象流更可靠:

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { Readable } from 'stream';

// 初始化S3客户端(建议在模块中注入,避免重复创建)
const s3Client = new S3Client({ region: '你的S3区域' });

async documentDownload(@Body() body: DownloadURLRequest) {
  // 替换为获取S3桶名和文件key的逻辑(替代原预签名URL方式)
  const { bucket, key } = await this.service.getS3BucketAndKey(body);
  const command = new GetObjectCommand({ Bucket: bucket, Key: key });
  const s3Response = await s3Client.send(command);
  
  return new StreamableFile(s3Response.Body as Readable, {
    type: 'application/pdf',
    disposition: 'attachment; filename="target.pdf"'
  });
}

2. 强制got返回原始二进制流

如果必须使用got,需禁用编码确保流为原始二进制:

async documentDownload(@Body() body: DownloadURLRequest) {
  const result = await this.service.getS3DownloadURL(body);
  const got = require('got');
  // 禁用编码,避免自动转换为字符串
  const fileStream = got.stream(result.downloadUrl, { encoding: null });
  
  return new StreamableFile(fileStream, {
    type: 'application/pdf',
    disposition: 'attachment; filename="target.pdf"'
  });
}

3. 手动处理响应流(替代StreamableFile)

如果StreamableFile仍存在解析问题,可以直接将流pipe到Express响应对象:

import { Response } from 'express';

@Post('download')
@ApiOkResponse({
  schema: {
    type: 'string',
    format: 'binary'
  }
})
@ApiProduces('application/pdf')
async documentDownload(@Body() body: DownloadURLRequest, @Res() res: Response) {
  const result = await this.service.getS3DownloadURL(body);
  const got = require('got');
  
  // 设置正确的响应头
  res.setHeader('Content-Type', 'application/pdf');
  res.setHeader('Content-Disposition', 'attachment; filename="target.pdf"');
  
  // 直接将流管道到响应
  got.stream(result.downloadUrl, { encoding: null }).pipe(res);
}

4. Swagger测试的特殊情况

Swagger UI在处理二进制流时存在兼容性问题,建议用Postman或实际Angular客户端测试接口。如果仅Swagger返回的文件损坏,实际客户端请求正常,说明问题出在Swagger的流处理逻辑,无需修改接口代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 17:55:59