大文件传输系统技术方案

作为江苏某软件公司的技术负责人,在处理公司产品部门提出的大文件传输需求时,我经过详细调研和评估,提出以下技术方案:

需求分析与技术挑战

当前需求面临的主要技术挑战包括:

  1. 超大文件传输:50GB以上文件的高效可靠传输
  2. 断点续传稳定性:浏览器刷新/关闭后不丢失进度
  3. 文件夹结构保留:完整保持原始文件夹层级关系
  4. 多平台兼容性:支持Windows 7+IE8等老旧环境
  5. 系统集成要求:与现有.NET Core+Vue3技术栈无缝整合

技术选型与架构设计

后端架构 (.NET Core)

// 文件分片上传控制器示例
[ApiController]
[Route("api/upload")]
public class FileUploadController : ControllerBase
{
    private readonly IFileService _fileService;
    
    public FileUploadController(IFileService fileService)
    {
        _fileService = fileService;
    }
    
    [HttpPost("init")]
    public async Task InitUpload([FromBody] UploadInitRequest request)
    {
        // 初始化上传会话,生成唯一ID
        var session = await _fileService.InitUploadSessionAsync(
            request.FileName,
            request.FileSize,
            request.FolderStructure);
            
        return Ok(new {
            sessionId = session.Id,
            chunkSize = session.ChunkSize
        });
    }
    
    [HttpPost("chunk/{sessionId}")]
    public async Task UploadChunk(
        string sessionId,
        [FromForm] IFormFile chunk,
        [FromForm] int chunkIndex)
    {
        await _fileService.SaveChunkAsync(
            sessionId,
            chunkIndex,
            chunk.OpenReadStream());
            
        return Ok();
    }
    
    [HttpPost("complete/{sessionId}")]
    public async Task CompleteUpload(string sessionId)
    {
        var fileInfo = await _fileService.CompleteUploadAsync(sessionId);
        return Ok(fileInfo);
    }
}

前端实现 (Vue3)

// 文件上传组件示例
export default {
  data() {
    return {
      fileList: [],
      uploadSessions: {},
      chunkSize: 5 * 1024 * 1024 // 5MB分片
    }
  },
  methods: {
    async handleUpload(files) {
      for (const file of files) {
        const sessionId = await this.initUploadSession(file)
        this.uploadSessions[sessionId] = {
          file,
          uploadedChunks: new Set(),
          sessionId
        }
        await this.uploadFileInChunks(sessionId)
      }
    },
    
    async initUploadSession(file) {
      const response = await api.post('/api/upload/init', {
        fileName: file.name,
        fileSize: file.size,
        folderStructure: file.webkitRelativePath || ''
      })
      return response.data.sessionId
    },
    
    async uploadFileInChunks(sessionId) {
      const session = this.uploadSessions[sessionId]
      const chunkCount = Math.ceil(session.file.size / this.chunkSize)
      
      // 恢复已上传的分片
      const progress = await this.checkUploadProgress(sessionId)
      session.uploadedChunks = new Set(progress.uploadedChunks)
      
      for (let i = 0; i < chunkCount; i++) {
        if (!session.uploadedChunks.has(i)) {
          await this.uploadChunk(session, i)
          session.uploadedChunks.add(i)
          // 保存进度到本地存储
          this.saveProgressToStorage(sessionId, Array.from(session.uploadedChunks))
        }
      }
      
      await this.completeUpload(sessionId)
    },
    
    async uploadChunk(session, chunkIndex) {
      const start = chunkIndex * this.chunkSize
      const end = Math.min(start + this.chunkSize, session.file.size)
      const chunkBlob = session.file.slice(start, end)
      
      const formData = new FormData()
      formData.append('chunk', chunkBlob, `chunk-${chunkIndex}`)
      formData.append('chunkIndex', chunkIndex)
      
      await api.post(`/api/upload/chunk/${session.sessionId}`, formData, {
        onUploadProgress: progressEvent => {
          const percent = Math.round(
            (progressEvent.loaded / progressEvent.total) * 100
          )
          this.$emit('progress', {
            sessionId: session.sessionId,
            chunkIndex,
            percent
          })
        }
      })
    }
  }
}

核心技术实现

1. 断点续传持久化方案

// 断点续传状态存储服务
public class UploadSessionService : IUploadSessionService
{
    private readonly AppDbContext _dbContext;
    
