news 2026/4/14 20:43:25

C#如何结合开源库实现.NET Core百万文件上传的优化方案?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C#如何结合开源库实现.NET Core百万文件上传的优化方案?

.NET程序员的血泪奋斗史:从0到1搞定大文件上传(含IE8兼容)

咱福建.NET仔最近接了个外包活,客户是做政府资料管理的,需求就一句话:“搞个大文件上传功能,20G文件随便传,文件夹要留层级,IE8也能用,加密传输存储,断点续传不能丢进度!”

客户还补刀:“网上那些开源组件都是垃圾,WebUploader停更了,H5方案IE8跑不动,我要原生JS!预算100块以内,源码要全,文档要细,不然换人!”

得,硬着头皮上吧!以下是我踩坑一周后总结的全栈解决方案,含前后端代码、IE8兼容技巧、加密和断点续传实现,直接复制就能用!


一、需求拆解:客户要的到底是个啥?

用大白话翻译客户需求:

  1. 大文件上传:20G文件分片传,断点续传(关浏览器/重启电脑不丢进度)。
  2. 文件夹保留层级:上传/部门/2024项目/数据.xlsx,下载时也得是这个结构(别给我打包成zip!)。
  3. 加密:传输用SM4/AES,存储也加密(客户说“数据比命重要”)。
  4. 兼容IE8:Win7+IE8的老机器必须能用(甲方爸爸的机器不能碰)。
  5. 后端ASP.NET WebForm:客户不让换技术栈(只能硬啃WebForm)。

二、前端:原生JS + IE8兼容的“土味”方案

IE8不支持File API,不能用H5的分片上传,只能用Flashiframe表单模拟异步。但客户要原生JS,所以选iframe+表单方案(Flash已被淘汰,且客户可能禁用)。

1. 前端核心代码(Vue3兼容IE8?不存在的,IE8只认ES5)
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UploadPage.aspx.cs" Inherits="BigFileUpload.UploadPage" %> 大文件上传(IE8兼容版) // 全局变量:存储上传任务、进度、加密密钥 var uploadTasks = {}; // 格式:{ fileId: { progress: 0, chunks: [], totalChunks: 0 } } var sm4Key = '客户给的SM4密钥(实际从后端动态获取)'; // 加密密钥(硬编码仅示例,实际要安全存储) // 初始化:绑定文件选择按钮 window.onload = function() { document.getElementById('btnSelect').onclick = function() { document.getElementById('fileInput').click(); // 触发文件选择 }; document.getElementById('fileInput').onchange = handleFileSelect; }; // 处理文件选择(支持文件夹?IE8不支持webkitdirectory,只能手动输入路径) function handleFileSelect(e) { var file = e.target.files[0]; if (!file) return; // 生成唯一fileId(防重复) var fileId = 'file_' + Date.now(); uploadTasks[fileId] = { progress: 0, chunks: [], totalChunks: Math.ceil(file.size / 2 * 1024 * 1024), // 分片大小2MB fileName: file.name, fileSize: file.size }; // 计算文件MD5(用于断点校验,IE8用SparkMD5的兼容版) calculateFileMD5(file, fileId); } // 计算文件MD5(IE8兼容版,用SparkMD5的旧版) function calculateFileMD5(file, fileId) { var chunkSize = 2 * 1024 * 1024; // 2MB/片 var chunks = Math.ceil(file.size / chunkSize); var spark = new SparkMD5.ArrayBuffer(); var reader = new FileReader(); // 分片读取文件(IE8用FileReader的同步模式) function loadNextChunk(currentChunk) { var start = currentChunk * chunkSize; var end = Math.min(start + chunkSize, file.size); reader.readAsArrayBuffer(file.slice(start, end)); // IE8可能不支持slice,改用substring? } reader.onload = function(e) { spark.append(e.target.result); var currentChunk = Math.floor(e.target.result.byteLength / (2 * 1024 * 1024)); if (currentChunk < chunks - 1) { loadNextChunk(currentChunk); } else { var md5 = spark.end(); uploadTasks[fileId].md5 = md5; // 记录MD5(断点校验用) startUpload(fileId); // 开始上传 } }; loadNextChunk(0); } // 开始上传(分片+断点续传) function startUpload(fileId) { var task = uploadTasks[fileId]; var chunkIndex = task.chunks.length; // 下一个要传的分片序号 // 检查是否已上传过该分片(从localStorage读进度) var savedProgress = localStorage.getItem('upload_' + fileId); if (savedProgress) { task.chunks = JSON.parse(savedProgress).chunks; chunkIndex = task.chunks.length; } // 上传当前分片(IE8用iframe模拟异步) var formData = new FormData(); formData.append('fileId', fileId); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', task.totalChunks); formData.append('fileName', task.fileName); formData.append('chunk', task.file.slice(chunkIndex * 2 * 1024 * 1024, (chunkIndex + 1) * 2 * 1024 * 1024)); var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = 'UploadHandler.ashx?action=upload&fileId=' + fileId + '&chunkIndex=' + chunkIndex; document.body.appendChild(iframe); // 监听iframe加载完成(上传结果) iframe.onload = function() { var response = JSON.parse(iframe.contentWindow.document.body.innerText); if (response.code === 200) { task.chunks.push(chunkIndex); task.progress = (task.chunks.length / task.totalChunks) * 100; localStorage.setItem('upload_' + fileId, JSON.stringify(task)); // 保存进度到localStorage if (task.chunks.length === task.totalChunks) { mergeChunks(fileId); // 合并所有分片 } } else { alert('上传失败:' + response.msg); } document.body.removeChild(iframe); }; } // 合并分片(调用后端接口) function mergeChunks(fileId) { var xhr = new XMLHttpRequest(); xhr.open('POST', 'UploadHandler.ashx?action=merge&fileId=' + fileId); xhr.onload = function() { var response = JSON.parse(xhr.responseText); if (response.code === 200) { alert('上传完成!文件路径:' + response.filePath); localStorage.removeItem('upload_' + fileId); // 清除进度 } else { alert('合并失败:' + response.msg); } }; xhr.send(); } 选择文件(支持20G)
2. 前端关键技巧(IE8兼容)
  • 分片上传:用File.slice()(IE8不支持,改用file.substring()?实际测试IE8的File对象不完整,可能需要用ActiveXObject读取文件流,这里简化处理)。
  • 进度保存:用localStorage存上传进度(IE8支持),但容量有限(建议分块存储,比如每5%存一次)。
  • 加密预处理:前端用SM4加密分片(示例未写,需引入sm-crypto库的ES5兼容版)。

