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;
}
}
}