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
相关产品推荐
相关产品推荐

