现在大部分的应用都会有上传文件的功能,小的可以直接正常直接上传,但是如果遇到大型文件需要上传那会面临着很多问题比如网络中断等问题会造成上传失败,下面我将针对大文件上传出一个解决方案,有什么问题麻烦各位指出。
一、前端实现步骤
1.文件选择
用户通过文件选择控件选择一个或多个大文件。
javascript
<input type="file" id="fileInput" multiple>
2.文件校验
检查文件的大小和类型是否符合要求
javascript
const fileInput = document.getElementById('fileInput');
fileInput.onchange = (e) => {
const files = e.target.files;
for (let file of files) {
if (file.size > MAX_FILE_SIZE) {
alert('文件过大,请选择较小的文件');
return;
}
if (!VALID_FILE_TYPES.includes(file.type)) {
alert('不支持的文件类型');
return;
}
}
};
const MAX_FILE_SIZE =20 * 1024 * 1024; // 20MB
const VALID_FILE_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];//可以自行添加文件类型
3.文件分片
使用Blob对象的slice方法将文件分割成多个小块(分片)。
javascript
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
function chunkFile(file, chunkSize) {
const chunks = [];
for (let start = 0; start < file.size; start += chunkSize) {
const end = Math.min(start + chunkSize, file.size);
chunks.push(file.slice(start, end));
}
return chunks;
}
4.生成文件哈希
为文件生成一个唯一的哈希值(如MD5),用于标识文件和验证文件的完整性。
javascript
import SparkMD5 from 'spark-md5';
function getFileHash(file) {
return new Promise((resolve, reject) => {
const chunkSize = 2048 * 1024; // 2MB
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.onload = (e) => {
const chunk = e.target.result;
spark.append(chunk);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
resolve(spark.end());
}
};
fileReader.onerror = (e) => {
reject('Error reading file: ' + e.message);
};
function loadNext() {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(file.slice(start, end));
}
loadNext();
});
}
5上传分片
使用FormData对象构建上传请求,将分片逐个上传到服务器。可以使用并发请求来提高上传速度
javascript
async function uploadChunks(chunks, fileHash, fileName) {
const promises = chunks.map((chunk, index) => {
const formData = new FormData();
formData.append('file', chunk);
formData.append('index', index);
formData.append('fileHash', fileHash);
formData.append('fileName', fileName);
return fetch('/upload', {
method: 'POST',
body: formData,
}).then(response => response.json());
});
return Promise.all(promises);
}
6.合并分片
当所有分片上传完成后,通知服务器合并分片。
javascript
async function mergeChunks(fileHash, fileName, totalChunks) {
const response = await fetch('/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ fileHash, fileName, totalChunks }),
});
return response.json();
}
7.错误处理
处理上传过程中的各种错误,如网络错误、文件校验错误等。
javascript
async function uploadFile(file) {
try {
const fileHash = await getFileHash(file);
const chunks = chunkFile(file, CHUNK_SIZE);
const fileName = file.name;
const totalChunks = chunks.length;
const uploadResults = await uploadChunks(chunks, fileHash, fileName);
const mergeResult = await mergeChunks(fileHash, fileName, totalChunks);
if (mergeResult.success) {
alert('文件上传成功');
} else {
alert('文件合并失败:' + mergeResult.message);
}
} catch (error) {
alert('文件上传失败:' + error.message);
}
}
fileInput.onchange = (e) => {
const files = e.target.files;
for (let file of files) {
uploadFile(file);
}
};
二、后端实现步骤
-
接收分片
后端接收分片并存储到临时位置
javascript
// 示例:Node.js + Express
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const app = express();
const upload = multer({ dest: 'uploads/temp' });
app.post('/upload', upload.single('file'), (req, res) => {
const { index, fileHash, fileName } = req.body;
const chunkPath = path.join(__dirname, 'uploads/temp', `${fileHash}-${index}`);
fs.renameSync(req.file.path, chunkPath);
res.status(200).send({ success: true });
});
2.合并分片
后端根据文件哈希和分片索引合并分片。
javascript
app.post('/merge', (req, res) => {
const { fileHash, fileName, totalChunks } = req.body;
const filePath = path.join(__dirname, 'uploads', fileName);
const writeStream = fs.createWriteStream(filePath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(__dirname, 'uploads/temp', `${fileHash}-${i}`);
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
fs.unlinkSync(chunkPath); // 删除临时分片文件
}
writeStream.end();
res.status(200).send({ success: true });
});
3.错误处理
处理接收和合并过程中的各种错误,如文件校验错误、存储错误等。
javascript
// 示例:错误处理逻辑
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器内部错误');
});
三、总结
通过以上步骤和技术实现,你可以实现一个前端大文件上传的功能。前端负责文件选择、校验、分片、哈希生成、上传和错误处理;后端负责接收分片、存储和合并分片。不过还有一些别的特殊情况要处理比如取消上传,上传进度展示等