# 文件上传相关
# 文件上传思路
# 客户端
- 🍠1、input标签选择文件/拖拽方式选择文件/复制到剪切板获取文件
- 🍿2、File Api获取文件信息
- 🍪3、XMLHttpRequest/fetch 上传
- 🍵4、上传数据 FormaData/Blob等,服务端使用mutipart/form-data
# 服务端
- 🍗1、设置文件存储目录
- 🥘2、是否更改文件名称 (客户端是否传名称)
- 🍙3、上传成功,通知客户端可以访问的url
- 😱4、url的产生,需要启动静态资源服务
# 单文件上传
<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>
<input id="uploadFile" type="file" accept="image/*" />
<!--
type属性file,用户选择文件
accept属性 规定选择文件类型
multiple 是否可以上传多个文件
-->
<button type="button" id="uploadBtn" onClick="startUpload()">
开始上传
</button>
<div class="progress">上传进度:<span id="progressValue">0</span></div>
<div id="uploadResult" class="result"></div>
<script>
const uploadFileEle = document.getElementById("uploadFile");
const progressValueEle = document.getElementById("progressValue");
const uploadResultEle = document.getElementById("uploadResult");
try {
function startUpload() {
if (!uploadFileEle.files.length) return;
//获取文件
const file = uploadFileEle.files[0];
//创建上传数据
const formData = new FormData();
formData.append("file", file);
//上传文件
this.upload(formData);
}
function upload(data) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const result = JSON.parse(xhr.responseText);
uploadResultEle.innerText = xhr.responseText;
}
};
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
progressValueEle.innerText = Math.ceil(event.loaded * 100 / event.total) + "%";
}
}
xhr.open("POST", "http://127.0.0.1:3000/upload", true);
xhr.send(data);
}
} catch (e) {
console.log("error==", e);
}
</script>
</body>
</html>
# 多文件上传
<input id="uploadFile" type="file" multiple accept="image/*" />
# 大文件上传
# 切片
/**
* const DefaultChunkSize = 5 * 1024 * 1024;
* 文件分片
* @param {*} file 文件
* @param {*} chunkSize 分片大小
* @returns
*/
const handleFileChunk = function (file, chunkSize) {
const fileChunkList = [];
// 索引值
let curIndex = 0;
while (curIndex < file.size) {
//最后一个切片以实际结束大小为准。
const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size;
const curFileChunkFile = file.slice(curIndex, endIndex);
curIndex += chunkSize;
fileChunkList.push({ file: curFileChunkFile })
}
return fileChunkList;
};
# 获取全部文件hash
/**
*
* 获取文件内容
* @param {any} file
* @returns
*/
function getFileContent(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
//读取文件内容
fileReader.readAsArrayBuffer(file);
fileReader.onload = (e) => {
//返回读取到的文件内容
resolve(e.target.result);
};
fileReader.onerror = (e) => {
reject(fileReader.error);
fileReader.abort();
};
});
}
/**
* spark-md5.min.js
* 获取全部文件内容hash
* @param {any} fileList
*/
async function getFileHash2(file) {
console.time("filehash")
const spark = new SparkMD5.ArrayBuffer();
//获取全部内容
const content = await getFileContent(file)
try {
spark.append(content);
//生成指纹
const result = spark.end();
console.timeEnd("filehash")
return result;
} catch (e) {
console.log(e);
}
}
# 上传所有切片
/**
*
* 单个文件上传
* @param {any} {
* url,
* method="post",
* data,
* headers={},
* }
* @returns
*/
function singleRequest({
url,
method = "post",
data,
headers = {},
}) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
Object.keys(headers).forEach(key =>
xhr.setRequestHeader(key, headers[key])
);
xhr.send(data);
xhr.onload = e => {
resolve({
data: e.target.response
});
};
});
}
/**
*
* 上传所有的分片
* @param {any} chunks
* @param {any} fileName
*/
async function uploadChunks(chunks, fileName) {
const requestList = chunks
.map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => {
//生成每个切片上传的信息
const formData = new FormData();
formData.append("hash", hash);
formData.append("index", index);
formData.append("fileCount", fileCount);
formData.append("size", size);
formData.append("splitSize", DefaultChunkSize);
formData.append("fileName", fileName);
formData.append("fileHash", fileHash);
formData.append("chunk", chunk);
formData.append("totalSize", totalSize);
return { formData, index };
})
.map(async ({ formData, index }) =>
singleRequest({
url: "http://127.0.0.1:3000/uploadBigFile",
data: formData,
})
);
//全部上传
await Promise.all(requestList);
}
# 全部逻辑
//设置默认切片大小为5M
const DefaultChunkSize = 5 * 1024 * 1024;
const start = async function (bigFile) {
//生成多个切片
const fileList = handleFileChunk(bigFile, DefaultChunkSize);
//获取整个大文件的内容hash,方便实现秒传
// const containerHash = await getFileHash(fileList);
const containerHash = await getFileHash2(bigFile);
//给每个切片添加辅助内容信息
const chunksInfo = fileList.map(({ file }, index) => ({
//整个文件hash
fileHash: containerHash,
//当前是第几个切片
index,
//文件个数
fileCount: fileList.length,
//当前切片的hash
hash: containerHash + "-" + index,
//切片内容
chunk: file,
//文件总体大小
totalSize: bigFile.size,
//单个文件大小
size: file.size
}));
//上传所有文件
uploadChunks(chunksInfo, bigFile.name);
};