大文件传输解决方案设计
项目背景与需求分析
作为江西某软件公司的前端工程师,我面临一个具有挑战性的文件传输需求场景:
- 超大文件传输:支持20G单文件传输,100G+的10万级文件夹传输
- 全平台兼容:包括IE8、国产浏览器和多种国产操作系统
- 安全要求:支持SM4/AES加密传输和存储
- 模块化设计:提供前后端完整源代码,易于集成
- 可靠性要求:需要解决现有WebUploader方案的问题(进度丢失、不稳定等)
技术选型与架构设计
前端方案
针对IE8兼容性问题,我们采用"渐进增强"策略:
- 现代浏览器:使用基于Vue3的现代上传组件
- IE8:回退到基于Flash的上传方案(作为备选)
// 文件上传组件入口检测
function detectUploadMethod() {
if (window.File && window.FileReader && window.FileList && window.Blob) {
return 'modern'; // 使用HTML5上传
} else if (window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash')) {
return 'flash'; // 使用Flash上传
} else {
throw new Error('不支持的浏览器环境');
}
}
后端方案
基于.NET Core构建高性能文件传输服务,主要特点:
- 分块上传/下载
- 断点续传
- 加密传输存储
- 多存储后端支持(华为云OBS/本地存储)
核心组件实现
前端实现(Vue3组件)
// LargeFileUploader.vue
import { ref } from 'vue';
import { useChunkedUpload } from './composables/useChunkedUpload';
const props = defineProps({
maxFileSize: { type: Number, default: 21474836480 }, // 20GB
chunkSize: { type: Number, default: 10485760 }, // 10MB
accept: { type: String, default: '*' },
});
const {
files,
progress,
upload,
pause,
resume,
cancel,
} = useChunkedUpload({
maxFileSize: props.maxFileSize,
chunkSize: props.chunkSize,
endpoint: '/api/upload',
});
后端实现(.NET Core控制器)
// FileTransferController.cs
[ApiController]
[Route("api/[controller]")]
public class FileTransferController : ControllerBase
{
private readonly IFileTransferService _fileTransferService;
private readonly ILogger _logger;
public FileTransferController(
IFileTransferService fileTransferService,
ILogger logger)
{
_fileTransferService = fileTransferService;
_logger = logger;
}
[HttpPost("upload")]
[DisableRequestSizeLimit]
public async Task UploadChunk(
[FromQuery] string fileId,
[FromQuery] int chunkNumber,
[FromQuery] int totalChunks,
[FromQuery] string fileName,
[FromQuery] long fileSize,
IFormFile chunk)
{
try
{
var result = await _fileTransferService.ProcessChunkAsync(
fileId, chunkNumber, totalChunks, fileName, fileSize, chunk);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "上传分块失败");
return StatusCode(500, new { error = ex.Message });
}
}
[HttpGet("download")]
public async Task DownloadFile(
[FromQuery] string fileId,
[FromQuery] string fileName)
{
try
{
var (stream, contentType) = await _fileTransferService.GetFileStreamAsync(fileId, fileName);
return File(stream, contentType, fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, "下载文件失败");
return StatusCode(500, new { error = ex.Message });
}
}
}
关键技术实现
1. 断点续传实现
// FileTransferService.cs
public async Task ProcessChunkAsync(
string fileId, int chunkNumber, int totalChunks,
string fileName, long fileSize, IFormFile chunk)
{
// 检查并创建临时目录
var tempDir = Path.Combine(Path.GetTempPath(), "uploads", fileId);
Directory.CreateDirectory(tempDir);
// 保存分块文件
var chunkPath = Path.Combine(tempDir, $"{chunkNumber}.part");
await using var stream = new FileStream(chunkPath, FileMode.Create);
await chunk.CopyToAsync(stream);
// 检查是否所有分块都已上传
var uploadedChunks = Directory.GetFiles(tempDir).Length;
if (uploadedChunks == totalChunks)
{
// 合并文件
var finalPath = await MergeChunksAsync(tempDir, fileName, totalChunks);
// 加密存储
await _storageService.StoreEncryptedFileAsync(finalPath, fileId);
return new ChunkUploadResult
{
Completed = true,
FileId = fileId,
Progress = 100
};
}
return new ChunkUploadResult
{
Completed = false,
FileId = fileId,
Progress = (int)((uploadedChunks / (double)totalChunks) * 100)
};
}
2. 文件夹结构保留实现
// 前端文件夹上传处理
async function uploadFolder(folder, basePath = '') {
const entries = await readDirectoryEntries(folder);
for (const entry of entries) {
if (entry.isFile) {
const file = await getAsFile(entry);
const relativePath = basePath + entry.name;
await uploadFile(file, {
relativePath,
isFolderUpload: true
});
} else if (entry.isDirectory) {
await uploadFolder(
entry,
`${basePath}${entry.name}/`
);
}
}
}
// 后端文件夹结构重建
public async Task ReconstructFolderStructure(string sessionId, string rootPath)
{
var folderInfo = await _dbContext.UploadSessions
.Where(s => s.SessionId == sessionId)
.Select(s => s.FolderStructure)
.FirstOrDefaultAsync();
if (string.IsNullOrEmpty(folderInfo)) return;
var structure = JsonConvert.DeserializeObject(folderInfo);
await RecreateFolders(rootPath, structure);
}
private async Task RecreateFolders(string currentPath, FolderNode node)
{
var fullPath = Path.Combine(currentPath, node.Name);
Directory.CreateDirectory(fullPath);
foreach (var child in node.Children)
{
if (child.IsDirectory)
{
await RecreateFolders(fullPath, child);
}
else
{
await MoveFileToDestination(child.FileId, fullPath, child.Name);
}
}
}
3. 多浏览器兼容方案
// 浏览器兼容层
export function getUploader() {
if (supportsModernUpload()) {
return new ModernUploader();
} else if (supportsFlash()) {
return new FlashUploader();
} else {
throw new Error('Unsupported browser');
}
}
function supportsModernUpload() {
return (
window.File &&
window.FileReader &&
window.FileList &&
window.Blob &&
'slice' in File.prototype
);
}
function supportsFlash() {
try {
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
} catch (e) {
return (
navigator.mimeTypes &&
navigator.mimeTypes['application/x-shockwave-flash'] !== undefined
);
}
}
部署架构
客户端浏览器
│
├─ IE8/国产浏览器 → Flash上传网关 → 文件传输服务
│
└─ 现代浏览器 → 直接连接 → 文件传输服务
│
├─ 文件元数据 → 数据库集群
│
├─ 文件内容 → 华为云OBS/本地存储
│
└─ 加密密钥 → 密钥管理服务
性能优化措施
- 分块并发上传:前端同时上传多个分块,提高传输速度
- 内存优化:后端流式处理,避免大文件内存占用
- 进度持久化:本地存储上传进度,防止页面刷新丢失
- 智能重试机制:网络波动时自动重试失败的分块
- 传输压缩:可选启用LZ4压缩减少传输量
安全措施
- 传输加密:支持SM4/AES加密传输
- 存储加密:文件落地前自动加密
- 完整性校验:每个分块MD5校验
- 访问控制:基于JWT的细粒度权限控制
- 审计日志:记录所有文件传输操作
项目进度规划
- 第一阶段(2周):核心上传下载功能实现
- 第二阶段(1周):文件夹结构保留功能
- 第三阶段(1周):IE8和国产浏览器兼容
- 第四阶段(1周):加密传输和存储集成
- 第五阶段(1周):测试和性能优化
预期成果
- 完全满足客户需求的文件传输解决方案
- 解决现有WebUploader方案的稳定性问题
- 提供良好的用户体验和可靠的数据传输
- 模块化设计便于未来扩展和维护
- 完整的技术文档和API参考
这套方案将彻底解决公司当前面临的大文件传输难题,同时为未来的类似需求提供了可靠的技术基础。
设置框架
目标框架选择8.0

IDE使用VS2022

编译项目

修改测试端口
修改项目测试端口

访问测试页面

NOSQL
NOSQL无需任何配置可直接访问页面进行测试

创建数据库

配置数据库连接信息

检查数据库配置

访问页面进行测试

效果预览
文件上传

文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载完整示例
已经上传到gitee了,可以直接下载

下载完整示例
856

被折叠的 条评论
为什么被折叠?



