分块上传下载时.net如何处理批量大文件?

河南郑州程序员的大文件传输系统开发实战:基于WebUploader的国产化全栈解决方案

一、项目背景与需求分析

1.1 核心需求

  • 大文件传输:支持20GB+文件上传/下载,需分片传输、断点续传。
  • 文件夹结构保留:上传文件夹时需完整保留层级关系(前端递归解析 + 后端重组)。
  • 全浏览器兼容
    • 传统浏览器:IE8+、Chrome、Firefox、Edge
    • 信创浏览器:龙芯浏览器、红莲花浏览器、奇安信安全浏览器
  • 加密传输
    • 国密算法:SM4(需引入第三方库如gmssl.js
    • 国际标准:AES-256(浏览器原生Crypto APIcrypto-js
  • 国产化环境适配
    • 操作系统:统信UOS、中标麒麟、银河麒麟
    • 数据库:达梦、人大金仓(兼容SQL Server语法)
    • 云存储:阿里云OSS、华为云OBS、腾讯云COS、百度云BOS
  • 后端技术栈:.NET Core 3.1/5.0(跨平台兼容Linux/Windows)
  • 前端技术栈:Vue2 + WebUploader(需二次开发支持文件夹上传)

1.2 痛点与挑战

  • WebUploader原生缺陷
    • 仅支持单文件上传,文件夹上传需手动扩展(通过``)
    • 分片逻辑需自行实现(参考File.slice()
  • IE8兼容性
    • 需引入es5-shimjson2.js polyfill
    • 替换PromisejQuery.Deferred
  • 国产化数据库适配
    • 达梦/人大金仓的SQL方言差异(如分页语法LIMIT vs ROW_NUMBER()
  • 加密性能优化
    • 大文件加密需流式处理(避免内存溢出)

二、技术方案设计与实现

2.1 前端实现(Vue2 + WebUploader扩展)

2.1.1 文件夹上传核心代码
// src/components/FileUploader.vue



import WebUploader from 'webuploader';
import CryptoJS from 'crypto-js'; // 或引入SM4库

export default {
  data() {
    return {
      fileList: [],
      uploader: null,
      chunkSize: 5 * 1024 * 1024, // 5MB分片
    };
  },
  methods: {
    handleFolderSelect(e) {
      const files = e.target.files;
      const fileTree = this.parseFolder(files); // 递归解析文件夹结构
      this.fileList = fileTree;
      this.initUploader(fileTree);
    },
    parseFolder(files) {
      const tree = [];
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const pathParts = file.webkitRelativePath.split('/');
        const fileName = pathParts.pop();
        const dirPath = pathParts.join('/') || '/';
        tree.push({
          id: file.lastModified + '-' + file.name,
          path: file.webkitRelativePath,
          name: fileName,
          size: file.size,
          type: file.type,
          dir: dirPath,
        });
      }
      return tree;
    },
    initUploader(fileList) {
      this.uploader = WebUploader.create({
        swf: '/static/Uploader.swf', // IE8兼容
        server: '/api/upload',
        chunked: true,
        chunkSize: this.chunkSize,
        threads: 3,
        formData: {
          encryptType: 'AES', // 或 'SM4'
        },
      });

      fileList.forEach((file) => {
        const reader = new FileReader();
        reader.onload = (e) => {
          // 加密文件内容(示例:AES)
          const encrypted = CryptoJS.AES.encrypt(
            e.target.result,
            'your-secret-key'
          ).toString();
          
          // 模拟分片上传(实际需调用uploader.upload方法)
          this.uploadChunk(file, encrypted, 0);
        };
        reader.readAsArrayBuffer(file); // 或使用slice分片读取
      });
    },
    uploadChunk(file, encryptedData, chunkIndex) {
      // 实际需通过WebUploader的API实现分片上传
      console.log(`Uploading chunk ${chunkIndex} of ${file.path}`);
    },
  },
};

2.1.2 IE8兼容性处理
  • 引入Polyfill:
    
    
    
    
  • 替换fetchjQuery.ajaxaxios(需配置xhr兼容模式)。

2.2 后端实现(.NET Core)

2.2.1 文件分片接收与重组
// Controllers/UploadController.cs
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
    private readonly IWebHostEnvironment _env;
    private readonly IDatabaseService _db; // 抽象数据库操作

    public UploadController(IWebHostEnvironment env, IDatabaseService db)
    {
        _env = env;
        _db = db;
    }

    [HttpPost]
    public async Task Upload()
    {
        var form = await Request.ReadFormAsync();
        var file = form.Files[0];
        var chunkIndex = int.Parse(form["chunkIndex"]);
        var totalChunks = int.Parse(form["totalChunks"]);
        var fileId = form["fileId"];
        var encryptType = form["encryptType"]; // "AES"或"SM4"

        // 临时存储分片
        var tempPath = Path.Combine(_env.WebRootPath, "temp", fileId.ToString());
        Directory.CreateDirectory(tempPath);
        var chunkPath = Path.Combine(tempPath, $"{chunkIndex}.dat");
        using (var stream = new FileStream(chunkPath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }

        // 如果是最后一个分片,则合并文件
        if (chunkIndex == totalChunks - 1)
        {
            var finalPath = Path.Combine(_env.WebRootPath, "uploads", file.FileName);
            MergeChunks(tempPath, finalPath, totalChunks);

            // 解密文件(根据encryptType调用不同算法)
            if (encryptType == "AES")
            {
                DecryptFileAES(finalPath, "your-secret-key");
            }
            else if (encryptType == "SM4")
            {
                // 调用SM4解密库
            }

            // 记录文件元数据到数据库
            await _db.SaveFileRecord(new FileRecord
            {
                Name = file.FileName,
                Path = finalPath,
                Size = file.Length,
                UploadTime = DateTime.Now,
            });

            Directory.Delete(tempPath, true);
            return Ok(new { success = true, path = finalPath });
        }

        return Ok(new { success = true, message = "Chunk uploaded" });
    }

    private void MergeChunks(string tempDir, string outputPath, int totalChunks)
    {
        using (var outputStream = new FileStream(outputPath, FileMode.Create))
        {
            for (int i = 0; i < totalChunks; i++)
            {
                var chunkPath = Path.Combine(tempDir, $"{i}.dat");
                var chunkData = System.IO.File.ReadAllBytes(chunkPath);
                outputStream.Write(chunkData, 0, chunkData.Length);
                System.IO.File.Delete(chunkPath);
            }
        }
    }

    private void DecryptFileAES(string inputPath, string key)
    {
        // 实现AES解密逻辑(需处理大文件流式解密)
    }
}
2.2.2 国产化数据库适配
  • 通过IDatabaseService抽象层隔离数据库差异:
    public interface IDatabaseService
    {
        Task SaveFileRecord(FileRecord record);
        Task> GetFilesByUser(string userId);
    }
    
    // 达梦数据库实现
    public class DamengDatabaseService : IDatabaseService
    {
        private readonly string _connString;
    
        public DamengDatabaseService(IConfiguration config)
        {
            _connString = config["ConnectionStrings:Dameng"];
        }
    
        public async Task SaveFileRecord(FileRecord record)
        {
            using (var conn = new DmConnection(_connString))
            {
                await conn.OpenAsync();
                var cmd = new DmCommand(
                    "INSERT INTO FILES(NAME, PATH, SIZE, UPLOAD_TIME) VALUES(@name, @path, @size, @time)",
                    conn
                );
                cmd.Parameters.AddWithValue("@name", record.Name);
                cmd.Parameters.AddWithValue("@path", record.Path);
                cmd.Parameters.AddWithValue("@size", record.Size);
                cmd.Parameters.AddWithValue("@time", record.UploadTime);
                await cmd.ExecuteNonQueryAsync();
            }
        }
    }
    

三、部署与测试

3.1 国产化环境部署

  • 统信UOS
    # 安装.NET Core运行时
    sudo apt-get install dotnet-sdk-5.0
    # 运行项目
    dotnet run --project YourProject.csproj
    
  • 银河麒麟
    • 需手动安装libgdiplus(用于图像处理)。

3.2 信创浏览器测试

  • 龙芯浏览器
    • 检查webkitdirectory支持情况(可能需降级为Flash上传组件)。
  • 奇安信安全浏览器
    • 启用“兼容模式”以支持IE8级API。

四、寻求社区支持

4.1 现有问题

  1. WebUploader文件夹上传在IE8下失效:需替换为Flash上传组件(如Plupload)。
  2. SM4加密性能瓶颈:大文件解密耗时过长。
  3. 达梦数据库分页查询语法:与SQL Server差异较大。

4.2 加入技术交流群

  • QQ群:374992201(备注“大文件传输开发”)
  • 需求
    • 免费获取完整源码(前端+后端+数据库脚本)
    • 7×24小时技术支持(优先解决国产化环境问题)
    • 联合优化加密与分片传输性能

(完)
P.S.:本项目已实现核心功能,但需进一步测试信创环境兼容性。欢迎大神贡献代码或提供优化建议!

设置框架

安装.NET Framework 4.7.2
https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472
框架选择4.7.2
Alt

添加3rd引用

Alt

编译项目

Alt

NOSQL

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

SQL

使用IIS
大文件上传测试推荐使用IIS以获取更高性能。
Alt

使用IIS Express

小文件上传测试可以使用IIS Express
Alt

创建数据库

Alt

配置数据库连接信息

Alt

检查数据库配置

Alt

访问页面进行测试

Alt
相关参考:
文件保存位置

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载完整示例

下载完整示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值