大文件上传系统开发吐槽日记

甲方爸爸的需求清单

作为一个浙江苦逼的.NET程序员,最近接了个外包项目,甲方爸爸的需求让我差点当场表演"程序员暴毙":

  1. 文件传输:要传20G的文件,还得是文件夹(可能包含1000+文件)
  2. 加密要求:传输要SM4/AES加密,存储也要加密
  3. 断点续传:关浏览器、重启电脑都不能丢进度
  4. 兼容性:从IE8到现代浏览器全都要支持
  5. 预算:100块(我寻思这连服务器一天的电费都不够)
  6. 服务:7*24小时免费技术支持+源码+部署一条龙

技术选型困境

前端方案

// 看到IE8兼容要求时的我
function handleIe8Compatibility() {
  if (isIE8()) {
    console.log("建议用户升级浏览器");
    throw new Error("老板说必须兼容,代码还得写");
  }
}

后端存储方案

// 看到20G文件存储时的数据库设计
public class FileStorage
{
    // 甲方:就用SQL Server存文件
    public void Store20GBFile()
    {
        // 我的内心:E盘会不会爆炸?
        var freeSpace = DriveInfo.GetDrives()
                         .First(d => d.Name == "E").TotalFreeSpace;
        if (freeSpace < 20L * 1024 * 1024 * 1024)
        {
            throw new OutOfDiskSpaceException("甲方爸爸,加个硬盘呗?");
        }
    }
}

部分实现代码

前端文件上传核心逻辑

// 基于WebUploader的魔改版,兼容IE8
(function() {
    // IE8 polyfill大礼包
    if (!Array.prototype.forEach) {
        Array.prototype.forEach = function(callback) {
            for (var i = 0; i < this.length; i++) {
                callback(this[i], i, this);
            }
        };
    }
    
    // 文件夹上传逻辑
    function uploadFolder(folder) {
        // 遍历文件夹结构
        traverseFolder(folder, function(file) {
            // 保持相对路径
            var relativePath = getRelativePath(file);
            
            // 分片上传
            uploadFileInChunks(file, relativePath);
        });
    }
    
    // 断点续传实现
    function resumeUpload(fileId) {
        checkServerForProgress(fileId, function(progress) {
            if (progress < 100) {
                console.log("从" + progress + "%继续上传");
                // 继续上传逻辑
            }
        });
    }
})();

后端C#处理代码

// 文件上传处理
public class FileUploadHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        try
        {
            // 获取上传文件
            HttpPostedFile file = context.Request.Files[0];
            string relativePath = context.Request["relativePath"];
            
            // 加密存储
            using (var cryptoStream = new CryptoStream(
                File.Create(Path.Combine("E:\\Uploads", relativePath)),
                new SM4CryptoProvider().CreateEncryptor(),
                CryptoStreamMode.Write))
            {
                file.InputStream.CopyTo(cryptoStream);
            }
            
            // 记录上传进度到数据库
            RecordUploadProgress(context.Request["fileId"], 100);
        }
        catch (Exception ex)
        {
            // 100块预算的异常处理
            context.Response.Write("出错啦!但我们没钱修!");
        }
    }
    
    private void RecordUploadProgress(string fileId, int progress)
    {
        // SQL Server操作
        using (var conn = new SqlConnection("连不起Azure只能用本地..."))
        {
            conn.Open();
            // 省略一万行SQL...
        }
    }
}

开发者的自我修养

  1. 兼容IE8的绝招

    • 准备大量polyfill
    • 放弃使用任何现代CSS特性
    • 把jQuery 1.x刻进DNA
    • 每晚祈祷用户早日升级
  2. 大文件上传秘籍

    // 分片上传伪代码
    while(budget < projectCost) {
        Console.WriteLine("再接个外包补这个窟窿");
        budget += 100; // 每个项目都预算100
    }
    
  3. 加密存储小技巧

    // 前端加密演示
    function encrypt(data) {
        if (budget > 0) {
            return realEncrypt(data); // 真加密
        } else {
            return data; // 预算不足时的"加密"
        }
    }
    

项目总结

这个项目教会了我:

  • 如何用100块预算做出10万块的功能
  • 如何在2023年继续伺候IE8这位"老佛爷"
  • 如何在不睡觉的情况下提供7*24支持
  • 为什么程序员会秃头

最终方案:建议甲方加钱,或者我把QQ群(374992201)里的"大神"们介绍给他,毕竟:

  • 加群送红包
  • 推荐有提成
  • “超级会员直接50%提成”
  • 大家一起用爱发电

(免责声明:以上代码仅供娱乐,实际开发请根据预算调整功能范围)

设置框架

安装.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
相关参考:
文件保存位置

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载示例

下载完整示例

更多推荐