1. 项目背景与需求分析
作为一名长期奋战在前端开发一线的工程师,最近接手了一个颇具挑战性的文件上传需求:需要在Vue.js项目中实现4GB以上大文件的稳定上传功能,并且要完整保留用户上传的文件夹层级结构。这个需求来自一个企业级文档管理系统,用户经常需要上传包含多层子目录的大型项目文件包。
1.1 核心痛点解析
在传统方案中,我们通常会遇到以下几个典型问题:
- 大文件上传不稳定:普通表单上传在遇到网络波动时容易失败,且无法恢复
- 文件夹结构丢失:浏览器默认的文件选择器会拍平文件夹结构
- 进度反馈缺失:用户无法获知大文件上传的实时进度
- 浏览器兼容性问题:不同浏览器对文件API的实现存在差异
1.2 技术方案选型
经过对市面上主流方案的评估,我们发现:
- WebUploader:虽然成熟但已停止维护,对新浏览器特性支持不足
- Uppy:功能丰富但体积庞大,定制化成本高
- 原生input标签:无法满足文件夹结构保持的需求
最终决定基于Vue.js + HTML5 File System API实现自定义解决方案,主要基于以下考量:
- 完全掌控上传流程,便于深度优化
- 可以利用现代浏览器提供的Directory API获取完整目录结构
- 便于与现有Vue技术栈无缝集成
- 可以针对业务需求进行定制化开发
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术实现方案详解
2.1 前端架构设计
2.1.1 文件选择与目录遍历
核心代码实现:
javascript复制// 处理文件夹选择
async function handleDirectoryUpload(event) {
const directoryHandle = await window.showDirectoryPicker()
const fileTree = await traverseDirectory(directoryHandle)
return fileTree
}
// 递归遍历目录结构
async function traverseDirectory(dirHandle, path = '') {
const result = []
for await (const entry of dirHandle.values()) {
const entryPath = `${path}/${entry.name}`
if (entry.kind === 'file') {
const file = await entry.getFile()
result.push({
type: 'file',
name: entry.name,
path: entryPath,
size: file.size,
file
})
} else if (entry.kind === 'directory') {
result.push({
type: 'directory',
name: entry.name,
path: entryPath,
children: await traverseDirectory(entry, entryPath)
})
}
}
return result
}
2.1.2 分片上传策略
针对大文件上传,我们实现了智能分片机制:
javascript复制function createFileChunks(file, chunkSize = 5 * 1024 * 1024) {
const chunks = []
let offset = 0
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize)
chunks.push({
file: chunk,
offset,
size: chunk.size,
index: chunks.length,
total: Math.ceil(file.size / chunkSize),
fileId: generateFileId(file)
})
offset += chunkSize
}
return chunks
}
// 生成文件唯一标识
function generateFileId(file) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = e => {
const hash = crypto.subtle.digest('SHA-256', e.target.result)
resolve(Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join(''))
}
reader.readAsArrayBuffer(file.slice(0, 65536)) // 读取前64KB计算哈希
})
}
2.2 后端接口设计
2.2.1 分片上传接口
php复制// upload_chunk.php
header('Content-Type: application/json');
$chunkIndex = $_POST['chunkIndex'] ?? 0;
$totalChunks = $_POST['totalChunks'] ?? 1;
$fileId = $_POST['fileId'] ?? '';
$relativePath = $_POST['relativePath'] ?? '';
// 验证参数
if (empty($fileId) || !is_numeric($chunkIndex)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid parameters']);
exit;
}
// 创建临时目录
$tempDir = __DIR__ . '/uploads/temp/' . $fileId;
if (!file_exists($tempDir)) {
mkdir($tempDir, 0777, true);
}
// 保存分片
$chunkFile = $tempDir . '/' . $chunkIndex;
if (move_uploaded_file($_FILES['chunk']['tmp_name'], $chunkFile)) {
// 更新上传进度
$progressFile = $tempDir . '/progress.j
