大文件上传
后端实现
-
上传文件路由
texttestRouter.post("/up", upload.any(), async (ctx, next) => { await sleep(Math.random() * 10000 + 2000); // 保存文件 fs.writeFileSync( `./rebase/${ctx.request.files[0].fieldname}`, ctx.request.files[0].buffer ); ctx.body = "/abc success"; }); -
合并文件路由
- appendFileSync:用于将给定数据同步附加到文件。如果不存在,将创建一个新文件。
texttestRouter.post("/merge", async (ctx, next) => { const { name } = ctx.request.body; // 1. 获取所有分片 const chunks = fs.readdirSync("./rebase"); // 2. 拼接文件名字 const currTime = new Date(); const fileName = `${currTime.getFullYear()}_${ currTime.getMonth() + 1 }_${currTime.getDate()}_${Date.now().toString().slice(-5)}_${Math.ceil( Math.random() * 100000 )}`; // 3. 便利文件通过appendFile整合文件 for (let k of chunks) { fs.appendFileSync( `./upload/${fileName}.${name.split(".")[1]}`, fs.readFileSync(`./rebase/${k}`) ); // 4. 删除该文件 fs.unlinkSync(`./rebase/${k}`); } ctx.body = "/merge success"; });
前端实现
-
布局
text<input type="file" id="btn" @change="ced" /> <span id="upload" @click="send">上传</span> -
实现ced和send
-
FileReader:是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。
它使用事件来传递数据,因为从磁盘读取数据可能比较费时间。
- readAsArrayBuffer(blob) —— 将数据读取为二进制格式的 ArrayBuffer。
- onload读取完成事件回调
-
SparkMD5:用于校验字符串或者文件,以防止文件、字符串被“篡改”。因为如果文件、散列值不一样,说明文件内容也是不一样的
textconst ced = (e) => { // 1.拿到上传的文件 const file = e.target.files[0]; console.log(file); fileInfo = file; }; const send = () => { // 2.前端校验 const { size, type } = fileInfo; // if (size > 20 * 1024 * 1024) { // alert("文件过大"); // btn.value = ""; // return; // } // if (type.split("/")[0] !== "image") { // alert("文件类型错误"); // btn.value = ""; // } // 3.获取文件buffer const fileRead = new FileReader(); fileRead.readAsArrayBuffer(fileInfo); // 4.监听转换完成 fileRead.onload = (res) => { // 5.转换完成获取文件buffer const buf = res.target.result; // 6.通过文件内容获取唯一hash const sp5 = new SparkMD5.ArrayBuffer(); sp5.append(buf); const hash = sp5.end(); // 7.分割大文件 5m为一片,buf数组保存到partList const partSize = 5 * 1024 * 1024; const pageNum = Math.ceil(fileInfo.size / partSize); let current = 0; const partList = []; for (let i = 0; i < pageNum; i++) { let reqItem = { chunk: fileInfo.slice(current, current + partSize), filename: `${hash}_${i}`, }; current += partSize; partList.push(reqItem); } // 8.创建切片请求 let count = 0; for (let item of partList) { // 9.构建formdata const formData = new FormData(); formData.append(item.filename, item.chunk); // 10.并行发送上传请求 axios.post("http://localhost:1000/test/up", formData).then((res) => { count++; // 所有片段上传成功,合并服务器碎片为完整文件 if (count === pageNum) { axios .post("http://localhost:1000/test/merge", { name: "a.mp4" }) .then((res) => { console.log(res); }); } console.log(res); }); } }; }; -