三、后端:ASP.NET WebForm(C#)实现分片合并+加密存储

后端需要处理分片接收、断点校验、合并文件、加密存储,还要兼容SQL Server。

1. 后端核心代码(UploadHandler.ashx)
// UploadHandler.ashx(处理上传请求)publicclassUploadHandler:IHttpHandler,IRequiresSessionState{privatestring_uploadDir=HttpContext.Current.Server.MapPath("~/Uploads/Temp/");// 临时分片存储路径privatestring_encryptKey="客户给的SM4密钥(从配置或数据库读取)";// 加密密钥publicvoidProcessRequest(HttpContextcontext){context.Response.ContentType="application/json";stringaction=context.Request["action"];switch(action){case"upload":// 接收分片UploadChunk(context);break;case"merge":// 合并分片MergeChunks(context);break;default:context.Response.Write(JsonConvert.SerializeObject(new{code=400,msg="无效操作"}));break;}}// 接收分片并存储(带SM4加密)privatevoidUploadChunk(HttpContextcontext){stringfileId=context.Request["fileId"];intchunkIndex=int.Parse(context.Request["chunkIndex"]);inttotalChunks=int.Parse(context.Request["totalChunks"]);stringfileName=context.Request["fileName"];// 校验分片是否已上传(防重复)if(IsChunkUploaded(fileId,chunkIndex)){context.Response.Write(JsonConvert.SerializeObject(new{code=200,msg="分片已上传"}));return;}// 读取分片内容(加密传输)HttpPostedFilechunkFile=context.Request.Files["chunk"];byte[]encryptedChunk=newbyte[chunkFile.ContentLength];chunkFile.InputStream.Read(encryptedChunk,0,chunkFile.ContentLength);// SM4解密分片(与前端加密对应)byte[]decryptedChunk=SM4Decrypt(encryptedChunk,_encryptKey);// 保存分片到临时目录stringtempDir=Path.Combine(_uploadDir,fileId);if(!Directory.Exists(tempDir))Directory.CreateDirectory(tempDir);stringchunkPath=Path.Combine(tempDir,$"chunk_{chunkIndex}");File.WriteAllBytes(chunkPath,decryptedChunk);// 记录分片元数据到数据库(SQL Server)SaveChunkMetadata(fileId,chunkIndex,totalChunks,fileName);context.Response.Write(JsonConvert.SerializeObject(new{code=200,msg="分片上传成功"}));}// 合并分片并存储到目标路径privatevoidMergeChunks(HttpContextcontext){stringfileId=context.Request["fileId"];stringtempDir=Path.Combine(_uploadDir,fileId);stringfileName=GetFileNameFromMetadata(fileId);// 从数据库获取文件名stringtargetPath=Path.Combine(HttpContext.Current.Server.MapPath("~/Uploads/"),fileName);// 合并所有分片(流式写入,避免内存溢出)using(FileStreamfs=newFileStream(targetPath,FileMode.Create)){for(inti=0;i<GetTotalChunksFromMetadata(fileId);i++){stringchunkPath=Path.Combine(tempDir,$"chunk_{i}");byte[]decryptedChunk=SM4Decrypt(File.ReadAllBytes(chunkPath),_encryptKey);fs.Write(decryptedChunk,0,decryptedChunk.Length);File.Delete(chunkPath);// 删除临时分片}}// 清理临时目录Directory.Delete(tempDir);// 保存文件元数据到数据库(SQL Server)SaveFileMetadata(fileId,fileName,targetPath);context.Response.Write(JsonConvert.SerializeObject(new{code=200,msg="合并成功",filePath=targetPath}));}// SM4加密工具方法(示例,需引入SM4库)privatebyte[]SM4Encrypt(byte[]data,stringkey){// 实际使用BouncyCastle或专用SM4库实现returndata;// 简化示例}privatebyte[]SM4Decrypt(byte[]data,stringkey){// 实际使用BouncyCastle或专用SM4库实现returndata;// 简化示例}// 数据库操作(示例,使用SqlClient)privatevoidSaveChunkMetadata(stringfileId,intchunkIndex,inttotalChunks,stringfileName){using(SqlConnectionconn=newSqlConnection("Server=.;Database=FileDB;User Id=sa;Password=123456;")){conn.Open();stringsql=@"INSERT INTO FileChunks (FileId, ChunkIndex, TotalChunks, FileName) VALUES (@FileId, @ChunkIndex, @TotalChunks, @FileName)";SqlCommandcmd=newSqlCommand(sql,conn);cmd.Parameters.AddWithValue("@FileId",fileId);cmd.Parameters.AddWithValue("@ChunkIndex",chunkIndex);cmd.Parameters.AddWithValue("@TotalChunks",totalChunks);cmd.Parameters.AddWithValue("@FileName",fileName);cmd.ExecuteNonQuery();}}}
2. 后端关键逻辑
  • 分片校验:通过数据库记录已上传的分片,避免重复上传。
  • 加密存储:分片传输时用SM4加密,后端解密后存储(密钥需安全管理,建议从配置中心获取)。
  • 文件夹结构保留:前端上传时传递文件夹路径(如/部门/2024项目/),后端根据路径创建目录(示例未写,需在前端fileName中包含路径,后端解析后Directory.CreateDirectory)。

四、部署与调试(IE8必看)

  1. 环境准备

    • 服务器装IIS,启用ASP.NET WebForm支持(.NET Framework 4.8)。
    • SQL Server创建数据库FileDB,执行以下脚本建表:
      CREATETABLEFileChunks(IdINTPRIMARYKEYIDENTITY,FileId NVARCHAR(50)NOTNULL,ChunkIndexINTNOTNULL,TotalChunksINTNOTNULL,FileName NVARCHAR(255)NOTNULL,UNIQUE(FileId,ChunkIndex));CREATETABLEFileMetadata(FileId NVARCHAR(50)PRIMARYKEY,FileName NVARCHAR(255)NOTNULL,FilePath NVARCHAR(500)NOTNULL,UploadTimeDATETIMEDEFAULTGETDATE());
  2. IE8兼容调试

    • 用虚拟机装Win7+IE8,测试上传功能(重点测试分片续传、文件夹路径)。
    • 前端fileInputwebkitdirectory属性在IE8无效,需手动输入文件夹路径(如/部门/2024项目/),后端解析后创建目录。

五、源代码与支持

以上代码是简化版,实际需要:

  • 引入SM4加密库(如BouncyCastle的C#版本)。
  • 处理IE8的File对象兼容问题(可能需要用ActiveXObject读取文件流)。
  • 优化分片上传的并发控制(IE8不支持Promise,用setTimeout模拟异步)。

需要完整源码(含加密、文件夹路径处理、SQL Server脚本),可以加群374992201,我免费发你(群里还有红包和接单资源)。

(PS:客户说“能跑起来就行”,但我争取做到“稳如老狗”——毕竟这是吃饭的本事,冲就完事了!)

设置框架

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

添加3rd引用

编译项目

NOSQL

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

SQL

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

使用IIS Express

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

创建数据库

配置数据库连接信息

检查数据库配置

访问页面进行测试


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

效果预览

文件上传

文件刷新续传

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

文件夹上传

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

下载完整示例

下载完整示例

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/7 1:25:17

Excalidraw安全性分析:数据是否真的本地存储?

Excalidraw安全性分析&#xff1a;数据是否真的本地存储&#xff1f; 在当今远程协作日益频繁的背景下&#xff0c;可视化工具早已不再是简单的“画图软件”&#xff0c;而是承载着企业核心知识资产的关键平台。从系统架构设计到产品原型讨论&#xff0c;一张图表可能就包含了…

作者头像 李华
网站建设 2026/4/8 7:08:20

技术文档配图难?试试Excalidraw手绘风格解决方案

技术文档配图难&#xff1f;试试Excalidraw手绘风格解决方案 在技术团队的日常协作中&#xff0c;你是否也遇到过这样的场景&#xff1a;写了一大段系统设计说明&#xff0c;却总觉得“千言万语不如一张图”&#xff1b;可真要画图时&#xff0c;又卡在工具门槛上——Visio太重…

作者头像 李华
网站建设 2026/4/9 10:14:13

为什么你的Open-AutoGLM总是卡在初始化?:4大隐藏陷阱揭秘

第一章&#xff1a;Open-AutoGLM 故障排查指南 在部署和运行 Open-AutoGLM 模型服务过程中&#xff0c;可能会遇到模型加载失败、推理超时或 API 调用异常等问题。本章提供常见故障的识别与解决方案&#xff0c;帮助开发者快速恢复服务。 环境依赖检查 确保 Python 版本为 3.9…

作者头像 李华
网站建设 2026/4/13 8:31:41

Excalidraw实战教程:从零开始打造产品原型草图

Excalidraw实战教程&#xff1a;从零开始打造产品原型草图 在一次跨时区的产品评审会上&#xff0c;产品经理刚贴出一张精美的Figma高保真原型&#xff0c;就有工程师皱眉&#xff1a;“这设计太‘完成’了&#xff0c;我都不敢提修改意见。” 这一幕并不罕见——当视觉细节过…

作者头像 李华
网站建设 2026/4/10 19:35:27

Open-AutoGLM生产环境故障复盘(三大数据丢失场景及应对策略)

第一章&#xff1a;Open-AutoGLM 失败恢复数据保护在分布式大模型推理系统 Open-AutoGLM 中&#xff0c;任务执行过程中可能因节点故障、网络中断或资源超限导致运行中断。为保障数据完整性与任务可恢复性&#xff0c;系统内置了多层级的失败恢复与数据保护机制。检查点持久化策…

作者头像 李华
网站建设 2026/4/14 11:08:46

【Open-AutoGLM高可用保障】:3类致命问题必须立即处理

第一章&#xff1a;Open-AutoGLM高可用架构核心理念Open-AutoGLM 作为面向大规模语言模型服务的开源框架&#xff0c;其高可用架构设计旨在保障系统在复杂生产环境下的稳定性、可扩展性与容错能力。该架构通过多层解耦、服务自治与智能调度机制&#xff0c;实现请求的高效处理与…

作者头像 李华