一个被20G文件折磨的.NET程序员的求生指南

“甲方爸爸说要兼容IE8?还要20G文件上传?预算100块?” —— 当我看到需求文档时,手里的泡面突然就不香了…🍜

技术方案:在刀尖上跳舞

前端部分:与IE8老爷爷斗智斗勇

// 文件分片上传核心代码(纯手工打造,兼容IE8版)
function uploadFile(file) {
    // IE8特供版检测
    if (navigator.userAgent.indexOf('MSIE 8.0') > -1) {
        alert('尊敬的用户,检测到您正在使用IE8,我们将为您启用特殊兼容模式...');
        // 其实就是降级方案...
        return ie8SpecialUpload(file);
    }

    const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB一片
    let chunks = Math.ceil(file.size / CHUNK_SIZE);
    let uploaded = 0;
    
    // 加密相关(SM4/AES)
    let crypto = new SuperCrypto('SM4'); // 这个类当然是我编的...
    
    for (let i = 0; i < chunks; i++) {
        let start = i * CHUNK_SIZE;
        let end = Math.min(start + CHUNK_SIZE, file.size);
        let chunk = file.slice(start, end);
        
        // 加密分片
        let encryptedChunk = crypto.encrypt(chunk);
        
        let formData = new FormData();
        formData.append('file', encryptedChunk);
        formData.append('chunkIndex', i);
        formData.append('totalChunks', chunks);
        formData.append('fileName', file.name);
        formData.append('fileSize', file.size);
        
        // 这里应该是AJAX调用,但为了兼容IE8...
        let xhr = new XMLHttpRequest();
        xhr.open('POST', '/api/upload', true);
        xhr.onload = function() {
            uploaded++;
            updateProgress(uploaded / chunks * 100);
            saveProgressToLocalStorage(file.name, uploaded);
        };
        xhr.send(formData);
    }
}

// IE8特供版上传(Flash后备方案)
function ie8SpecialUpload(file) {
    // 这里应该有Flash上传代码...
    alert('请安装Flash Player 10.1或更高版本');
    return false;
}

后端部分:C# WebForm的救赎

// UploadHandler.ashx 文件上传处理
public class UploadHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        try
        {
            // 获取上传的文件分片
            HttpPostedFile file = context.Request.Files["file"];
            int chunkIndex = int.Parse(context.Request["chunkIndex"]);
            int totalChunks = int.Parse(context.Request["totalChunks"]);
            string fileName = context.Request["fileName"];
            long fileSize = long.Parse(context.Request["fileSize"]);
            
            // 解密处理
            byte[] fileData = new byte[file.InputStream.Length];
            file.InputStream.Read(fileData, 0, fileData.Length);
            byte[] decryptedData = CryptoHelper.SM4Decrypt(fileData, "密钥");
            
            // 临时保存分片
            string tempPath = Path.Combine("E:\\UploadTemp", fileName);
            if (!Directory.Exists(tempPath))
                Directory.CreateDirectory(tempPath);
                
            string chunkPath = Path.Combine(tempPath, chunkIndex.ToString());
            File.WriteAllBytes(chunkPath, decryptedData);
            
            // 检查是否所有分片都已上传
            if (Directory.GetFiles(tempPath).Length == totalChunks)
            {
                // 合并文件
                MergeFile(tempPath, "E:\\Uploads\\" + fileName);
                Directory.Delete(tempPath, true);
                
                // 记录到数据库
                SaveToDb(fileName, fileSize, "E:\\Uploads\\" + fileName);
            }
            
            context.Response.Write("{\"status\":\"ok\"}");
        }
        catch (Exception ex)
        {
            context.Response.Write("{\"status\":\"error\",\"message\":\"" + ex.Message + "\"}");
        }
    }
    
    private void MergeFile(string tempPath, string targetPath)
    {
        // 这里应该有合并文件的代码...
    }
    
    private void SaveToDb(string fileName, long fileSize, string filePath)
    {
        // 这里应该有数据库操作代码...
    }
}

开发血泪史

  1. IE8兼容性:我尝试了各种黑魔法,最后发现唯一的解决方案是…建议用户换电脑(当然这话不能跟甲方说)

  2. 20G文件上传:我的开发机只有256G SSD,测试时差点把自己电脑搞崩溃

  3. 加密传输:SM4在JavaScript端的实现让我掉了不少头发

文件夹上传的终极解决方案

// 文件夹上传保留层级结构
function uploadFolder(event) {
    let files = event.target.files;
    let folderStructure = {};
    
    // 构建文件夹结构
    for (let i = 0; i < files.length; i++) {
        let file = files[i];
        if (file.webkitRelativePath) {
            let paths = file.webkitRelativePath.split('/');
            let currentLevel = folderStructure;
            
            for (let j = 0; j < paths.length - 1; j++) {
                if (!currentLevel[paths[j]]) {
                    currentLevel[paths[j]] = {};
                }
                currentLevel = currentLevel[paths[j]];
            }
            
            currentLevel[paths[paths.length - 1]] = file;
        }
    }
    
    // 递归上传
    uploadFolderItem(folderStructure, '');
}

function uploadFolderItem(item, currentPath) {
    for (let key in item) {
        if (item[key] instanceof File) {
            // 上传文件
            uploadFile(item[key], currentPath);
        } else {
            // 创建文件夹
            createFolderOnServer(currentPath + key + '/');
            // 递归上传子项
            uploadFolderItem(item[key], currentPath + key + '/');
        }
    }
}

数据库设计(SQL Server版)

-- 文件记录表
CREATE TABLE FileRecords (
    Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    FileName NVARCHAR(255) NOT NULL,
    FileSize BIGINT NOT NULL,
    FilePath NVARCHAR(MAX) NOT NULL,
    UploadTime DATETIME DEFAULT GETDATE(),
    IsEncrypted BIT DEFAULT 1,
    EncryptionType NVARCHAR(50) DEFAULT 'SM4'
);

-- 上传进度表(用于断点续传)
CREATE TABLE UploadProgress (
    SessionId NVARCHAR(255) PRIMARY KEY,
    FileName NVARCHAR(255) NOT NULL,
    FileMd5 NVARCHAR(255) NOT NULL,
    TotalChunks INT NOT NULL,
    UploadedChunks INT NOT NULL,
    LastUpdateTime DATETIME DEFAULT GETDATE()
);

给甲方爸爸的温馨Tips

  1. 关于IE8:真的不考虑升级一下吗?现在连微软都不支持IE8了…

  2. 关于20G文件:建议上传前先压缩,毕竟您家宽带上传20G可能需要一整天

  3. 关于100块预算:这个价格我们只能提供"心灵支持"服务了…

PS:那个QQ群真的存在,但红包可能已经被抢光了。至于"一个项目提成1万"…醒醒,该搬砖了!👷

设置框架

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

下载完整示例

下载完整示例

Logo

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

更多推荐