1. 银行级大文件上传系统开发实战(Vue3+原生JS实现)
最近在开发一个银行系统的文件上传模块时,遇到了20GB大文件上传的需求。经过两周的攻坚,终于实现了一套完整的解决方案。本文将分享如何用Vue3+原生JS实现支持断点续传、加密传输、文件夹结构保留的大文件上传系统。
提示:本文代码已开源,可直接用于商业项目,但生产环境请务必做好安全加固。
1.1 核心需求分析
银行系统对文件上传有特殊要求:
- 超大文件支持(单文件20GB+)
- 文件夹结构完整保留
- 传输过程加密(AES/SM4)
- 断点续传能力
- 全浏览器兼容(包括IE9+)
1.2 技术选型考量
为什么选择Vue3+原生JS组合?
- Vue3提供良好的代码组织能力
- 原生JS实现核心上传逻辑确保性能
- 避免第三方库的依赖和兼容性问题
- 更灵活应对银行系统的特殊需求
2. 前端架构设计与实现
2.1 文件选择与结构解析
关键点在于处理webkitRelativePath属性:
javascript复制handleFileSelect(e) {
const files = Array.from(e.target.files);
const fileMap = {};
files.forEach(file => {
const path = file.webkitRelativePath || file.name;
const parts = path.split('/');
const fileName = parts.pop();
let currentLevel = fileMap;
parts.forEach(part => {
currentLevel[part] = currentLevel[part] || {
name: part,
isDir: true,
children: {}
};
currentLevel = currentLevel[part].children;
});
currentLevel[fileName] = {
name: fileName,
file: file,
size: file.size,
isDir: false
};
});
this.fileTree = this.buildDisplayTree(fileMap);
}
2.2 分片上传核心逻辑
采用5MB固定分片大小,兼顾性能和可靠性:
javascript复制async uploadFile(file, relativePath) {
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
for(let i=0; i<chunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const encryptedChunk = await this.encryptChunk(chunk);
await this.uploadChunk(encryptedChunk, i, chunks);
// 更新进度
this.progress = ((i + 1) / chunks) * 100;
}
await this.mergeFile(file.name, chunks);
}
2.3 断点续传实现方案
基于文件MD5和localStorage的状态保存:
javascript复制// 计算文件MD5(用于唯一标识)
async calculateFileMD5(file) {
const spark = new SparkMD5.ArrayBuffer();
const chunkSize = 2 * 1024 * 1024; // 2MB chunks
let currentChunk = 0;
return new Promise(resolve => {
const fileReader = new FileReader();
fileReader.onload = e => {
spark.append(e.target.result);
currentChunk++;
if(currentChunk * chunkSize < file.size) {
loadNext();
} else {
resolve(spark.end());
}
};
function loadNext() {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
fileReader.readAsArrayBuffer(file.slice(start, end));
}
loadNext();
});
}
// 保存上传状态
saveUploadStatus(fileMd5, status) {
localStorage.setItem(`upload_${fileMd5}`, JSON.stringify(status));
}
3. 加密传输实现
3.1 AES加密方案
使用crypto-js实现前端加密:
javascript复制import CryptoJS from 'crypto-js';
async encryptChunk(chunk) {
// 从服务器获取加密密钥(实际项目)
const key = await this.fetchEncryptionKey();
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
const wordArray = CryptoJS.lib.WordArray.create(reader.result);
const encrypted = CryptoJS.AES.encrypt(wordArray, key);
resolve(new Blob([encrypted.toString()]));
};
reader.readAsArrayBuffer(chunk);
});
}
3.2 安全注意事项
- 密钥必须由服务器动态生成
- 每次会话使用不同密钥
- 传输过程必须使用HTTPS
- 考虑添加时间戳防重放攻击
4. 后端配合要点
4.1 SpringBoot接收分片
关键API设计:
java复制@PostMapping("/chunk")
public ResponseEntity uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("fileMd5") String fileMd5) {
// 存储分片到临时目录
String chunkPath = TEMP_DIR + fileMd5 + "_" + chunkIndex;
file.transferTo(new File(chunkPath));
return ResponseEntity.ok().build();
}
4.2 文件合并逻辑
java复制@PostMapping("/merge")
public ResponseEntity mergeFile(
@RequestParam("fileMd5") String fileMd5,
@RequestParam("fileName") String fileName) {
try (OutputStream out = new FileOutputStream(FINAL_DIR + fileName)) {
for(int i=0; i<totalChunks; i++) {
String chunkPath = TEMP_DIR + fileMd5 + "_" + i;
Files.copy(new File(chunkPath).toPath(), out);
new File(chunkPath).delete(); // 删除临时分片
}
return ResponseEntity.ok().build();
}
}
5. 兼容性处理方案
5.1 IE9特殊处理
添加必要的polyfill:
html复制<!-- 在index.html中添加 -->
<!--[if IE]>
<script src="https://cdn.jsdelivr.net/npm/bluebird@3/js/browser/bluebird.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fetch-ie8@1.5.0/fetch.js"></script>
<![endif]-->
5.2 文件夹上传降级
javascript复制triggerFileInput() {
const input = document.getElementById('fileInput');
if(isIE()) {
input.removeAttribute('webkitdirectory');
alert('IE浏览器仅支持单文件上传');
} else {
input.setAttribute('webkitdirectory', '');
}
input.click();
}
6. 性能优化技巧
6.1 并发控制
限制同时上传的分片数(建议3-5个):
javascript复制const MAX_CONCURRENT = 3;
let currentUploading = 0;
async uploadAllChunks() {
const chunks = /* 分片数组 */;
const queue = [];
while(chunks.length > 0) {
if(currentUploading < MAX_CONCURRENT) {
const chunk = chunks.shift();
currentUploading++;
queue.push(
this.uploadChunk(chunk)
.finally(() => currentUploading--)
);
} else {
await Promise.race(queue);
}
}
await Promise.all(queue);
}
6.2 内存优化
使用Blob.slice()避免大文件内存溢出:
javascript复制function processLargeFile(file) {
const chunkSize = 5 * 1024 * 1024;
let offset = 0;
return new Promise(resolve => {
function readNext() {
const chunk = file.slice(offset, offset + chunkSize);
const reader = new FileReader();
reader.onload = () => {
// 处理chunk
offset += chunkSize;
if(offset < file.size) {
readNext();
} else {
resolve();
}
};
reader.readAsArrayBuffer(chunk);
}
readNext();
});
}
7. 生产环境部署建议
7.1 Nginx关键配置
code复制# 调整上传大小限制
client_max_body_size 20G;
# 超时设置
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
# 启用gzip压缩(不压缩已压缩文件)
gzip on;
gzip_exclude application/octet-stream;
7.2 服务器优化
- 使用单独的存储服务器
- 增加tmp目录空间
- 调整Linux文件描述符限制
- 使用SSD存储提高IO性能
8. 常见问题排查
8.1 上传中断问题
可能原因及解决方案:
- 网络不稳定:增加重试机制
- 服务器超时:调整Nginx超时设置
- 浏览器崩溃:优化内存使用
- 跨域问题:正确配置CORS
8.2 性能瓶颈分析
使用Chrome DevTools的Performance面板:
- 记录上传过程
- 分析主要耗时操作
- 检查内存使用曲线
- 识别热点函数
9. 安全加固方案
9.1 必备安全措施
- 文件类型白名单校验
- 病毒扫描接口集成
- 上传频率限制
- 权限验证(JWT等)
9.2 防恶意上传策略
java复制// 示例:文件类型校验
public boolean isSafeFile(MultipartFile file) {
String[] allowedTypes = {"pdf", "doc", "docx", "xls", "xlsx"};
String ext = FilenameUtils.getExtension(file.getOriginalFilename());
return Arrays.asList(allowedTypes).contains(ext.toLowerCase());
}
10. 项目扩展方向
10.1 进阶功能建议
- 实时进度同步(WebSocket)
- 云存储直传(OSS/COS)
- 分布式文件存储
- 自动压缩/解压功能
10.2 监控与统计
- 上传成功率监控
- 速度波动分析
- 用户行为分析
- 异常自动报警
这套方案已在某银行系统稳定运行半年,单日处理文件超过10TB。核心优势在于:
- 纯前端实现,服务端压力小
- 完善的错误恢复机制
- 灵活的架构设计
- 严格的安全控制
实际开发中最大的教训是:一定要在早期做好压力测试,我们曾因低估并发量导致存储服务器宕机。现在系统设计容量是实际需求的5倍以上。