1. 项目背景与核心挑战
在航空航天领域,卫星数据的传输与存储一直是关键的技术瓶颈。我们最近负责的一个遥感卫星地面站项目,需要处理单日高达2TB的原始观测数据。这些数据以文件夹形式组织,包含数百个不同格式的文件(如HDF5、NetCDF、RAW图像等),传统FTP或HTTP上传方式在稳定性、完整性和用户体验方面都难以满足需求。
核心痛点集中在三个方面:
- 大文件传输中断:卫星数据单文件常达10GB以上,网络波动导致传输中断后需要完全重传
- 文件夹结构保持:科学数据需要严格保持原始目录结构和文件元数据
- 完整性验证:必须确保每个文件块在传输过程中未被篡改,这对后续数据分析至关重要
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. HTML5技术选型解析
2.1 为什么选择HTML5方案
相比传统Java Applet或ActiveX方案,HTML5提供了一套完整的现代Web文件处理API:
- File API:直接访问本地文件系统(通过
<input type="file">的webkitdirectory属性) - Blob.slice():实现文件分片(支持自定义分片策略)
- Web Workers:后台计算文件哈希值不阻塞UI
- ProgressEvent:实时上传进度反馈
- IndexedDB:支持断点续传的本地存储
关键提示:Chrome/Firefox对目录上传的支持度最好,需针对Edge/ Safari做降级处理
2.2 典型技术栈组合
mermaid复制graph TD
A[用户界面] -->|选择文件夹| B[HTML5 FileSystem API]
B --> C[分片处理器]
C --> D[Web Worker计算MD5]
D --> E[XMLHttpRequest2上传]
E --> F[服务端校验]
3. 完整实现方案
3.1 前端核心代码实现
文件夹选择与遍历:
javascript复制document.getElementById('folderInput').addEventListener('change', function(e) {
const files = e.target.files;
const fileList = [];
// 递归处理WebKitDirectory获取的文件列表
function traverseFiles(entries, path = '') {
for (let entry of entries) {
if (entry.isDirectory) {
const reader = entry.createReader();
reader.readEntries(entries => traverseFiles(entries, path + entry.name + '/'));
} else {
entry.file(file => {
file.fullPath = path + file.name; // 保留原始路径
fileList.push(file);
});
}
}
}
traverseFiles(Array.from(files));
});
分片上传逻辑:
javascript复制async function uploadFile(file, chunkSize = 5 * 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
const fileHash = await calculateMD5(file); // Web Worker计算
for (let i = 0; i < totalChunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
formData.append('fileHash', fileHash);
formData.append('relativePath', file.fullPath); // 保留目录结构
await axios.post('/upload', formData, {
onUploadProgress: progress => {
console.log(`文件 ${file.name} 分片 ${i} 进度: ${Math.round((progress.loaded / progress.total) * 100)}%`);
}
});
}
}
3.2 服务端关键设计
分片接收接口(Node.js示例):
javascript复制const fs = require('fs-extra');
const multer = require('multer');
const upload = multer({ dest: 'temp/' });
app.post('/upload', upload.single('chunk'), async (req, res) => {
const { chunkIndex, totalChunks, fileHash, relativePath } = req.body;
// 按文件哈希创建临时目录
const tempDir = `uploads/temp/${fileHash}`;
await fs.ensureDir(tempDir);
// 移动分片到临时位置
await fs.move(req.file.path, `${tempDir}/${chunkIndex}`);
// 检查是否所有分片已到达
const uploadedChunks = await fs.readdir(tempDir);
if (uploadedChunks.length === parseInt(totalChunks)) {
await assembleChunks(tempDir, relativePath); // 分片合并
verifyIntegrity(fileHash); // 完整性校验
}
res.status(200).send('OK');
});
分片合并算法:
javascript复制async function assembleChunks(tempDir, relativePath) {
const chunks = (await fs.readdir(tempDir))
.sort((a, b) => parseInt(a) - parseInt(b));
// 创建目标目录结构
const targetPath = `uploads/${path.dirname(relativePath)}`;
await fs.ensureDir(targetPath);
// 顺序写入文件
const writeStream = fs.createWriteStream(`uploads/${relativePath}`);
for (const chunk of chunks) {
const chunkData = await fs.readFile(`${tempDir}/${chunk}`);
writeStream.write(chunkData);
}
writeStream.end();
}
4. 完整性保障机制
4.1 三级校验体系
- 分片级CRC32:每个分片上传时携带校验码
- 文件级MD5:合并完成后验证完整文件哈希
- 目录级Manifest:整个文件夹生成SHA-256校验文件
4.2 断点续传实现
javascript复制// 前端检查已上传分片
async function checkUploadedChunks(fileHash) {
const response = await axios.get(`/upload-status?fileHash=${fileHash}`);
return response.data.uploadedChunks; // 返回已上传分片索引数组
}
// 服务端记录状态(Redis示例)
app.get('/upload-status', async (req, res) => {
const uploaded = await redisClient.smembers(`upload:${req.query.fileHash}`);
res.json({ uploadedChunks: uploaded.map(Number) });
});
5. 性能优化实践
5.1 并发控制策略
javascript复制// 限制同时上传3个分片
const CONCURRENCY = 3;
const semaphore = new Semaphore(CONCURRENCY);
async function uploadAllFiles() {
await Promise.all(fileList.map(file =>
semaphore.acquire().then(() =>
uploadFile(file).finally(() => semaphore.release())
)
));
}
5.2 内存优化技巧
- 使用
Stream替代Buffer处理大文件 - 分片大小动态调整(网络好时增大分片)
- Web Workers卸载哈希计算任务
6. 浏览器兼容方案
特征检测与降级策略:
javascript复制function checkBrowserSupport() {
return !!(
window.File &&
window.FileReader &&
window.FileList &&
window.Blob &&
'webkitdirectory' in document.createElement('input')
);
}
if (!checkBrowserSupport()) {
showFallbackUploader(); // 显示传统表单上传界面
}
7. 实测数据对比
| 方案 | 2GB文件夹上传时间 | 失败恢复时间 | CPU占用 |
|---|---|---|---|
| 传统表单上传 | 42分18秒 | 完全重传 | 12% |
| HTML5分片上传 | 8分37秒 | 15秒 | 68% |
| 商业SDK(参考值) | 6分52秒 | 8秒 | 82% |
8. 安全防护要点
-
目录遍历攻击防护:规范化
relativePath参数javascript复制function sanitizePath(path) { return path.replace(/\.\.\//g, '').replace(/^\//, ''); } -
CSRF防护:上传接口添加Token验证
-
分片校验:服务端重新计算分片哈希
9. 部署架构建议
code复制负载均衡器(Nginx)
├── Web服务器集群(处理前端请求)
└── 上传服务器专用节点
├── 临时存储(高性能SSD)
└── 持久化存储(分布式文件系统)
10. 调试与监控
前端错误捕获:
javascript复制window.addEventListener('unhandledrejection', event => {
sentry.captureException(event.reason);
showToast(`上传失败: ${event.reason.message}`);
});
// 网络中断检测
axios.interceptors.response.use(null, error => {
if (error.message === 'Network Error') {
startOfflineMode();
}
return Promise.reject(error);
});
服务端监控指标:
- 分片上传成功率
- 合并操作耗时
- 最终校验失败率
- 存储空间使用趋势
11. 移动端适配方案
- 拍照上传优化:压缩EXIF元数据
- 弱网处理:自动降低分片大小(1MB→256KB)
- 后台上传:Service Worker维持任务
12. 项目演进方向
- WebRTC实现P2P分片传输
- WASM加速哈希计算
- 区块链存证校验记录
在卫星地面站的实际部署中,这套方案使数据回传失败率从6.7%降至0.3%,操作员反馈"就像在本地复制文件夹一样简单"。对于更复杂的场景,我们正在试验WebTransport协议替代HTTP/2,以进一步提升传输效率。
