前端代码:
<!DOCTYPE html>
<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>Document</title>
<style>
#pro {
height: 10px;
width: 0;
background-color: #f00;
}
</style>
</head>
<body>
<h3>单文件上传 【断点续传】</h3>
<input type="file" id="fileIpt">
<h3>进度:</h3>
<div id="pro"></div>
<span id="protxt"></span>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.min.js"></script>
<script>
$(function(){
$('#fileIpt').change(res => {
const file = res.target.files[0];
obj.uploadFile(file);
})
})
var progressval = 0; // 进度
var slipefile = []; // 切片文件数组
var urladdress = ''; // 文件地址
const obj = {
async uploadFile(file) {
slipefile = [];
let chunks = await this.getFileChunks(file, Math.pow(1024, 2));
// console.log(chunks);
await chunks.forEach(chunk => {
let formData = new FormData();
formData.append('file', chunk.file);
axios({
url: 'http://192.168.88.214:3001/uploadsplit',
method: 'post',
data: formData,
params: {
hash: chunk.hash,
name: chunk.name,
type: chunk.type,
}
}).then(async res => {
progressval = Number(((chunk.name + 1) / chunks.length * 100).toFixed(2));
slipefile.push(res.data);
document.querySelector('#pro').style.width = progressval + 'px';
document.querySelector('#protxt').innerHTML = progressval + '%';
}).catch(err => {
// slipefile = { err };
})
});
let { hash, type } = await this.getFileInfo(file);
axios({
url: 'http://192.168.88.214:3001/uploadmerge',
method: 'get',
params: { hash, type }
}).then(result => {
progressval = 100;
slipefile = [result.data, ...slipefile];
document.querySelector('#pro').style.width = progressval + 'px';
document.querySelector('#protxt').innerHTML = progressval + '%';
// urladdress = result.data
console.log(result.data);
})
},
getFileInfo(file) {
return new Promise((resolve, reject) => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = (e) => {
let buffer = e.target.result;
let spark = new SparkMD5.ArrayBuffer();
spark.append(buffer);
let hash = spark.end();
resolve({ hash, type: file.type });
}
})
},
/**
* 文件切片
* file 文件
* size 单个切片大小
*/
async getFileChunks(file, size) {
let { hash, type } = await this.getFileInfo(file);
let chunks = [];
let count = Math.ceil(file.size / size);
let index = 0;
while (index < count) {
chunks.push({ file: file.slice(index * size, (index + 1) * size), hash, name: index, type });
index++;
}
return chunks;
}
}
</script>
</html>
- 后端代码(nodejs)
const express = require('express');
const UploadRote = express.Router();
const path = require('path');
const fs = require('fs');
var multiparty = require('multiparty');
var mime = require('mime-types');
const multipartyUploadFile = (req) => {
let { hash, name } = req.query;
return new Promise((resolve, reject) => {
let form = new multiparty.Form({});
form.parse(req, (err, fields, files) => {
if (err) return reject(err);
let file = files.file[0];
let dir = `${path.join(__dirname, '../')}upload/${hash}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
};
let savePath = `${dir}/${name}`;
fs.renameSync(file.path, savePath);
file.realPath = savePath;
return resolve({ fields, files });
});
})
}
// 切片上传
UploadRote.post('/uploadsplit', (req, res) => {
let { hash, name, type } = req.query;
let dir = `${path.join(__dirname, '../')}upload/${hash}`;
// 文件是否已经上传过
let realPath = `${dir}.${mime.extension(type)}`;
if (fs.existsSync(realPath)) {
res.status(200).json({ realPath, msg: '文件已存在,无需上传!' });
return
};
// 切片路径 判断切片是否上传过
let chunkPath = `${dir}/${name}`;
if (fs.existsSync(chunkPath)) {
res.status(200).json({ chunkPath, msg: '切片已存在,跳过此切片' });
} else {
multipartyUploadFile(req).then(value => {
res.status(200).json(value);
}).catch(reason => {
res.status(500).json(reason);
})
};
});
// 合并文件
UploadRote.get('/uploadmerge', (req, res) => {
let { hash, type } = req.query;
let dir = `${path.join(__dirname, '../')}upload/${hash}`;
// 文件是否已经上传过
let realPath = `${dir}.${mime.extension(type)}`;
if (fs.existsSync(realPath)) {
res.status(200).json({ realPath, msg: '文件已存在,无需合并!' });
return
}
let fileList = fs.readdirSync(dir);
fileList.sort((a, b) => a - b).forEach(item => {
fs.appendFileSync(`${dir}.${mime.extension(type)}`, fs.readFileSync(`${dir}/${item}`));
fs.unlinkSync(`${dir}/${item}`);
})
fs.rmdirSync(dir);
res.status(200).json({ path: `${dir}.${mime.extension(type)}`, msg: '合并成功!' });
});