    public UploadSessionService(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    
    public async Task InitSessionAsync(
        string fileName, 
        long fileSize,
        string folderPath)
    {
        var session = new UploadSession
        {
            Id = Guid.NewGuid().ToString(),
            FileName = fileName,
            FileSize = fileSize,
            FolderPath = folderPath,
            ChunkSize = 5 * 1024 * 1024, // 5MB
            Status = UploadStatus.InProgress,
            CreatedAt = DateTime.UtcNow,
            UploadedChunks = new List()
        };
        
        _dbContext.UploadSessions.Add(session);
        await _dbContext.SaveChangesAsync();
        
        return session;
    }
    
    public async Task SaveChunkAsync(string sessionId, int chunkIndex, Stream chunkStream)
    {
        // 1. 将分片保存到临时存储 (OSS/本地)
        var tempPath = await SaveChunkToTempStorage(sessionId, chunkIndex, chunkStream);
        
        // 2. 更新数据库记录
        var session = await _dbContext.UploadSessions.FindAsync(sessionId);
        if (!session.UploadedChunks.Contains(chunkIndex))
        {
            session.UploadedChunks.Add(chunkIndex);
            await _dbContext.SaveChangesAsync();
        }
    }
    
    public async Task CheckChunkExistsAsync(string sessionId, int chunkIndex)
    {
        var session = await _dbContext.UploadSessions.FindAsync(sessionId);
        return session?.UploadedChunks.Contains(chunkIndex) ?? false;
    }
}

2. 文件夹结构处理

// 文件夹上传处理器
public class FolderUploadProcessor
{
    public async Task ProcessFolderUpload(
        string sessionId, 
        string tempFolderPath,
        string targetFolderPath)
    {
        // 从数据库获取上传会话信息
        var session = await _sessionService.GetSessionAsync(sessionId);
        
        // 重建文件夹结构
        string fullTargetPath = Path.Combine(targetFolderPath, session.FolderPath);
        Directory.CreateDirectory(fullTargetPath);
        
        // 合并所有分片到最终文件
        string tempFilePath = Path.Combine(tempFolderPath, sessionId, session.FileName);
        string finalFilePath = Path.Combine(fullTargetPath, session.FileName);
        
        await MergeChunksToFinalFile(tempFilePath, finalFilePath);
        
        // 更新会话状态为已完成
        await _sessionService.CompleteSessionAsync(sessionId, finalFilePath);
    }
}

兼容性处理方案

IE8兼容性适配

// IE8兼容的XHR上传封装
function uploadChunkIE8(sessionId, chunkIndex, blob, callback) {
  var xhr = new XDomainRequest(); // IE8特定对象
  var formData = new FormData();
  
  formData.append('chunk', blob);
  formData.append('chunkIndex', chunkIndex);
  
  xhr.open('POST', '/api/upload/chunk/' + sessionId);
  xhr.onload = function() {
    callback(null, xhr.responseText);
  };
  xhr.onerror = function() {
    callback(new Error('Upload failed'));
  };
  
  // IE8特殊处理 - 需要将FormData转为字符串
  var boundary = '----' + new Date().getTime();
  xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
  
  var body = '';
  for (var pair of formData.entries()) {
    body += '--' + boundary + '\r\n';
    body += 'Content-Disposition: form-data; name="' + pair[0] + '"';
    
    if (typeof pair[1] === 'object') {
      body += '; filename="' + pair[1].name + '"\r\n';
      body += 'Content-Type: application/octet-stream\r\n\r\n';
      body += pair[1].getAsBinary() + '\r\n';
    } else {
      body += '\r\n\r\n' + pair[1] + '\r\n';
    }
  }
  
  body += '--' + boundary + '--';
  xhr.send(body);
}

部署架构建议

混合云部署方案

                           +-----------------------+
                           |      Load Balancer    |
                           +-----------+-----------+
                                       |
               +-----------------------+-----------------------+
               |                                               |
+--------------+-----------+                     +-------------+-------------+
|   Public Cloud (OSS)     |                     |   Private Data Center     |
|                          |                     |                           |
|  +---------------------+ |                     |  +---------------------+ |
|  |   Frontend Servers  | |                     |  |   Frontend Servers  | |
|  +----------+----------+ |                     |  +----------+----------+ |
|             |            |                     |             |            |
|  +----------+----------+ |                     |  +----------+----------+ |
|  |  API Gateway (K8s)  +<----------------------->+  API Gateway (K8s)  | |
|  +----------+----------+ |                     |  +----------+----------+ |
|             |            |                     |             |            |
|  +----------+----------+ |                     |  +----------+----------+ |
|  | Upload Microservice | |                     |  | Upload Microservice | |
|  +----------+----------+ |                     |  +----------+----------+ |
|             |            |                     |             |            |
|  +----------+----------+ |                     |  +----------+----------+ |
|  |    Database (SQL)   | |                     |  |    Database (SQL)   | |
|  +---------------------+ |                     |  +---------------------+ |
|                          |                     |                           |
+--------------------------+                     +---------------------------+

商务建议

基于公司每年100+项目的规模,建议采用技术授权买断模式:

  1. 预算控制:80万左右一次性买断费用
  2. 授权范围:公司内部所有项目无限使用
  3. 技术支持:包含5年免费技术支持服务
  4. 升级权益:3年内免费版本升级

这种模式相比单项目授权可节省约60%的长期成本,同时简化采购流程。

实施路线图

  1. 第一阶段 (2周):核心上传/下载功能开发

    • 文件分片上传下载基础功能
    • 基本断点续传支持
  2. 第二阶段 (3周):增强功能开发

    • 文件夹结构处理
    • 浏览器关闭恢复功能
    • 多存储后端支持(OSS/SQL/本地)
  3. 第三阶段 (1周):兼容性测试与优化

    • 全平台浏览器测试
    • Windows 7+IE8专项优化
  4. 第四阶段 (1周):系统集成与部署

    • 现有系统对接
    • 混合部署方案实施

总开发周期约7周,可满足业务部门的时间要求。

设置框架

目标框架选择8.0
Alt

IDE使用VS2022

Visual Studio 2022

编译项目

编译项目

修改测试端口

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

访问测试页面

访问测试页面

NOSQL

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

创建数据库

数据库脚本

配置数据库连接信息

数据库连接

检查数据库配置

检查数据库连接状态

访问页面进行测试

访问测试页面

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载完整示例

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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