文件上传
文件上传分为如下几个步骤
① 创建切片
② 上传切片
③ 全部上传成功后,告诉后端,后端将所有的切片整合成一个文件
首先编写几个函数,用于切片的处理及上传,最后再组合到一起实现完整功能
1. 创建切片
const createFileChunks = function (file, size = 1024*100) {
// 创建数组,存储文件的所有切片
let fileChunks = [];
for (let cur = 0; cur < file.size; cur += size) {
// file.slice 方法用于切割文件,从 cur 字节开始,切割到 cur+size 字节
fileChunks.push(file.slice(cur, cur + size));
}
return fileChunks;
};
createFileChunks 方法接收两个参数
- 要进行切片的文件对象
- 切片大小,这里设置默认值为 1024*100,单位为字节
2. 拼接 formData
* 2、拼接 formData
* 参数1:存储文件切片信息的数组
* 参数2:上传时的文件名称
*/
const concatFormData = function (fileChunks, filename) {
/**
* map 方法会遍历切片数组 fileChunks中的元素map 方法会遍历切片数组 fileChunks中的元素,
* 数组中有多少个切片,创建几个 formData,在其中上传的文件名称、hash值和切片,并将此 formData
* 返回,最终chunksList中存储的就是多个 formData(每个切片对应一个 formData)
*
*/
const chunksList = fileChunks.map((chunk, index) => {
let formData = new FormData();
// 这个'filename' 字符串的名字要与后端约定好
formData.append("filename", filename);
// 作为区分每个切片的编号,后端会以此作为切片的文件名称,此名称也应该与后端约定好
formData.append("hash", index);
// 后端会以此作为切片文件的内容
formData.append("chunk", chunk);
return {
formData,
};
});
return chunksList;
};
3. 上传切片
遍历上面的 chunksList 数组,调用 axios 对每个 formData 信息进行提交
const uploadChunks=async (chunksList)=>{
const uploadList = chunksList.map(({ formData }) =>
axios({
method: "post",
url: "/upload",
data: formData,
})
);
await Promise.all(uploadList);
}
4. 合并切片
当所有切片都已经上传成功后,告诉后端一声
const mergeFileChunks = async function (filename) {
await axios({
method: "get",
url: "/merge",
params: {
filename,
},
});
};
5. 方法组合
async function handleFileUpload(event) {
event.preventDefault();
const file = window.file;
if (!file) return;
// 1、切片切割,第二个参数采用默认值
const fileChunks = createFileChunks(file);
// 2、将切片信息拼接成 formData 对象
const chunksList = concatFormData(fileChunks, file.name);
// 3、上传切片
await uploadChunks(chunksList);
// 4、所有切片上传成功后后,再告诉后端所有切片都已完成
await mergeFileChunks(file.name);
console.log("上传完成");
}
6. 完整代码
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>大文件上传</title>
</head>
<body>
<div id="app">
<form action="">
<input type="file" name="" id="uploadInput" />
<button id="uploadBtn">上传</button>
</form>
</div>
</body>
</html>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.defaults.baseURL = `http://localhost:3000`;
let file = null;
// 文件被更改
function handleFileChange(event) {
const file = event.target.files[0];
if (!file) return;
window.file = file;
}
// 1、创建切片
const createFileChunks = (file, size = 1024 * 100) => {
// 创建数组,存储文件的所有切片
let fileChunks = [];
for (let cur = 0; cur < file.size; cur += size) {
// file.slice 方法用于切割文件,从 cur 字节开始,切割到 cur+size 字节
fileChunks.push(file.slice(cur, cur + size));
}
return fileChunks;
};
/**
* 2、拼接 formData
* 参数1:存储文件切片信息的数组
* 参数2:上传时的文件名称
*/
const concatFormData = function (fileChunks, filename) {
/**
* map 方法会遍历切片数组 fileChunks中的元素map 方法会遍历切片数组 fileChunks中的元素,
* 数组中有多少个切片,创建几个 formData,在其中上传的文件名称、hash值和切片,并将此 formData
* 返回,最终chunksList中存储的就是多个 formData(每个切片对应一个 formData)
*
*/
const chunksList = fileChunks.map((chunk, index) => {
let formData = new FormData();
// 这个'filename' 字符串的名字要与后端约定好
formData.append("filename", filename);
// 作为区分每个切片的编号,后端会以此作为切片的文件名称,此名称也应该与后端约定好
formData.append("hash", index);
// 后端会以此作为切片文件的内容
formData.append("chunk", chunk);
return {
formData,
};
});
return chunksList;
};
// 3、上传切片
const uploadChunks = async (chunksList) => {
const uploadList = chunksList.map(({ formData }) =>
axios({
method: "post",
url: "/upload",
data: formData,
})
);
await Promise.all(uploadList);
};
// 大文件上传
async function handleFileUpload(event) {
event.preventDefault();
const file = window.file;
if (!file) return;
// 1、切片切割,第二个参数采用默认值
const fileChunks = createFileChunks(file);
// 2、将切片信息拼接成 formData 对象
const chunksList = concatFormData(fileChunks, file.name);
// 3、上传切片
await uploadChunks(chunksList);
// 4、所有切片上传成功后后,再告诉后端所有切片都已完成
await mergeFileChunks(file.name);
console.log("上传完成");
}
// 合并切片
const mergeFileChunks = async function (filename) {
await axios({
method: "get",
url: "/merge",
params: {
filename,
},
});
};
document
.getElementById("uploadInput")
.addEventListener("change", handleFileChange);
document
.getElementById("uploadBtn")
.addEventListener("click", handleFileUpload);
</script>
vue 改造
<div>
<form action="">
<input type="file" @change="handleFileChange($event)" />
<button @click.prevent="handleFileUpload()">上传</button>
</form>
</div>
</template>
<script>
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000";
export default {
data() {
return {
file: null,
};
},
methods: {
handleFileChange(event) {
const file = event.target.files[0];
if (!file) return;
this.file = file;
},
// 1、创建切片
createFileChunks(size = 1024 * 100) {
// 创建数组,存储文件的所有切片
let fileChunks = [];
for (let cur = 0; cur < this.file.size; cur += size) {
// file.slice 方法用于切割文件,从 cur 字节开始,切割到 cur+size 字节
fileChunks.push(this.file.slice(cur, cur + size));
}
return fileChunks;
},
/**
* 2、拼接 formData
* 参数1:存储文件切片信息的数组
* 参数2:上传时的文件名称
*/
concatFormData(fileChunks, filename) {
/**
* map 方法会遍历切片数组 fileChunks中的元素map 方法会遍历切片数组 fileChunks中的元素,
* 数组中有多少个切片,创建几个 formData,在其中上传的文件名称、hash值和切片,并将此 formData
* 返回,最终chunksList中存储的就是多个 formData(每个切片对应一个 formData)
*
*/
const chunksList = fileChunks.map((chunk, index) => {
let formData = new FormData();
// 这个'filename' 字符串的名字要与后端约定好
formData.append("filename", filename);
// 作为区分每个切片的编号,后端会以此作为切片的文件名称,此名称也应该与后端约定好
formData.append("hash", index);
// 后端会以此作为切片文件的内容
formData.append("chunk", chunk);
return {
formData,
};
});
return chunksList;
},
// 3、上传切片
async uploadChunks(chunksList) {
const uploadList = chunksList.map(({ formData }) =>
axios({
method: "post",
url: "/upload",
data: formData,
})
);
await Promise.all(uploadList);
},
// 大文件上传
async handleFileUpload() {
console.log(1);
const file = this.file;
if (!file) return;
// 1、切片切割,第二个参数采用默认值
const fileChunks = this.createFileChunks();
// 2、将切片信息拼接成 formData 对象
const chunksList = this.concatFormData(fileChunks, this.file.name);
// 3、上传切片
await this.uploadChunks(chunksList);
// 4、所有切片上传成功后后,再告诉后端所有切片都已完成
await this.mergeFileChunks(this.file.name);
console.log("上传完成");
},
// 合并切片
async mergeFileChunks(filename) {
await axios({
method: "get",
url: "/merge",
params: {
filename,
},
});
},
},
};
</script>