Nest 大文件上传尝试

大家圣诞节快乐呀,我是晨光,上周五看到 @神说要有光 光哥发的文章,原文见,https://articles.zsxq.com/id_4iqlbn0nqofl.html


出于好奇,跟着光哥的文章敲了一遍,自己遇到了一些困惑,并尝试解决,过程还是蛮有趣的,分享给大家


1、分片太多,合并时文件无法还原


我随手截图一张,大概有 7.2 M,上传后合并的图片,发现只有很小的一部分



我限制分片 20 次,合并后的图片如下,只有头部的一小部分,不清楚问题出在哪里,需要去定位问题。



做实验,发现 size 比较小的图片,分片的次数少,合并后没有问题,所以我尝试调整分片的次数,比如,调整为 10 次,测试后发现,合并后的图片正常了!!



2、怎么保证分片的次数是 小于等于 10 的呢?


之前分享的分段请求里面有提到过,不是无限制的分段,需设置一个边界值,也就是不让它分片的次数太多,需要在某个范围内,那应用到这里,最大分片次数为 10


根据上传的文件的大小,除以分片的 size,和 10 来比较,根据比较的结果,得到最终真实的分片的 size。



附上代码

const fileInput = document.querySelector('#fileInput');

const chunkSize = 20 * 1024; // 默认单次分片 size
const maxChunk = 10; // 最大分片次数

fileInput.onchange = async function () {
const file = fileInput.files[0];
console.log(file);

const chunks = [];

let startPos = 0;

// 最终实际的分片 size
const relChunkSize =
Math.ceil(file.size / chunkSize) > maxChunk
? Math.ceil(file.size / maxChunk)
: chunkSize;

while (startPos < file.size) {
chunks.push(file.slice(startPos, startPos + relChunkSize));
startPos += relChunkSize;
}
// 其他代码省略
}


这样做有个好处,我们无法控制上传的图片到底有多大,但是能够保证最大的分片次数是 10,那最终合并的文件就不会有问题。



3、为什么要保证分片次数 <=10 ?


查了很多资料,也有很多猜想

  1. 浏览器并发限制,但是请求都在本地,而且都发送成功,所以应该不是这个原因
  2. Node.js 中,EventEmitter 是一个用于处理事件的基类。当一个 EventEmitter 实例被创建时,它会默认添加一个最大监听器数量为 10 的限制;这个原因也不太像
  3. 分片顺序不正确:如果分片上传的顺序不正确,合并后的文件可能会损坏。确保在合并分片之前按照正确的顺序进行合并。感觉有点像这个


说干就干, debug 了一下代码,发现读取到的文件顺序,1 后面竟然是 10,照这样合并确实会有问题。

解决办法:尝试给文件排个序,从而保证分片读取的顺序正常。



按照上面的文件名格式,我们写个排序方法把文件的顺序给调过来


files.sort((a, b) => {
const aLastDigit = parseInt(a.split('-')[1]); // 获取 a 的末尾数字
const bLastDigit = parseInt(b.split('-')[1]); // 获取 b 的末尾数字

return aLastDigit - bLastDigit; // 按末尾数字从小到大排序
});


我们重新上传一次,分片还是 20 ,有了排序方法,文件的读取顺序正常了,



最终合并出来的图片也正常了!!



印证了之前的猜想,确实是因为文件读取顺序导致合并异常的,再修最大批次数为 30,发现依旧可以合并正常。


4、优化文件名排序


基于上面的代码,我们给上传的文件重命名,多加个短横线,



发现上传后合并又出现了之前的问题,合并的图片又只能展示出一小部分了,推测可能是排序出了问题,排查发现,现在的文件名 932769_test2-name.png-8 有多个短横线


我们优化一下排序方法,不管名称中有多少个短横线,都只取最后一个值来进行排序


files.sort((a, b) => {
const arrA = a.split('-');
const arrB = b.split('-');
const aLastDigit = parseInt(arrA[arrA.length - 1]); // 获取 a 的末尾数字
const bLastDigit = parseInt(arrB[arrB.length - 1]); // 获取 b 的末尾数字

return aLastDigit - bLastDigit; // 按末尾数字从小到大排序
});


修改后,重新上传,合并后的文件也能正常展示了。至此,我的探索结束



5、总结


  1. 遇到问题不要退缩,大胆假设并验证
  2. 限制最大批次为 10 的操作看起来有点多余,不是必须的,只能算是锦上添花啦
  3. 分片合并的时候,文件读取顺序很重要
  4. 为了确保文件读取顺序,需要进行排序,因此在文件分片上传时就需要有标识位能够确保文件的顺序


以上,全文完,Nest 还挺有意思的,以后多研究研究,大家有收获点个赞呀~

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP