Skip to content

Nest 返回文件流

  • 有的时候文件太大了,不能直接返回给前端,需要流式返回,这个时候就需要用到流式下载了

基础版本

  • 不管文件多大,都先读取出来,然后内存一起返回
ts
  // 增加流式下载
  @Get('download')
  @Header('Content-Disposition', `attachment; filename="fangda.txt"`)
  download(@Res() res: Response) {
    // 获取到路径
    const dir = path.join(__dirname, '../../../', 'public/fangda.txt');
    // 读取文件
    const content = fs.readFileSync(dir, 'utf-8');
    // 设置响应头部 要是用了@Header 这里面就不用了
    //res.set('Content-Disposition', `attachment; filename="fangda.txt"`);
    res.send(content);
  }

流式版本

读取一部分 返回一部分.随用随读

downland.service.ts

ts
import { Injectable } from "@nestjs/common";

// 文件下载
import * as path from "path";
import * as fs from "fs";
import { Transform } from "stream";
@Injectable()
export class DownlandService {
  //  大文件分流下载案例
  async getFileStream(filename: string) {
    // 获取到路径
    const dir = path.join(__dirname, "../../../public", filename);
    console.log(dir);
    // 获取文件实例
    const fileStat = fs.statSync(dir);
    // 获取实例大小
    const fileSize = fileStat.size;
    // 获取文件位置
    let start = 0;
    let end = fileSize - 1;
    // 分片开始,分了10片
    const chunksize = (end - start) / 10;
    const stream = fs.createReadStream(dir, { start, end });
    return {
      stream,
      start,
      end,
      chunksize,
      fileSize,
      createProgressStream: this.createProgressStream,
    };
  }

  // 进度控制
  // 进度控制
  createProgressStream(totalSize: number) {
    let downloaded = 0;
    return new Transform({
      transform(chunk, encoding, callback) {
        downloaded += chunk.length;
        const progress = ((downloaded / totalSize) * 100).toFixed(2);
        console.log(`下载进度: ${progress}%`);
        callback(null, chunk);
      },
    });
  }
}

要使用下载的控制器

  • xxx.controller.ts
ts
import { Controller, Get, Param, Res } from "@nestjs/common";
import { DownlandService } from "./downland.service";

import type { Response } from "express";

// 文件流
import { pipeline } from "stream";
import { promisify } from "util";

@Controller("user")
export class UserController {
  constructor(private readonly downlandService: DownlandService) {}

  // 增加流式下载
  @Get("download/:filename")
  async downloadFile(
    @Param("filename") filename: string,
    @Res() res: Response
  ) {
    const pipelineAsync = promisify(pipeline); // 将stream的pipeline方法转换为promise

    const { stream, start, end, chunksize, fileSize, createProgressStream } =
      await this.downlandService.getFileStream(filename);

    const headers = {
      "Accept-Ranges": "bytes",
      "Content-Length": chunksize,
      "Content-Type": "application/octet-stream",
    };
    res.writeHead(200, headers);
    // 处理进度
    const progressStream = createProgressStream(fileSize);
    try {
      await pipelineAsync(stream, progressStream, res);
      console.log("文件传输完成");
    } catch (error) {
      console.error("传输失败:", error);
      throw error;
    }
  }
}