楼主: clj12378
51 0

C#如何实现大文件上传的进度监控? [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-6-5
最后登录
2018-6-5

楼主
clj12378 发表于 2025-12-12 13:46:02 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

作为武汉光谷软件园里一名40岁的C#开发工程师,我日常不仅要能写WebAPI,还得顺手修好卡纸的打印机。但这次面对“支持2G文件批量上传”的需求时,我还是忍不住嘬了一口热干面汤,额头上渗出的汗顺着T恤领口滑下——这不仅仅是个技术问题,更像是一场三重渡劫:Vue2的兼容性难题、SQL Server的性能瓶颈,以及那个早已年久失修的WebUploader组件。

它此刻正瘫在Chrome控制台里,像个被热干面汤泡胀的筷子,软绵无力。

第一幕:WebUploader的“热干面式崩溃”

“动了!进度条到99%了!”我激动地指着屏幕,话音未落,标签页瞬间白屏。这个由百度开源的上传组件,表现得越来越像一位行为艺术家:

  • 分片上传:功能看似正常,却偶尔会把第13片数据误传至汉口火车站——后来排查发现是Nginx配置了单请求最大2M限制所致
  • 跨浏览器兼容:在360安全浏览器上演“进度条卡顿魔术”,QQ浏览器则直接显示“NaN%”,仿佛在嘲讽用户的耐心
  • 断点续传:客户一旦重启路由器,所有已上传分片便集体“消失在江汉路”,再也无法找回
client_max_body_size

而它的错误处理机制更是令人哭笑不得:

// 前端代码(带武昌口音注释版)
uploader.on('error', function(type) {
    if(type === 'F_DUPLICATE') {
        alert('文件已存在,但后端可能没收到消息,就像你喊服务员加豆浆他装作没听见');
    } else {
        console.log('出错了,但我不知道错在哪,就像问路时对方只回你一句"往那头走"');
    }
});
    

第二幕:.NET Core与Vue2的“长江大桥式沟通”

“前端说要实时显示上传速度!”我一边啃着周黑鸭鸭脖,一边对着电话大吼。电话那头的后端同事声音透着绝望——其实那是我自己,不过是另一个身份下的我。

“哥,WebUploader的文档比东湖还深,根本捞不到重点。”

于是我们开启了一场量子纠缠式的协作开发:

// 后端接口(第五杯碧螺春后的产物)
[HttpPost("upload-chunk")]
public async Task UploadChunk(IFormFile file, string fileHash, int chunkIndex)
{
    try {
        var chunkPath = Path.Combine("uploads", fileHash, $"{chunkIndex}.part");
        await using var stream = new FileStream(chunkPath, FileMode.Create);
        await file.CopyToAsync(stream); // 可能抛出“神秘异常”,如同长江突涨潮水
        return Ok(new { success = true }); // 实际未必成功,就像你以为抢到了粮票,结果是去年的
    } catch {
        return StatusCode(500, "服务器说它想静静,就像你老婆说'我没事'");
    }
}
    

前端这边也不轻松:

// Vue2调用逻辑(掺杂汉口话风格注释)
uploadChunk(chunk) {
    const formData = new FormData();
    formData.append('file', chunk.file);
    formData.append('fileHash', this.fileHash);
    formData.append('chunkIndex', chunk.index);
    axios.post('/api/upload-chunk', formData, {
        onUploadProgress: () => {
            // 此回调随机触发三次,就像公交司机说"马上到站",然后又开过三站
        }
    });
}
    

第三幕:SQL Server的“户部巷式拥堵”

当客户随口补了一句:“能不能查一下历史上传记录?”我望着那台仅配8G内存的云服务器,陷入了长久沉默。

最初的数据库设计天真得如同以为周末的光谷步行街不会堵车:

-- 初始建表语句(理想主义版本)
CREATE TABLE UploadTasks (
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    FileName NVARCHAR(255),
    FileSize BIGINT,          -- 一个2G文件就是2147483648字节
    Status INT,               -- 0=上传中 1=完成 2=失败 3=合并中...
    CreatedAt DATETIME2,
    -- 省略五个关联表的设计,就像省略热干面里的萝卜丁
);
    
FileHash

测试一跑,问题接踵而至:

  • 插入1000条记录后,查询“进行中的任务”耗时高达2.8秒——堪比等待643路公交车进站
  • 关键字段未添加索引?别问我怎么知道的,就像别问我为何总是错过末班地铁
  • Nginx因超时断开连接时,.NET Core仍在默默写入分片日志——就像你举着手机满街找信号,对方早已挂断
FileStream

最致命的是文件合并阶段,系统资源瞬间飙高,磁盘I/O如同户部巷早高峰般堵塞不堪。

设置框架与环境准备

为确保系统稳定运行,首先需安装 .NET Framework 4.7.2 版本。该版本兼容性强,适合处理高并发场景下的文件操作。

下载地址:
https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472

在项目中正确配置目标框架为 4.7.2,以支持后续的异步处理和内存映射功能。

项目构建流程

完成框架设定后,引入必要的第三方库引用,包括用于数据库交互、缓存管理及后台任务调度的核心组件。

随后对整个解决方案执行编译操作,确保所有依赖项无误加载,代码逻辑通过静态检查。

存储方案选择:NOSQL 与 SQL 对比

NOSQL 方案:
无需额外配置即可直接访问页面进行测试,适用于快速验证上传流程的基础功能。

SQL 方案:
推荐结合 IIS 部署使用,尤其在进行大文件上传测试时,可显著提升服务响应性能与连接稳定性。

前端重构计划(汉味优化版)

原使用的 WebUploader 存在兼容性问题且缺乏活跃维护,决定替换为更现代的上传组件:

