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

如何让NestJS无需等待下载即可实时返回SharePoint文件?

如何在NestJS中实现从SharePoint API流式传输文件到客户端

当然可以实现无需等待全量下载就实时返回文件给客户端,核心思路是用**流式传输(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 04:18:24