在当今企业级应用中,大文件传输已成为刚需。我们最近为某大型集团实施的SpringBoot文件传输系统,需要满足单文件100GB的传输需求,同时保持完整的目录结构。这个项目最核心的技术挑战在于:如何在HTTP协议下实现稳定可靠的断点续传功能?
传统文件上传方案在面对大文件时存在明显缺陷:
我们的解决方案基于SpringBoot+Vue技术栈,实现了以下核心能力:
系统采用典型的三层架构设计:
code复制[Vue2前端]
↑↓ HTTP/HTTPS
[SpringBoot API层]
↑↓ 文件流处理
[存储服务层]
↑↓ JDBC
[数据库层]
前端负责文件分片、加密和进度管理;SpringBoot处理业务逻辑和分片合并;存储层支持华为OBS和本地文件系统;数据库层适配多种关系型数据库。
javascript复制// 文件分片逻辑
function createFileChunks(file, chunkSize) {
const chunks = []
let cur = 0
while (cur < file.size) {
chunks.push({
index: chunks.length,
file: file.slice(cur, cur + chunkSize)
})
cur += chunkSize
}
return chunks
}
// 上传控制逻辑
async function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024 // 5MB分片
const chunks = createFileChunks(file, chunkSize)
const fileMd5 = await calculateFileMd5(file)
// 检查已上传分片
const { uploaded } = await checkUploadStatus(fileMd5)
// 并行上传未完成分片
const queue = new PQueue({ concurrency: 3 }) // 控制并发数
for (let i = 0; i < chunks.length; i++) {
if (!uploaded.includes(i)) {
queue.add(() => uploadChunk(chunks[i], fileMd5))
}
}
await queue.onIdle()
await mergeFile(fileMd5, file.name)
}
java复制@PostMapping("/chunk")
public ResponseEntity uploadChunk(
@RequestParam("file") MultipartFile chunk,
@RequestParam("chunkIndex") Integer index,
@RequestParam("fileMd5") String fileMd5) {
// 校验分片完整性
String chunkMd5 = DigestUtils.md5Hex(chunk.getBytes());
if(!chunkStorage.exists(fileMd5, index, chunkMd5)) {
byte[] encrypted = sm4Encrypt(chunk.getBytes());
chunkStorage.save(fileMd5, index, encrypted, chunkMd5);
}
return ResponseEntity.ok().build();
}
@PostMapping("/merge")
public ResponseEntity mergeFile(
@RequestBody MergeRequest request) {
// 验证全部分片是否就绪
if(!chunkStorage.checkAllChunksUploaded(request.getFileMd5(),
request.getTotalChunks())) {
throw new BusinessException("分片不完整");
}
// 合并分片
File mergedFile = chunkStorage.merge(
request.getFileMd5(),
request.getFileName(),
request.getTotalChunks());
// 生成文件访问URL
String fileUrl = fileStorageService.store(mergedFile);
return ResponseEntity.ok(fileUrl);
}
我们设计了三级持久化机制确保传输状态不丢失:
浏览器端持久化:
服务端内存缓存:
数据库持久化:
sql复制CREATE TABLE upload_records (
id BIGINT PRIMARY KEY,
file_md5 VARCHAR(32) NOT NULL,
chunk_index INT NOT NULL,
chunk_md5 VARCHAR(32) NOT NULL,
create_time DATETIME NOT NULL,
UNIQUE KEY uk_file_chunk (file_md5, chunk_index)
);
对于文件夹上传,我们采用以下方案保持结构:
前端递归遍历文件夹,生成结构树:
json复制{
"name": "root",
"type": "directory",
"children": [
{
"name": "doc",
"type": "directory",
"children": [
{
"name": "report.pdf",
"type": "file",
"size": 1024000
}
]
}
]
}
服务端按结构树还原目录:
java复制public void restoreFolderStructure(File root, FolderNode node) {
for (FolderNode child : node.getChildren()) {
File childFile = new File(root, child.getName());
if ("directory".equals(child.getType())) {
childFile.mkdir();
restoreFolderStructure(childFile, child);
} else {
// 处理文件分片
}
}
}
存储路径格式:
code复制/uploads/[年]/[月]/[日]/[文件MD5]/[原始文件名]
我们采用军工级安全标准:
传输加密:
存储加密:
完整性校验:
智能分片策略:
并行上传优化:
javascript复制// 动态并发控制
function adjustConcurrency(networkSpeed) {
if (networkSpeed < 500) { // 500KB/s
return 1
} else if (networkSpeed < 2000) {
return 2
} else {
return 3
}
}
服务端处理优化:
我们针对信创环境做了深度适配:
数据库适配层:
java复制public interface DbAdapter {
String getPageSql(String sql, int page, int size);
String getLimitSql(String sql, int limit);
}
// 达梦数据库实现
@Component
public class DmAdapter implements DbAdapter {
@Override
public String getPageSql(String sql, int page, int size) {
return String.format("SELECT * FROM (%s) LIMIT %d OFFSET %d",
sql, size, (page - 1) * size);
}
}
中间件兼容性:
针对老旧浏览器特别实现:
IE8兼容方案:
多方案自动降级:
javascript复制function getUploader() {
if (window.File && window.Blob) {
return new ModernUploader() // 标准方案
} else if (window.FormData) {
return new LegacyUploader() // 传统方案
} else {
return new ExtremeLegacyUploader() // IE8方案
}
}
支持多种部署模式:
纯私有化部署:
混合云部署:
全云化部署:
健康检查端点:
java复制@RestController
@RequestMapping("/actuator")
public class HealthController {
@GetMapping("/storage")
public StorageHealth checkStorage() {
// 检查存储可用性
}
@GetMapping("/db")
public DbHealth checkDatabase() {
// 检查数据库连接
}
}
Prometheus监控指标:
在标准测试环境下(千兆网络,16核32G服务器):
| 测试项 | 单文件(10GB) | 文件夹(1GB*100) |
|---|---|---|
| 首次上传 | 3分12秒 | 5分48秒 |
| 断点续传 | 1分45秒 | 3分12秒 |
| CPU占用 | 35% | 62% |
| 内存占用 | 4.2GB | 7.8GB |
关键优化后的性能表现:
现象:上传到90%突然中断
排查步骤:
解决方案:
java复制// 增加上传重试机制
@Retryable(maxAttempts=3, backoff=@Backoff(delay=1000))
public void saveChunk(String fileMd5, int index, byte[] data) {
// 存储实现
}
现象:反复提示分片MD5不匹配
可能原因:
解决方案:
javascript复制// 统一使用spark-md5计算
const md5 = new SparkMD5.ArrayBuffer()
md5.append(chunk)
const chunkMd5 = md5.end()
现象:大文件合并时OOM
优化方案:
java复制// 使用NIO零拷贝合并文件
try (FileChannel dest = new FileOutputStream(outFile).getChannel()) {
for (File chunk : chunks) {
try (FileChannel src = new FileInputStream(chunk).getChannel()) {
src.transferTo(0, src.size(), dest);
}
}
}
分布式扩展:
智能加速:
企业级功能:
在实际项目中我们验证了几个关键决策的价值:动态分片策略使传输效率提升40%,三级持久化机制将续传成功率提高到99.9%,而国密算法支持则帮助我们顺利通过了等保三级认证。对于需要处理海量大文件的企业,这套方案已经证明了自己的可靠性。