场景再现:那个让服务器瑟瑟发抖的下午
记得那天,产品经理笑眯眯地走过来:"咱们加个功能,用户要上传高清企业宣传片,也就10个G左右..."
我当时的表情:(⊙_⊙)
服务器当时的表情:Σ(°△°|||)︴
但作为一名优秀的Nest.js开发者,我露出了神秘的微笑:"没问题,咱们来'分尸'...啊不,分片处理!"
为什么要分片?因为服务器也不是超级计算机啊!
想象一下,你要把一头大象塞进冰箱...等等,这个比喻太老套了。换个说法:你要把《指环王》三部曲加长版一次性通过门缝塞进房间,门会哭的!
同理,大文件直接上传会导致:
- 服务器内存爆炸(想象一下气球吹到极限)
- 网络连接像初恋一样脆弱(说断就断)
- 用户体验堪比看缓冲中的视频------卡到怀疑人生
Nest.js分片上传:像吃披萨一样优雅
第一步:把大文件切成"披萨片"
ts
// 当大文件来袭,我们的态度是:切它!
@Post('upload-chunk')
async uploadChunk(@UploadedFile() file, @Body() body) {
const { chunkNumber, totalChunks, filename } = body;
// 每个chunk就像一块披萨,单独处理不会噎着
return this.uploadService.saveChunk({
chunkNumber,
totalChunks,
filename,
data: file.buffer
});
}
第二步:给披萨片编号,免得拼回去时变成抽象画
ts
// 想象一下拼图时找不到最后一块的绝望...所以我们这样:
async saveChunk(chunkData: ChunkData) {
// 给每个chunk一个VIP座位号
const chunkPath = `/tmp/${chunkData.filename}.part${chunkData.chunkNumber}`;
// 万一服务器突然想重启,我们也不怕!
await this.ensureDirExists(path.dirname(chunkPath));
// 把chunk存起来,像收藏邮票一样认真
await fs.promises.writeFile(chunkPath, chunkData.data);
}
第三步:当所有披萨片到齐,开始组装!
ts
@Post('merge-chunks')
async mergeChunks(@Body() body) {
const { filename, totalChunks } = body;
// 就像拼乐高,但要小心别踩到碎片
const mergePath = `/uploads/${filename}`;
const writeStream = fs.createWriteStream(mergePath);
for (let i = 1; i <= totalChunks; i++) {
// 按顺序读取每一片,防止视频变成抽象艺术
const chunkPath = `/tmp/${filename}.part${i}`;
const chunkBuffer = await fs.promises.readFile(chunkPath);
writeStream.write(chunkBuffer);
// 清理现场,我们是有洁癖的程序员
await fs.promises.unlink(chunkPath);
}
writeStream.end();
return { message: '文件拼装完成,堪比变形金刚合体!' };
}
分片上传的超级福利
1. 断点续传:网络断了?喝杯咖啡继续!
ts
// 检查哪些chunk已经上传了,就像看书夹书签
@Get('upload-progress/:filename')
async getProgress(@Param('filename') filename: string) {
// 扫描已经到位的chunk
const uploadedChunks = await this.findExistingChunks(filename);
return { uploadedChunks, message: '欢迎回来,我们等你很久了!' };
}
2. 并行上传:多条车道就是比单行道快!
ts
// 同时上传多个chunk,就像超市开多个收银台
async uploadMultipleChunks(chunks: ChunkData[]) {
// Promise.all就是我们的多线程魔法
return Promise.all(chunks.map(chunk => this.uploadChunk(chunk)));
}
3. 进度条:给用户一种"一切尽在掌握"的错觉
ts
// 进度条是程序员给用户的心理按摩
calculateProgress(uploadedChunks: number, totalChunks: number) {
const progress = (uploadedChunks / totalChunks) * 100;
return {
progress: Math.round(progress),
message: this.generateEncouragingMessage(progress)
};
}
private generateEncouragingMessage(progress: number) {
if (progress < 50) return "正在努力搬运数据...";
if (progress < 80) return "过半了,坚持就是胜利!";
if (progress < 100) return "快好了,准备放鞭炮!";
return "上传完成,服务器表示情绪稳定!";
}
真实案例:那个10GB视频的结局
当产品经理再次过来询问进度时,我优雅地展示了分片上传的成果:
- ✅ 文件被切成1024KB的小chunk
- ✅ 支持暂停续传(用户可以去上个厕所)
- ✅ 实时进度条(缓解用户的焦虑)
- ✅ 服务器内存保持稳定(没有爆炸)
产品经理:"太棒了!那用户说下次想上传20GB的..."
我:"...(╯°□°)╯︵ ┻━┻"
总结:分片上传,真香!
就像不能一口吃掉整个汉堡(虽然有些人尝试过),服务器也不能一次性处理超大文件。分片上传让我们的应用:
- 更健壮:不怕网络抽风
- 更友好:给用户进度反馈
- 更高效:并行处理加速上传
所以下次遇到大文件,别硬刚,学会"分而治之"------这是从秦始皇到Nest.js开发者都懂的智慧!
温馨提示:本文代码经过简化,实际使用记得加错误处理,否则文件上传失败时,错误信息可能会比你的头发还乱!