1. JSP大文件断点续传系统架构解析
在Web应用开发中,大文件上传一直是个棘手的问题。传统表单上传在面对GB级文件时,往往会遇到浏览器卡死、网络中断导致重传等问题。我曾接手一个政府能源监测项目,需要上传每日数GB的监测数据包,经过多次迭代最终形成了这套稳定可靠的解决方案。
1.1 核心需求分析
典型的超大文件上传场景通常面临以下技术挑战:
- 网络稳定性:上传过程中网络抖动或中断
- 浏览器兼容性:需要支持包括老旧浏览器在内的多种环境
- 服务器压力:大文件上传占用服务器内存和带宽资源
- 用户体验:上传进度反馈和中断恢复能力
1.2 技术选型依据
针对20GB级文件上传,我们采用分治策略:
- 前端分片:使用WebUploader实现5MB分片(实测IE8最小支持2MB)
- 断点续传:基于文件MD5+分片索引实现续传定位
- 服务端校验:每片上传后立即校验,避免最后合并失败
- OSS存储:减轻应用服务器存储压力
关键决策:选择5MB分片是平衡了IE8内存限制(约10MB)和网络请求开销后的折中方案。过小会导致请求数爆炸,过大会引发浏览器内存溢出。
2. 前端实现深度优化
2.1 WebUploader定制配置
基础配置中需要特别注意这些参数:
javascript复制var uploader = WebUploader.create({
swf: '//cdn.jsdelivr.net/npm/webuploader@0.1.1/dist/Uploader.swf',
server: '/upload',
chunkSize: 5 * 1024 * 1024, // 关键参数
threads: 3, // IE8最大并发数
duplicate: true, // 允许重复文件
prepareNextFile: true // 预读下个文件提升效率
});
2.1.1 IE8特殊处理
通过特征检测实现渐进增强:
javascript复制function initUploader() {
if (isIE8()) {
// 降级为单线程
config.threads = 1;
// 使用Flash版分片
uploader = new WebUploader.Uploader(config);
} else {
// 现代浏览器使用HTML5版本
uploader = WebUploader.create(config);
}
}
2.2 断点续传关键实现
2.2.1 文件指纹生成
采用SparkMD5计算文件指纹(兼容IE8的asm.js版本):
javascript复制function calcFileMd5(file, callback) {
var blobSlice = File.prototype.slice || File.prototype.mozSlice;
var chunkSize = 2 * 1024 * 1024; // 2MB/块
var chunks = Math.ceil(file.size / chunkSize);
var spark = new SparkMD5.ArrayBuffer();
function loadNext(chunk) {
var reader = new FileReader();
reader.onload = function(e) {
spark.append(e.target.result);
if (chunk < chunks) {
loadNext(++chunk);
} else {
callback(spark.end());
}
};
reader.readAsArrayBuffer(blobSlice.call(file, chunk * chunkSize, (chunk + 1) * chunkSize));
}
loadNext(0);
}
2.2.2 进度持久化方案
利用localStorage保存上传状态:
javascript复制function saveProgress(fileMd5, chunkIndex) {
var progress = JSON.parse(localStorage.getItem('uploadProgress') || '{}');
if (!progress[fileMd5]) {
progress[fileMd5] = { chunks: [] };
}
progress[fileMd5].chunks.push(chunkIndex);
localStorage.setItem('uploadProgress', JSON.stringify(progress));
}
3. 服务端关键技术实现
3.1 分片处理流程
java复制// 伪代码展示核心逻辑
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 1. 获取分片参数
String fileMd5 = req.getParameter("md5");
int chunk = Integer.parseInt(req.getParameter("chunk"));
int chunks = Integer.parseInt(req.getParameter("chunks"));
// 2. 创建临时存储目录
File tempDir = new File("/upload/temp/" + fileMd5);
if (!tempDir.exists()) tempDir.mkdirs();
// 3. 保存分片文件
File chunkFile = new File(tempDir, chunk + ".part");
try (InputStream in = req.getInputStream();
OutputStream out = new FileOutputStream(chunkFile)) {
IOUtils.copy(in, out);
}
// 4. 检查是否全部完成
if (tempDir.listFiles().length == chunks) {
mergeFiles(fileMd5, tempDir);
}
}
3.2 内存优化技巧
对于GB级文件合并,必须避免内存溢出:
java复制void mergeFiles(String fileMd5, File tempDir) throws IOException {
File output = new File("/upload/final/" + fileMd5 + ".dat");
try (FileChannel outChannel = new FileOutputStream(output).getChannel()) {
for (int i = 0; i < tempDir.listFiles().length; i++) {
File chunkFile = new File(tempDir, i + ".part");
try (FileChannel inChannel = new FileInputStream(chunkFile).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
}
// 清理临时文件
FileUtils.deleteDirectory(tempDir);
}
4. 性能优化实战记录
4.1 上传速度提升方案
通过测试对比不同配置下的上传速度(100MB文件,10Mbps带宽):
| 配置方案 | 耗时(s) | 备注 |
|---|---|---|
| 单线程 | 82.3 | 基准值 |
| 3线程 | 28.7 | 最佳平衡点 |
| 5线程 | 26.5 | IE8易崩溃 |
| 分片2MB+3线程 | 27.1 | 适合不稳定网络 |
| 分片10MB+3线程 | 25.8 | 现代浏览器推荐 |
4.2 常见问题排查指南
问题1:分片上传后合并失败
现象:所有分片显示上传成功,但最终文件损坏
排查步骤:
- 检查临时目录权限(Linux需755权限)
- 验证分片MD5与服务端记录是否一致
- 确认合并时的文件顺序是否正确
问题2:IE8上传卡死
解决方案:
- 添加ActiveX白名单策略
html复制<!-- 必须放在head顶部 -->
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
- 注册Flash插件CLSID
reg复制Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Macromedia\FlashPlayer\SafeVersions]
"10.0"="10.0.0.0"
5. 安全增强方案
5.1 分片校验机制
服务端增加SHA-256校验:
java复制MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream is = file.getInputStream()) {
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
}
String checksum = Hex.encodeHexString(digest.digest());
5.2 防御措施清单
- 文件类型白名单:通过魔数校验真实文件类型
- 分片大小限制:拒绝异常大小的分片请求
- 频率限制:同一IP每分钟不超过60个分片
- 病毒扫描:集成ClamAV进行实时检测
在实际能源数据采集项目中,这套方案稳定支撑了日均500GB+的数据上传,最关键的改进是增加了分片自动修复机制——当某分片连续3次校验失败时,会自动触发该分片的重新上传,而无需用户干预。