uppy.io

新组件文档更新至 2024 年,相比某些多年未变的武汉公交时刻表更具可信度。

实现核心功能增强:

  • 断点续传机制: 利用 IndexedDB 在客户端本地保存已上传分片信息,以下方代码为例:
const dbPromise = idb.open('UploadDB', 1, upgradeDB => {
    upgradeDB.createObjectStore('chunks', { keyPath: 'id' }); // 就像给每片鸭脖编号
});
  • 心跳检测: 防止浏览器长时间无响应,类比于防止公交司机中途打盹。
  • 实时速度反馈: 借助 WebSocket 技术动态展示上传速率。虽然 .NET Core 的 SignalR 功能更强大,但学习成本过高,如同掌握武汉话中的儿化音一样困难。

后端自救策略(东湖特供方案)

针对大文件处理瓶颈,采用先进文件处理方式替代传统流式读取:

MemoryMappedFile

取代原有普通文件操作接口

FileStream
,有效降低内存峰值占用。

关键优化点如下:

  • 内存映射文件技术: 合并分片时不将全部数据载入内存,避免“一口气喝完整碗热干面汤”式的资源耗尽。
using (var mmf = MemoryMappedFile.CreateFromFile("final.dat", FileMode.Create)) {
    for (int i = 0; i < totalChunks; i++) {
        var chunkPath = Path.Combine("uploads", fileHash, $"{i}.part");
        // 内存映射文件操作...(就像分批次过长江大桥)
    }
}
  • 速率限制中间件: 控制客户端上传频率,防止突发流量冲击系统。
app.Use(async (context, next) => {
    var clientIp = context.Connection.RemoteIpAddress;
    var rateLimitKey = $"upload:{clientIp}";
    if (redis.Increment(rateLimitKey) > 100) {
        context.Response.StatusCode = 429;
        await context.Response.WriteAsync("慢点,兄弟!武汉话叫'莫慌'!");
        return;
    }
    await next();
});

该机制类似于早市摊主不能同时煮超过一定数量的热干面,保障整体秩序。

  • 异步合并任务: 使用 Hangfire 调度后台作业,实现非阻塞式文件整合。
_backgroundJobClient.Schedule(
    () => MergeFile(fileHash),
    TimeSpan.FromMinutes(1) // 延迟一分钟执行,留给前端充分传输时间,类似等车时先抽根烟
);

此举相当于让外卖小哥优先配送其他订单,提升用户体验。

数据库结构优化(户部巷实施策略)

元数据存储改用 SQL Server 的特定特性来提升效率:

FILESTREAM

如同将鸭脖进行真空包装,提高保存安全性与检索效率。

  • Redis 缓存机制: 实时缓存当前上传任务状态,便于快速查询,类比于通过美团查看哪家热干面排队人少。
  • 软删除策略: 删除标记仅作记录,不物理清除,防止用户反悔操作,正如母亲常说:“这碗面你吃不吃?不吃我倒了”,但实际上并不会真的倒掉。
  • 分表设计: 按年度拆分上传任务表,提升查询性能与维护便利性。
-- 按年份分表(就像把不同季节的衣服分开放)
CREATE TABLE UploadTasks_2024 (
    -- 结构同主表
);

最终测试阶段(光谷限定实测)

当客户提交首个正式测试文件时,系统监控呈现如下状态:

  • IIS 错误日志: 每分钟新增 5 条 "Connection_Abandoned_By_ReqQueue" 记录,如同每分钟有五名乘客在光谷广场迷路。
  • .NET Core 内存占用: 突破 1.8G,仿佛试图把整个户部巷的小吃都塞进背包。
  • SQL Server 慢查询日志: 大量出现特定语句
    SELECT * FROM UploadTasks WHERE Status=0
    ,犹如周末江汉路人满为患地寻找厕所。

然而,在那个 2.1G 的《武汉城市宣传片》终于显示“上传成功”时,我激动得将啃完的鸭脖骨头卡进了键盘缝隙——至少服务器没有宕机,只是全办公室的鼠标变得黏糊糊的,堪比光谷步行街被踩过无数遍的地砖。

客户反馈称:“IE11 下进度条会自动播放《龙船调》。”
我的回应是微笑.jpg,并默默在 Nginx 配置中添加一行规则:

if ($http_user_agent ~* "MSIE") { return 403; }

这一举动,宛如武汉公交司机面对问路者时的经典回答:“往那头走,莫问我”。

进行小文件上传测试时,可采用 IIS Express 作为运行环境,便于快速部署和调试。

接下来需要创建数据库,用于存储上传过程中产生的相关信息,如上传状态、进度记录等。

数据库创建完成后,需配置相应的连接信息,确保应用程序能够正确访问数据库。

配置完毕后,应仔细检查数据库的连接设置是否准确无误,避免因配置错误导致上传功能异常。

完成数据库验证后,可通过浏览器访问指定页面,对文件上传功能进行全面测试。

功能特性说明

文件上传与保存位置
上传的文件将按设定规则存储在服务器指定目录中,支持查看实际保存路径。

效果预览
提供实时上传效果展示,便于开发者或用户确认上传结果。

断点续传(刷新/离线)
具备断点续传能力,即使在浏览器关闭、页面刷新等情况下,已记录的上传进度也不会丢失,可在恢复后继续上传任务。

文件夹上传及结构保留
支持整个文件夹的上传操作,并能保持原有的目录层级结构。同时,上传进度支持离线持久化存储,即便重启系统或关闭页面,仍可恢复上传状态。

完整示例代码可供下载,包含全部功能实现细节,方便本地部署与二次开发。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:如何实现 connection Background Framework Microsoft

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-20 08:55