input进行文件上传时有关的属性
属性 | 说明 |
---|---|
accept | 指定上传文件的类型,例如:accept=”image/*”,只能上传图片文件 |
multiple | 允许上传多个文件 |
webkitdirectory | 是否上传文件夹 |
单文件上传
后端:
var express = require('express');
var router = express.Router();
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
/* GET home page. */
router.post('/upload',upload.single("image"),function(req, res, next) {
res.send({ok:1})
});
module.exports = router;
- form
<form method="post" action="http://localhost:3000/upload" enctype="multipart/form-data">
<input type="file" name="image"/><br />
<input type="submit" value="上传"/><br />
</form>
说明:form表单enctype属性的值必需为multipart/form-data,name=”image”与upload.single(“image”)相对应
- fetch
<input type="file" id="uploadFile">
<button onclick="upload()">upload</button>
<script>
const uploadFile = document.querySelector("#uploadFile")
function upload(){
const file = uploadFile.files[0]
const formData = new FormData()
formData.set("image", file)
fetch("http://localhost:3000/upload",{
method: "POST",
body: formData
}).then(res=>res.json()).then(res=>{
console.log(res)
})
}
</script>
说明:这里body的值需要利用FormData,其中formData.set(“image”, file)的image对应于upload.single(“image”)
多文件上传
后端:
var express = require('express');
var router = express.Router();
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
/* GET home page. */
router.post('/upload',upload.array("image",5),function(req, res, next) {
res.send({ok:1})
});
module.exports = router;
说明:upload.array(“image”,5)第二个参数指定了最多上传的图片数量
- form
<form method="post" action="http://localhost:3000/upload" enctype="multipart/form-data">
<input id="uploadFile" type="file" name="image" onchange="upload()" multiple/><br />
<input type="submit" value="上传"/><br />
</form>
<script>
const uploadFile = document.querySelector("#uploadFile")
function upload(){
if(uploadFile.files.length>5){
// 清空文件选择的输入框
uploadFile.value = ''
alert("最多上传5张图片")
}
}
</script>
- fetch
<input type="file" id="uploadFile" multiple>
<button onclick="upload()">upload</button>
<script>
const uploadFile = document.querySelector("#uploadFile")
function upload() {
const formData = new FormData()
if (uploadFile.files.length > 5) {
alert("最多上传5张图片")
} else {
for (let item of uploadFile.files) {
// 将文件数据存入表单
formData.append("image", item)
}
// 上传表单
fetch("http://localhost:3000/upload", {
method: "POST",
body: formData
}).then(res => res.json()).then(res => {
console.log(res)
})
}
}
</script>
上传文件夹
给input标签加个webkitdirectory即可
<input type="file" id="uploadFile" webkitdirectory>
<button onclick="upload()">upload</button>
<script>
const uploadFile = document.querySelector("#uploadFile")
function upload() {
const formData = new FormData()
if (uploadFile.files.length > 5) {
alert("最多上传5张图片")
} else {
for (let item of uploadFile.files) {
// 将文件数据存入表单
formData.append("image", item)
}
// 上传表单
fetch("http://localhost:3000/upload", {
method: "POST",
body: formData
}).then(res => res.json()).then(res => {
console.log(res)
})
}
}
</script>
拖拽上传
const dragArea = document.getElementsByClassName('dragArea')[0]
// 当拖拽文件进入时触发
dragArea.ondragenter = (e) => {
// 阻止默认行为
e.preventDefault()
}
// 当拖拽文件停留在此区域不松手会不断触发
dragArea.ondragover = (e) => {
e.preventDefault()
}
// 当拖拽文件松手时触发
dragArea.ondrop = async (e) => {
// 创建form表单进行数据存储
const formData = new FormData();
// 处理文件
const files = e.dataTransfer.files;
for (let file of files) {
if(file.type){
formData.append("file", file);
}
}
// 处理文件夹
const items = e.dataTransfer.items;
for (let item of items) {
const entry = item.webkitGetAsEntry();
if (entry.isDirectory) {
// 等待
await directoryDec(entry, formData);
}
}
// 一次性上传所有数据
fetch("http://localhost:3000/upload", {
method: "POST",
body: formData
}).then(res => res.json()).then(res => {
console.log(res)
})
e.preventDefault()
}
// 处理文件夹函数
function directoryDec(entry, formData) {
return new Promise((resolve, reject) => {
const reader = entry.createReader();
reader.readEntries(async (entries) => {
// 使用 Promise.all 确保所有递归调用完成
await Promise.all(entries.map(async (itemEntry) => {
if (itemEntry.isDirectory) {
// 等待处理子目录完成
await directoryDec(itemEntry, formData);
} else {
await new Promise(resolve1 => {
itemEntry.file(file => {
formData.append("file", file);
resolve1();
});
})
}
}));
resolve();
})
});
}
大文件分片上传
- 前端分片,后端进行文件重组
- 后端校验文件完整性
- 前端可根据自己的cpu利用worker开启多个进程,提高效率
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"
integrity="sha512-iWbxiCA4l1WTD0rRctt/BfDEmDC5PiVqFc6c1Rhj/GKjuj6tqrjrikTw3Sypm/eEgMa7jSOS9ydmDlOtxJKlSQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<input type="file" id="upload">
<button onclick="upload()">上传</button>
<script>
const uploadInput = document.querySelector("#upload")
async function upload() {
// 定义分片大小
const chunkSize = 1024 * 1024 * 3
// 文件大小
const fileSize = uploadInput.files[0].size
// 记录文件完整的MD5值
const spark = new SparkMD5.ArrayBuffer()
spark.append(uploadInput.files[0])
// 一共要分多少个切片
const chunkCount = Math.ceil(fileSize / chunkSize)
// 存储所有切片信息
const chunkAll = []
for (let i = 0; i < chunkCount; i++) {
const chunkItem = await createChunk(uploadInput.files[0], i, chunkSize)
chunkAll.push(chunkItem)
}
const formData = new FormData()
chunkAll.forEach((item)=>{
formData.append("chunk",item.blob, `chunk${item.index}`)
})
formData.append("MD5", spark.end())
// 发送请求
fetch("http://localhost:3000/upload", {
method: "POST",
body: formData
}).then(res => res.json()).then(res => {
console.log(res)
})
}
// 创建每一个分片信息
function createChunk(file, index, chunkSize) {
return new Promise(resolve => {
// 开始位置
let start = index * chunkSize
// 结束位置
let end = start + chunkSize
// 进行切片
const blob = file.slice(start, end)
const spark = new SparkMD5.ArrayBuffer()
const fileReader = new FileReader()
// 读取
fileReader.readAsArrayBuffer(blob)
// 监听读取完毕
fileReader.onload = (e) => {
// 生成MD5值
spark.append(e.target.result)
resolve({
start,
end,
index,
MD5: spark.end(),
blob
})
}
})
}
</script>
</body>
</html>
- 后端
var express = require('express');
const fs = require("fs")
const crypto = require('crypto');
const SparkMD5 = require('spark-md5')
const path = require("path")
var router = express.Router();
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
/* GET home page. */
router.post('/upload', upload.fields([
{ name: "chunk", maxCount: 100 },
]), function (req, res, next) {
// 分片数组
const chunks = req.files.chunk;
// 分片总数
const totalChunks = chunks.length;
// 生成随机文件名
function generateRandomFileName() {
return crypto.randomBytes(16).toString('hex');
}
// 输出文件路径,包含随机文件名
const randomFileName = generateRandomFileName();
const outputFile = path.join('outChunks/', randomFileName);
// 确保 outChunks文件夹存在
if (!fs.existsSync('outChunks')) {
fs.mkdirSync('outChunks');
}
// 确保输出文件不存在或清空已存在的文件
fs.writeFileSync(outputFile, '');
// 使用流进行文件合并
const outputStream = fs.createWriteStream(outputFile);
let currentChunk = 0;
function writeNextChunk() {
if (currentChunk >= totalChunks) {
// 所有分片写入完毕,关闭输出流
outputStream.end();
return;
}
const chunkStream = fs.createReadStream(chunks[currentChunk].path);
chunkStream.pipe(outputStream, { end: false });
chunkStream.on('end', () => {
// console.log(`分片 ${chunks[currentChunk].originalname} 成功写入`);
currentChunk++;
writeNextChunk(); // 写入下一个分片
}).on('error', (err) => {
console.error(`分片 ${chunks[currentChunk].originalname} 写入错误`, err);
// 可能需要在这里处理错误,比如删除已写入的部分文件,记录日志等
});
}
// 监听重组结束
outputStream.on('finish', () => {
chunks.forEach(chunk => {
fs.unlink(chunk.path, (err) => {
if (err) {
console.error(`删除分片文件 ${chunk.path} 出错:`, err);
} else {
// console.log(`分片文件 ${chunk.path} 已成功删除`);
}
});
});
// 计算文件完整性
const spark = new SparkMD5.ArrayBuffer()
spark.append(outputFile)
if(req.body.MD5 === spark.end()){
console.log("文件完整")
}else{
console.log("文件不完整")
}
}).on('error', (err) => {
console.error('文件合并过程中出现错误:', err);
});
// 开始写入第一个分片
writeNextChunk();
});
module.exports = router;
上传进度跟踪
目前fetch并不能跟踪文件的上传进度,所以这里使用的是XMLHttpRequest
<input type="file" id="file">
<button id="uploadClick">点击上传</button>
<progress id="progress" value="0" max="100"></progress>
<script>
const progress = document.querySelector("#progress")
uploadClick.onclick = () => {
const file = document.querySelector("#file").files[0]
const formData = new FormData()
formData.append("file", file)
// 发送请求
const xhr = new XMLHttpRequest()
xhr.open('post', 'http://127.0.0.1:3000/upload')
xhr.onload = (res) => {
console.log('上传成功', xhr.responseText)
}
xhr.onloadstart = () => {
console.log("上传开始!")
};
xhr.upload.onprogress = (e) => {
// 当前进度
const currentPro = e.loaded/file.size * 100
console.log("当前进度", currentPro)
progress.value = currentPro
}
xhr.send(formData);
}
</script>