HTML
2024年10月17日
一、认识
二、细节
2.1 获取文件
获取文件
function getFile(e) {
const {
target: { files },
} = e;
file = files[0];
}
2.2 获取文件的 MD5
获取文件的 MD5
fileMD5.js
import SparkMD5 from "spark-md5";
export function calcFileMD5(file, chunkSize) {
return new Promise((resolve, reject) => {
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.onload = (e) => {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
resolve(spark.end());
}
};
fileReader.onerror = (e) => {
reject(fileReader.error);
fileReader.abort();
};
function loadNext() {
const start = currentChunk * chunkSize;
const end =
start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(file.slice(start, end));
}
loadNext();
});
}
2.3 检测服务端是否含有文件名、文件 MD5 列表
检测服务端是否含有文件名、文件 MD5 列表
async function checkFileExist() {
const { name, type } = file;
const { data } = await get("http://localhost:4000/upload/exists", {
name,
type: type.split("/")[0],
md5: fileMD5,
});
return data;
}
2.4 如果服务端不存在该文件名,开始文件分片
如果服务端不存在该文件名,开始文件分片
function uploadChunks() {
const { size } = file;
const chunks = Math.ceil(size / chunkSize);
return asyncPool(poolLimit, [...new Array(chunks).keys()], (i) => {
if (chunkFinishList.indexOf(i + "") !== -1) {
return Promise.resolve();
}
const start = i * chunkSize;
const end = i + 1 === chunks ? size : (i + 1) * chunkSize;
const chunk = file.slice(start, end);
return uploadChunk({
chunk,
chunkIndex: i,
});
});
}
2.5 检测文件片段在服务端是否存在,如果不存在,并发请求
检测文件片段在服务端是否存在,如果不存在,并发请求
asyncPool.js
export default async function (poolLimit, array, iteratorFn) {
const taskList = [];
const currentTaskList = [];
for (const item of array) {
const promise = Promise.resolve().then(() => iteratorFn(item, array));
taskList.push(promise);
if (poolLimit <= array.length) {
const currentPromise = promise.then(() =>
currentTaskList.splice(currentTaskList.indexOf(currentPromise), 1)
);
currentTaskList.push(currentPromise);
if (currentTaskList.length >= poolLimit) {
await Promise.race(currentTaskList);
}
}
}
return Promise.all(taskList);
}
2.6 所有片段发送成功,服务端开始合并片段
所有片段发送成功,服务端开始合并片段
function concatChunks() {
const { name, type } = file;
return get("http://localhost:4000/upload/concatFragment", {
name,
type: type.split("/")[0],
md5: fileMD5,
});
}
三、实现
入口文件 index.js
import asyncPool from "./asyncPool";
import { calcFileMD5 } from "./fileMD5.js";
import { get, post, upload } from "./request.js";
let file;
let fileMD5;
const poolLimit = 2;
const chunkSize = 2 * 1024 * 1024;
let chunkFinishList = [];
start();
function start() {
const input = document.getElementById("file");
const btn = document.getElementById("btn");
input.addEventListener("change", getFile);
btn.addEventListener("click", uploadFile);
}
function getFile(e) {
const {
target: { files },
} = e;
file = files[0];
}
async function uploadFile() {
fileMD5 = await calcFileMD5(file, chunkSize);
const result = await checkFileExist();
if (result.isExists) {
console.log("该文件已上传成功!");
return;
}
chunkFinishList = result.chunkFinishList;
await uploadChunks();
await concatChunks();
}
async function checkFileExist() {
const { name, type } = file;
const { data } = await get("http://localhost:4000/upload/exists", {
name,
type: type.split("/")[0],
md5: fileMD5,
});
return data;
}
function uploadChunks() {
const { size } = file;
const chunks = Math.ceil(size / chunkSize);
return asyncPool(poolLimit, [...new Array(chunks).keys()], (i) => {
if (chunkFinishList.indexOf(i + "") !== -1) {
return Promise.resolve();
}
const start = i * chunkSize;
const end = i + 1 === chunks ? size : (i + 1) * chunkSize;
const chunk = file.slice(start, end);
return uploadChunk({
chunk,
chunkIndex: i,
});
});
}
function uploadChunk({ chunk, chunkIndex }) {
const { name } = file;
const formData = new FormData();
formData.set("file", chunk, fileMD5 + "-" + chunkIndex);
formData.set("fileName", name);
formData.set("timestamp", Date.now());
return upload("http://localhost:4000/upload/fragmentUpload", formData);
}
function concatChunks() {
const { name, type } = file;
return get("http://localhost:4000/upload/concatFragment", {
name,
type: type.split("/")[0],
md5: fileMD5,
});
}
请求方法 ajax.js
export function get(url, params) {
return new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
const newUrl = Object.keys(params).reduce((prev, curr) => {
return `${prev}${curr}=${params[curr]}&`;
}, `${url}?`);
ajax.open("get", newUrl.slice(0, -1), true);
ajax.onreadystatechange = () => {
if (ajax.readyState === 4 && [200, 304].includes(ajax.status)) {
const { response } = ajax;
resolve(JSON.parse(response));
}
};
ajax.send();
});
}
export function post(url, data) {
const ajax = new XMLHttpRequest();
}
export function upload(url, formData) {
return new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
ajax.open("post", url, true);
ajax.onreadystatechange = () => {
if (ajax.readyState === 4 && [200, 304].includes(ajax.status)) {
const { response } = ajax;
resolve(JSON.parse(response));
}
};
ajax.send(formData);
});
}
fileMD5.js
import SparkMD5 from "spark-md5";
export function calcFileMD5(file, chunkSize) {
return new Promise((resolve, reject) => {
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.onload = (e) => {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
resolve(spark.end());
}
};
fileReader.onerror = (e) => {
reject(fileReader.error);
fileReader.abort();
};
function loadNext() {
const start = currentChunk * chunkSize;
const end =
start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(file.slice(start, end));
}
loadNext();
});
}
asyncPool.js
export default async function (poolLimit, array, iteratorFn) {
const taskList = [];
const currentTaskList = [];
for (const item of array) {
const promise = Promise.resolve().then(() => iteratorFn(item, array));
taskList.push(promise);
if (poolLimit <= array.length) {
const currentPromise = promise.then(() =>
currentTaskList.splice(currentTaskList.indexOf(currentPromise), 1)
);
currentTaskList.push(currentPromise);
if (currentTaskList.length >= poolLimit) {
await Promise.race(currentTaskList);
}
}
}
return Promise.all(taskList);
}