视频文件在js中如何上传到服务器指定文件夹?
一个准毕业码农的"史诗级"文件管理系统开发日记
各位码友大家好,我就是那个被10G大文件折磨得死去活来的山西大三狗!😭
血泪开发史
“老师,我这个文件管理系统能传10G文件!” —— 这话说出来我自己都不信,毕竟我的笔记本硬盘总共才256G…
前端技术选型心路历程
“原生JS+Vue3?这不是前后矛盾吗?” —— 我的导师看到我的技术栈时露出了关爱智障的眼神。但我要证明这是可以实现的!
// 大文件分片上传核心代码片段(纯手工打造,童叟无欺)
const MAX_CHUNK_SIZE = 5 * 1024 * 1024; // 5MB一片,别问为什么,问就是IE8会哭
function splitFile(file) {
let chunks = [];
let start = 0;
while (start < file.size) {
let end = Math.min(start + MAX_CHUNK_SIZE, file.size);
chunks.push({
chunk: file.slice(start, end),
index: chunks.length,
total: Math.ceil(file.size / MAX_CHUNK_SIZE),
fileName: file.name,
fileSize: file.size
});
start = end;
}
return chunks;
}
// 断点续传黑科技(localStorage+IndexedDB双备份)
function saveProgress(fileMd5, chunkIndex) {
try {
localStorage.setItem(`upload_${fileMd5}`, chunkIndex);
// 这里应该还有IndexedDB的代码,但是...我还没学会...
} catch (e) {
console.log("您的存储空间已爆炸💥");
}
}
浏览器兼容性炼狱
“IE8?Win7?国产浏览器?” —— 当我看到需求文档时,我以为回到了2010年…
// 检测浏览器是否支持File API(给IE8老爷爷准备的轮椅)
function checkBrowserSupport() {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
alert('您的浏览器太老了,建议升级到IE9...等等,IE9也很老啊!');
return false;
}
return true;
}
// 文件夹上传的魔术代码(其实也没那么神奇)
function handleFolderUpload(event) {
let files = event.target.files;
let entries = [];
// 这个webkitRelativePath是文件夹上传的关键!
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (file.webkitRelativePath) {
entries.push({
path: file.webkitRelativePath,
file: file
});
}
}
return entries;
}
开发路上那些坑
-
断点续传的离线存储:我天真地以为localStorage就够用了,直到遇到了10G文件…现在正在恶补IndexedDB
-
文件夹层级保留:"webkitRelativePath"这个属性名字看起来就像是个临时工写的,但它确实是唯一能用的方案
-
加密传输:本来是准备用AES的,后来发现IE8不支持,现在正在研究如何用RSA+DES组合拳
求带飞环节
“有没有师傅愿意收留我这个迷途的羔羊?后端代码还是一片空白啊!Python?Java?PHP?我都可以学!” 😇
前端完整实现思路(伪代码版)
class MegaUploader {
constructor() {
this.chunks = [];
this.uploaded = 0;
this.fileMd5 = '';
this.resumeData = this.loadResumeData();
}
// 大文件MD5计算(分片计算避免卡死)
calculateFileMd5(file) {
return new Promise((resolve) => {
// 这里应该有很复杂的计算逻辑...
setTimeout(() => resolve("mock_md5_" + file.name), 500);
});
}
// 断点续传数据加载
loadResumeData() {
// 先从localStorage尝试
// 失败后尝试IndexedDB
// 再失败...那就从头开始吧
return {};
}
// 真正的上传逻辑
async upload(file) {
this.fileMd5 = await this.calculateFileMd5(file);
this.chunks = splitFile(file);
// 检查服务器哪些分片已经上传
let uploadedChunks = await checkServerProgress(this.fileMd5);
// 只上传未完成的部分
for (let i = 0; i < this.chunks.length; i++) {
if (!uploadedChunks.includes(i)) {
await this.uploadChunk(this.chunks[i]);
saveProgress(this.fileMd5, i);
}
}
// 所有分片完成,通知服务器合并
await notifyServerMerge(this.fileMd5, file.name);
}
// 上传单个分片
uploadChunk(chunk) {
return new Promise((resolve, reject) => {
let formData = new FormData();
formData.append('file', chunk.chunk);
formData.append('chunkIndex', chunk.index);
formData.append('totalChunks', chunk.total);
formData.append('fileMd5', this.fileMd5);
// 这里应该有加密逻辑...
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
if (response.ok) {
this.uploaded++;
resolve();
} else {
reject('上传失败');
}
});
});
}
}
致未来的雇主大大
“虽然我现在连个完整的后端都写不出来,但我前端已经能画出漂亮的进度条了!” 💪
PS:那个QQ群是真的,红包也是真的(虽然最大那个99元的红包可能已经被我领走了…)
将组件复制到项目中
示例中已经包含此目录

引入组件

配置接口地址
接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de

处理事件

启动测试

启动成功

效果

数据库

效果预览
文件上传

文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载示例
点击下载完整示例









