1. 项目背景与核心需求
在国产化信息技术应用创新(信创)环境下,前端开发面临诸多特殊挑战。最近在参与某政务云项目时,我们遇到了一个棘手的问题:如何在国产浏览器中实现文件夹上传并保留完整路径信息。这个需求看似简单,实则涉及多个技术难点:
- 国产浏览器兼容性:主流国产浏览器(如360安全浏览器、红莲花等)对HTML5新特性的支持程度不一
- 安全限制:信创环境对文件系统访问有严格限制,传统路径获取方式往往失效
- 路径保留需求:业务方要求上传时必须保持原始文件夹层级结构
经过多次技术验证,我们最终开发出一套完整的解决方案。下面将详细介绍实现思路和关键技术点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案设计
2.1 整体架构设计
方案采用前后端分离架构,核心思路是通过前端收集路径信息,后端重建目录结构:
code复制前端:
1. 监听文件夹选择事件
2. 递归遍历文件树
3. 提取相对路径信息
4. 分片上传文件数据+路径元数据
后端:
1. 接收文件分片和路径信息
2. 根据路径元数据重建目录
3. 存储完整文件树结构
2.2 关键技术选型
2.2.1 前端核心库
- Vue.js 2.6:兼顾开发效率和IE11兼容性
- Web Uploader:处理大文件分片上传
- File System Access API:获取完整路径信息(现代浏览器)
- 自定义Polyfill:国产浏览器降级方案
2.2.2 后端适配层
- Spring Boot 2.7:基础框架
- 国密SM4加密:文件传输加密
- 多存储引擎:适配达梦、人大金仓等国产数据库
3. 核心实现细节
3.1 前端路径获取方案
3.1.1 现代浏览器实现
Chrome等现代浏览器支持File System Access API:
javascript复制// 获取文件夹句柄
const dirHandle = await window.showDirectoryPicker();
// 递归遍历目录
async function traverseDirectory(dirHandle, relativePath = '') {
const files = [];
for await (const entry of dirHandle.values()) {
const nestedPath = `${relativePath}/${entry.name}`;
if (entry.kind === 'file') {
files.push({
handle: entry,
path: nestedPath
});
} else if (entry.kind === 'directory') {
files.push(...await traverseDirectory(entry, nestedPath));
}
}
return files;
}
3.1.2 国产浏览器降级方案
当API不可用时,采用webkitRelativePath属性:
javascript复制// input元素设置webkitdirectory属性
<input type="file" webkitdirectory @change="handleFolderUpload">
// 处理上传事件
handleFolderUpload(e) {
const files = Array.from(e.target.files);
files.forEach(file => {
const path = file.webkitRelativePath;
// 处理路径分隔符差异
const normalizedPath = path.replace(/\\/g, '/');
this.uploadFile(file, normalizedPath);
});
}
3.2 路径信息处理
3.2.1 路径规范化
不同操作系统路径分隔符不同,需要统一处理:
javascript复制function normalizePath(path) {
// 替换Windows反斜杠
path = path.replace(/\\/g, '/');
// 去除开头斜杠
if (path.startsWith('/')) {
path = path.substring(1);
}
// 处理多层嵌套
return path.split('/').filter(Boolean).join('/');
}
3.2.2 路径元数据提取
javascript复制function extractPathMeta(fullPath) {
const segments = fullPath.split('/');
return {
filename: segments.pop(),
directory: segments.join('/'),
depth: segments.length
};
}
3.3 分片上传实现
3.3.1 前端分片逻辑
javascript复制class Uploader {
constructor() {
this.chunkSize = 5 * 1024 * 1024;
