作为武汉光谷软件园里一名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 作为运行环境,便于快速部署和调试。
接下来需要创建数据库,用于存储上传过程中产生的相关信息,如上传状态、进度记录等。
数据库创建完成后,需配置相应的连接信息,确保应用程序能够正确访问数据库。
配置完毕后,应仔细检查数据库的连接设置是否准确无误,避免因配置错误导致上传功能异常。
完成数据库验证后,可通过浏览器访问指定页面,对文件上传功能进行全面测试。
功能特性说明
文件上传与保存位置
上传的文件将按设定规则存储在服务器指定目录中,支持查看实际保存路径。
效果预览
提供实时上传效果展示,便于开发者或用户确认上传结果。
断点续传(刷新/离线)
具备断点续传能力,即使在浏览器关闭、页面刷新等情况下,已记录的上传进度也不会丢失,可在恢复后继续上传任务。
文件夹上传及结构保留
支持整个文件夹的上传操作,并能保持原有的目录层级结构。同时,上传进度支持离线持久化存储,即便重启系统或关闭页面,仍可恢复上传状态。
完整示例代码可供下载,包含全部功能实现细节,方便本地部署与二次开发。



雷达卡


京公网安备 11010802022788号







