如何让NestJS无需等待下载即可实时返回SharePoint文件?
当然可以实现无需等待全量下载就实时返回文件给客户端,核心思路是用**流式传输(Stream)**替代原来的Buffer方式,直接把SharePoint返回的文件流转发给客户端,这样客户端能立刻开始接收数据,不用等大文件在服务端下载完成。
问题分析
你当前的代码用result.buffer()会把整个文件加载到服务端内存中,不仅导致客户端等待时间过长,还可能因为大文件(比如700MB)引发内存溢出问题。流式传输则是边接收SharePoint的字节数据,边转发给客户端,内存占用极低。
修改后的实现代码
1. 调整SharePoint服务层代码
修改原有方法,新增返回文件流的逻辑:
private async sharepointApi(url: string, isFileDownload = false): Promise<Response> { const authHeaders = await this.getAuthHeaders(); if (!authHeaders) { console.error('认证失败'); throw new Error('无法连接到SharePoint'); } const headers: Record<string, string> = { ...authHeaders, }; // 下载文件时不要设置JSON格式的Accept头,否则会拿到错误的响应 if (!isFileDownload) { headers['Accept'] = 'application/json;odata=verbose'; } return fetch(`${this.site.url}/_api/${url}`, { headers }); } private async sharepointStreamApi(url: string): Promise<ReadableStream<Uint8Array>> { const result = await this.sharepointApi(url, true); if (!result.ok) { throw new Error(`SharePoint请求失败: ${result.status} ${result.statusText}`); } // 返回原始响应流 return result.body; } async getFileStream(fileRelativeUrl = ''): Promise<ReadableStream<Uint8Array>> { fileRelativeUrl = this.transformServerRelativeUrl(fileRelativeUrl); return this.sharepointStreamApi( `web/GetFileByServerRelativeUrl('${encodeURIComponent(fileRelativeUrl)}')/$value` ); }
2. 控制器端点处理流式返回
在控制器中直接将SharePoint的流管道到客户端响应:
import { Controller, Get, Param, Res, HttpException, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; import { SharepointService } from './sharepoint.service'; @Controller('sharepoint-files') export class SharepointFileController { constructor(private readonly sharepointService: SharepointService) {} @Get(':filePath') async downloadFile(@Param('filePath') filePath: string, @Res() res: Response) { try { const fileStream = await this.sharepointService.getFileStream(filePath); // 从SharePoint响应头获取真实的Content-Type(可选,也可以手动指定) // const contentType = result.headers.get('Content-Type') || 'application/octet-stream'; const contentType = 'application/octet-stream'; const fileName = filePath.split('/').pop() || 'downloaded-file'; // 设置响应头,触发浏览器下载 res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`); // 将流直接转发给客户端 fileStream.pipe(res); } catch (error) { throw new HttpException(`文件下载失败: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR); } } }
关键注意点
- 请求头调整:下载文件必须去掉
Accept: application/json,否则SharePoint会返回JSON格式的错误信息,而非文件流。 - 错误处理:要捕获SharePoint请求失败的情况(比如文件不存在、权限不足),及时返回客户端友好的错误响应。
- 响应头设置:正确的
Content-Type和Content-Disposition能让浏览器正确识别文件类型并触发下载行为。 - 内存优化:流式传输不会将整个文件加载到内存,避免了大文件场景下的内存溢出风险。
内容的提问来源于stack exchange,提问作者Alexandre BSF
相关产品推荐
相关产品推荐

