ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

C#实现大文件秒传与断点续传:ASP.NET Core分片上传实战

C#实现大文件秒传与断点续传:ASP.NET Core分片上传实战 在机械制造行业做网页系统一开始我没想到上传文件能成为被吐槽最多的高频问题。车间里传一张DWG图纸、一个STEP装配体动不动就是几百MB老方案一旦遇到多人同时传文件服务器直接卡死。后来我用C#把后端彻底重写前端配合文件指纹与分片逻辑做了一套大文件秒传方案才真正把这块硬骨头啃下来。实际上这套方案上线后重复文件的上传时间从原来的二十多分钟变成了一秒内装配体这种大文件也能稳定断点续传车间老师傅终于不再骂进度条了。这篇内容适合正在做机械行业MES、PLM、图纸管理系统的开发者也适合任何一个被大文件上传折磨过的Web后端或前端同学。我会从原理到C#代码、前端哈希计算、分片合并实现再到机械制造场景的权限、网络适配和排错经验全部摊开来讲。你不需要有很深的框架基础但最好熟悉ASP.NET Core的基础用法因为后面会直接给接口代码。1. 机械制造行业的上传痛点与秒传原理1.1 车间图纸为什么让人崩溃机械制造行业跟互联网行业的文件上传完全不是一个量级。一张AutoCAD图纸可能只有几MB到几十MB但一个三维装配体模型比如SolidWorks的SLDASM或者UG/NX的PRT分分钟超过1GB。更麻烦的是这些东西不是一次传完就结束工程师改一版图就要重新上传装配体里还经常嵌套着几十个零件实际传到服务器的数据量会翻好几倍。另一个让人头疼的问题是车间网络环境。很多机械加工车间的网络并不是干净的办公网有老的屏蔽线、车间级交换机甚至有大功率设备启动时造成的电压波动偶尔还会出现Wi-Fi信号死角。在这种网络下面传统一整包上传的方式几乎等于自杀传了一半断线进度归零又要从头传。我见过最极端的案例一个800MB的CATIA模型传了三个多小时还没成功最后还是用U盘拷到办公室电脑再走内网传的。所以机械行业的上传功能本质上不是做一个能传文件的表单而是要解决三件事重复文件不要浪费时间重新传、大文件断了要能接着传、传输过程要能让普通工人看得懂进度。1.2 秒传的本质文件指纹复用秒传这个词听起来玄乎其实核心就是把一个网络传输问题变成一个数据库查询问题。思路是这样的任何一个文件不管它叫什么名字只要内容确定我们就用MD5或SHA1给它算出一个唯一的指纹。文件内容不同指纹几乎不可能相同。上传之前前端先把文件指纹算出来发给服务器问一句这文件我是不是以前传过服务器在指纹表里一查发现确实存在就直接返回不用传了已经秒传成功然后把当前业务记录关联到那个已存储的文件上。这就好比你去办事大厅办事窗口工作人员用身份证号一查发现系统里已经有你的档案就不用你再交一遍纸质材料了。指纹就是文件的身份证号。实际业务中这个机制对机械行业特别有效。因为工程师出图经常只是小改参数模型整体没有本质变化还有大量标准件库里的通用件这些文件在企业内部会被反复上传到不同项目、不同单据里。只要做过一次指纹记录后面所有关联操作都能秒级完成。1.3 为什么光有秒传还不够如果只是做重复文件秒传那遇到真正的新文件还是得老老实实传体验依然糟糕。所以完整的方案必须是两件事的组合秒传用来处理重复文件分片上传断点续传用来处理全新的大文件。分片上传的原理是把一个完整的文件切成若干个小块比如每块2MB逐块上传。某一块失败了只需要重传那一块已经传好的块不需要动。服务器端收齐所有分片后再按顺序合并成完整文件。这个过程天然抵抗网络抖动也非常适合机械行业那种时好时坏的车间网络。我在这套系统里把分片大小定为2MB内网条件好的企业可以调到5MB甚至10MB但如果外网访问或者车间网络不稳定2MB是更稳妥的选择。分片太小会让HTTP请求数量爆炸分片太大又失去了断点续传的优势这个取舍后面会详细讲。2. 前后端整体方案设计C#后端与浏览器的分工2.1 为什么选C# ASP.NET Core机械制造行业的软件生态里C#一直有很强存在感。很多车间设备的数据采集、上位机程序都是用C#写的企业的信息团队对.NET技术栈更熟悉。继续用C#做上传后端最大的好处是整个系统技术栈统一后期维护找人更容易不用为了一个上传模块单独养一支Java或Golang团队。具体框架我推荐ASP.NET Core Web API。它本身就支持IFormFile文件上传跨平台能部署在Windows Server和Linux上性能足够应对车间并发。而且.NET 6之后的版本在文件流处理上做了不少优化配合异步API处理大量并发分片上传时内存占用控制得比老Framework时代好得多。这个选型还有一个现实的考虑机械企业往往要求内网私有化部署有严格的网络安全要求。用ASP.NET Core可以很容易地把整套服务打包成Windows服务或者放在IIS后面做反向代理部署路径非常成熟。2.2 秒传与分片上传的完整流程整套上传流程我拆成了八个步骤前后端各司其职用户选择文件后前端在浏览器里用Web Worker算文件MD5。前端调用检查接口把MD5、文件名、文件大小发给后端。后端查指纹表如果文件完整存在直接返回秒传成功。如果文件是新文件后端继续查分片记录表返回已上传的分片序号列表。前端拿到已上传分片列表把缺失的分片按顺序逐块上传。每传完一块分片前端更新进度条并继续传下一块。所有分片传完后前端调用合并接口。后端按序号合并分片校验总大小写入指纹表整个上传结束。这个流程最巧妙的地方在第四步它顺便把断点续传做了。哪怕用户上次传了一半就关掉了浏览器下次再选同一个文件前端算出来的MD5不变服务器还有之前的分片记录只传缺失的部分就行。不需要额外设计什么恢复机制一个检查接口全部搞定。2.3 数据表与关键参数设计我用两张表来支撑这套方案。一张存文件信息一张存分片信息。文件信息表FileInfoFileMd5作为唯一指纹键记录文件名、文件大小、存储路径、上传状态、上传时间以及业务上需要的ProjectId、OwnerId等关联字段。分片记录表ChunkInfo每条记录对应一个已上传的分片包括FileMd5、ChunkIndex、分片大小、临时路径、上传时间。这张表不需要永远保留文件合并完成后就可以把对应记录清掉。为什么要分两张表因为查文件是否已存在和查哪些分片已上传是两个频率完全不同的操作。前者每次上传都会触发必须快后者只在秒传失败后触发并且又要支持断点续传的精确查询。如果挤在一张表里数据量大之后查询会互相拖累。关键参数我也列个表方便你直接参考。参数推荐值说明分片大小2MB内网可5-10MB太小请求多太大失败重传成本高前端并发数3-5个减少服务器瞬时压力同时保证吞吐单个文件上限按企业需求常见5-20GBKestrel和反向代理都要同步调临时分片保留时间24小时过期后由定时任务清理MD5计算切片2MB与上传分片大小一致避免额外IO2.4 检查接口与分片接口的边界后端接口我分成三个Check、Upload、Merge。Check是GET或POST都行但建议用POST因为要传文件名、大小、MD5这几个字段放在请求体里比放在查询串里更规范。Upload接收分片文件Merge处理合并。这三个接口的边界非常清晰Check只负责查询不写数据Upload只负责接收分片和落盘不管文件是否完整Merge只负责合并和指纹登记。后端代码尽量让每个接口只干一件事这样排查问题的时候日志一打你立刻知道卡在哪个环节。3. C#后端核心接口实现3.1 文件指纹检查接口Check接口是整个秒传方案的第一道关卡。它的逻辑很好理解先用FileMd5和FileSize查FileInfo表如果发现一个已完成状态的文件直接返回秒传成功。如果不是已完成就去查ChunkInfo表把已上传的分片序号返回给前端。我直接上一个可以用的C#接口代码基于EF Core。[HttpPost(check)] public async TaskIActionResult Check([FromBody] FileCheckRequest request) { var file await _db.FileInfos .FirstOrDefaultAsync(f f.FileMd5 request.FileMd5 f.FileSize request.FileSize); if (file ! null file.Status UploadStatus.Completed) { return Ok(new CheckResult { Exists true, FileId file.Id, Message 秒传成功 }); } var uploadedChunks await _db.ChunkInfos .Where(c c.FileMd5 request.FileMd5) .Select(c c.ChunkIndex) .ToListAsync(); return Ok(new CheckResult { Exists false, UploadedChunks uploadedChunks }); }这个接口很多人容易忽略一个点为什么要同时比对FileSize因为MD5理论上有极小概率碰撞但加上文件大小双重校验后实际误判概率可以忽略。机械行业里两个内容不同但大小相同的文件很常见但内容不同MD5还一样的概率基本不存在所以双字段查询是性价比最高的安全策略。3.2 分片上传接口Upload接口接收前端传过来的分片文件这里直接存成临时文件。注意一个比较容易踩坑的地方不要把分片直接堆在一个大目录里按FileMd5建子目录存放既避免文件数量过多导致目录查询变慢也方便合并时清理。我给的实现里分片文件名直接用ChunkIndex加.part后缀这样合并时按序号读取就好不需要再解析文件名里的额外信息。[HttpPost(upload)] public async TaskIActionResult Upload([FromForm] ChunkUploadRequest request, IFormFile file) { if (file null || file.Length 0) return BadRequest(分片内容为空); var tempRoot _config[FileStorage:TempRoot]; var safeDir Path.Combine(tempRoot, request.FileMd5); if (!safeDir.StartsWith(tempRoot)) return BadRequest(非法的MD5路径); Directory.CreateDirectory(safeDir); var chunkPath Path.Combine(safeDir, ${request.ChunkIndex}.part); await using (var stream new FileStream(chunkPath, FileMode.Create)) { await file.CopyToAsync(stream); } var chunk await _db.ChunkInfos .FirstOrDefaultAsync(c c.FileMd5 request.FileMd5 c.ChunkIndex request.ChunkIndex); if (chunk null) { _db.ChunkInfos.Add(new ChunkInfo { FileMd5 request.FileMd5, ChunkIndex request.ChunkIndex, ChunkSize file.Length, UploadTime DateTime.Now }); await _db.SaveChangesAsync(); } return Ok(new { success true, chunkIndex request.ChunkIndex }); }我特意写了路径校验safeDir.StartsWith(tempRoot)这行很关键。因为FileMd5是前端传过来的字符串如果不加校验攻击者可以构造一个带路径分隔符的MD5值把文件写到服务器任意目录这是非常典型的路径穿越漏洞。机械行业的内网系统虽然攻击风险比公网低但该防的必须防。3.3 分片合并接口Merge接口负责把临时目录下所有分片按顺序合并成一个完整文件。实现上我用FileMode.Create创建最终文件然后逐个用FileMode.Open打开分片并CopyToAsync到最终文件流。这里要注意必须按序号从小到大合并顺序一错文件就废了。合并完成后一定要校验最终文件大小。我见过不止一次因为前端遗漏分片或者网络异常导致合并出来的文件只有原文件一半大小的情况。校验不通过就直接删除文件返回错误并且把临时目录清理掉避免脏数据留在磁盘上。[HttpPost(merge)] public async TaskIActionResult Merge([FromBody] MergeRequest request) { var tempRoot _config[FileStorage:TempRoot]; var storageRoot _config[FileStorage:StorageRoot]; var dir Path.Combine(tempRoot, request.FileMd5); if (!Directory.Exists(dir)) return BadRequest(分片目录不存在); var extension Path.GetExtension(request.FileName); var finalPath Path.Combine(storageRoot, ${request.FileMd5}{extension}); await using (var finalStream new FileStream(finalPath, FileMode.Create)) { for (var i 0; i request.TotalChunks; i) { var partPath Path.Combine(dir, ${i}.part); if (!System.IO.File.Exists(partPath)) { return BadRequest($缺少分片 {i}); } await using var partStream new FileStream(partPath, FileMode.Open); await partStream.CopyToAsync(finalStream); } } var actualSize new FileInfo(finalPath).Length; if (actualSize ! request.FileSize) { System.IO.File.Delete(finalPath); Directory.Delete(dir, true); return BadRequest(合并后的文件大小与预期不一致); } Directory.Delete(dir, true); _db.FileInfos.Add(new FileInfo { FileMd5 request.FileMd5, FileName request.FileName, FileSize actualSize, FilePath finalPath, Status UploadStatus.Completed, UploadTime DateTime.Now }); await _db.SaveChangesAsync(); return Ok(new { success true, fileId request.FileMd5 }); }这里的扩展名提取有个细节不要直接用前端传的文件名做最终路径而是只取扩展名主体用FileMd5命名。这样带来的好处是服务器上同一个文件只有一个物理副本不管前端给它起什么名字只要内容相同就不会重复存储。但要注意如果你需要同一个文件在不同业务单据里显示不同文件名那就在业务表里存自己那份文件名别去改物理文件。3.4 后端必须要处理的三个安全隐患第一分片数量要有限制。恶意用户或者异常前端可能传几千个分片把服务器磁盘塞满。Merge接口里最好加一个判断如果TotalChunks超过预设上限比如5000直接拒绝。第二文件类型不能只看扩展名。机械行业允许上传DWG、STEP、STL等格式但扩展名可以被随意篡改。靠谱的做法是在合并完成后读一下文件头部字节跟预期的文件签名比对把伪装成DWG的病毒或恶意脚本挡在门外。第三下载和访问路径不能直接暴露物理路径。FileInfo表里虽然存了完整路径但不能把这个路径直接返回给前端。所有文件下载都通过后端接口鉴权后用文件ID换取文件流避免任何人猜出服务器目录结构。4. 前端实现浏览器里算哈希切分片做进度条4.1 用Web Worker计算文件MD5前端是整个方案的体验担当而体验的第一关就是算MD5。有的朋友偷懒直接在输入框change事件里读整个文件算哈希文件一大浏览器页面直接卡死用户以为系统坏了。正确的做法是把哈希计算放到Web Worker里让它后台跑不阻塞主线程UI。SparkMD5是前端算MD5比较常用的库配合Web Worker可以分片读取文件内容边读边累加哈希。我这里的示例用ArrayBuffer模式每片2MB这样即使算5GB文件内存也不会爆炸。// md5-worker.js importScripts(spark-md5.min.js); self.onmessage function (e) { const file e.data.file; const chunkSize 2 * 1024 * 1024; const totalChunks Math.ceil(file.size / chunkSize); const spark new SparkMD5.ArrayBuffer(); let currentChunk 0; const reader new FileReader(); reader.onload function (ev) { spark.append(ev.target.result); currentChunk; self.postMessage({ type: progress, percent: currentChunk / totalChunks }); if (currentChunk totalChunks) { loadNext(); } else { self.postMessage({ type: complete, md5: spark.end() }); } }; reader.onerror function () { self.postMessage({ type: error }); }; function loadNext() { const start currentChunk * chunkSize; const end Math.min(start chunkSize, file.size); reader.readAsArrayBuffer(file.slice(start, end)); } loadNext(); };主线程这边只需要创建Worker把File对象传进去监听返回的MD5就行。注意File对象是支持结构化克隆的可以直接通过postMessage传给Worker不需要自己先在主线程里把它读成ArrayBuffer。const worker new Worker(md5-worker.js); let fileMd5 ; worker.postMessage({ file: selectedFile }); worker.onmessage function (e) { if (e.data.type progress) { updateStatus(正在计算文件指纹${Math.round(e.data.percent * 100)}%); } else if (e.data.type complete) { fileMd5 e.data.md5; checkAndUpload(); } };很多团队会忽略哈希进度提示我以为这很重要。一个几百MB的文件算哈希也要五六秒如果界面上毫无反馈用户就会觉得系统没响应可能直接关页面。加上进度提示后体验立刻就不一样了。4.2 秒传检测与断点续传拿到MD5之后前端调用Check接口根据返回值执行两个分支如果Exists为true直接提示上传成功结束如果为false就拿到了UploadedChunks数组里面有服务器上已经存在的分片序号接下来只上传缺失分片。这个逻辑看起来简单但它实际上把秒传和断点续传合并成了一件事。用户第一天传了40%关掉电脑走了第二天回来重新打开页面选同一个文件算出的MD5一样服务器把之前传过的分片序号全告诉你剩下60%接着传。async function checkAndUpload() { const checkResp await fetch(/api/file/check, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ fileMd5: fileMd5, fileName: selectedFile.name, fileSize: selectedFile.size }) }); const checkData await checkResp.json(); if (checkData.exists) { setResult(文件已存在秒传成功); return; } await uploadMissingChunks(checkData.uploadedChunks); }这里有一个容易忽略的边界如果服务端返回的UploadedChunks数量已经等于总块数但文件状态还没标记为Completed说明上次传完了分片但没触发Merge。前端最好在这种情况下直接调用Merge接口而不是傻傻地什么都重传。4.3 分片上传的并发控制和重试分片上传阶段不建议一上来同时发几十个请求服务器会很受伤。我自己习惯把并发数控制在3到5个给后端留足IO余量。下面这个示例用Promise来模拟并发池每个WorkerTask从总队列里取最新分片号上传完成后继续取下一个。async function uploadMissingChunks(uploadedChunks) { const chunkSize 2 * 1024 * 1024; const totalChunks Math.ceil(selectedFile.size / chunkSize); const skipped new Set(uploadedChunks || []); let nextChunk 0; let uploadedCount skipped.size; const concurrency 3; async function uploadOne(index) { const start index * chunkSize; const end Math.min(start chunkSize, selectedFile.size); const blob selectedFile.slice(start, end); const formData new FormData(); formData.append(fileMd5, fileMd5); formData.append(chunkIndex, index); formData.append(totalChunks, totalChunks); formData.append(file, blob, chunk_${index}); let retry 0; while (retry 3) { try { const resp await fetch(/api/file/upload, { method: POST, body: formData }); if (!resp.ok) throw new Error(upload failed); uploadedCount; updateProgress(uploadedCount / totalChunks); return; } catch (err) { retry; await new Promise(r setTimeout(r, 1000 * retry)); } } throw new Error(分片 ${index} 上传失败); } async function workerTask() { while (nextChunk totalChunks) { const index nextChunk; if (skipped.has(index)) continue; await uploadOne(index); } } const tasks []; for (let i 0; i concurrency; i) { tasks.push(workerTask()); } await Promise.all(tasks); await fetch(/api/file/merge, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ fileMd5: fileMd5, fileName: selectedFile.name, fileSize: selectedFile.size, totalChunks: totalChunks }) }); setResult(上传完成); }这个方案我已经在项目里实测过同样的3GB装配体网络波动频繁的车间环境下完整上传从原来的反复失败变成基本稳定成功。分片重试带来的价值比任何花哨的界面优化都实在。5. 机械制造场景的落地优化5.1 文件类型白名单与大小限制机械制造行业需要的文件类型非常固定无非是设计图纸、三维模型、工艺文档这几大类。做上传功能时一定要有扩展名白名单不要把系统开放成什么文件都能传。我建议的常见清单DWG、DXF、STEP、STP、IGES、IGS、STL、PRT、ASM、SLDPRT、SLDASM、CATPART、CATPRODUCT、NC、XLSX、PDF。白名单之外的文件直接拒绝上传这在后期做数据治理时会替你省掉大量麻烦。Kestrel默认的请求体大小限制是30MB左右大型文件一定得调整。我一般在Program.cs里这样设置允许最大100GB的请求体builder.WebHost.ConfigureKestrel(options { options.Limits.MaxRequestBodySize 100L * 1024 * 1024 * 1024; });如果服务器前面还有IIS或者Nginx做反向代理那这些代理的配置也必须同步修改。Nginx默认的client_max_body_size是1MB你不改的话后端Kestrel设置成100GB也没用请求会在Nginx这一层就被拦下来。这个坑我见过太多人踩了前端明明显示上传了一半服务器日志却什么都没有。5.2 车间网络的特殊适配车间网络有两个特点带宽不一定小但抖动很厉害。很多工厂车间里用的是老式百兆交换机多台机床同时联网加上工人用手机刷视频网络质量完全不可控。分片上传对这类网络有天然的宽容度因为单次传输的数据量小就算某一瞬间网络断开也只是当前这一个分片失败前端重试机制会补上。这里要特别注意重试间隔必须有退避策略不要失败后立刻疯狂重试否则会把车间本来就脆弱的网络直接打爆。我习惯的重试策略是第一次失败等1秒第二次失败等2秒第三次失败等3秒超过三次就把错误上报给用户。这样既给了网络恢复的时间又不会让用户无限等待。如果你们的场景是车间设备离线采集数据然后集中上传我建议另外再加一道本地暂存机制。设备或工控机先写到本地磁盘攒到一定量再批量上传不要实时硬顶网络。5.3 权限、版本与业务联动机械企业里同一个CAD文件经常会被多个项目引用。秒传机制如果是全局性的会出现一个隐患A项目传了一个图纸B项目的用户只要知道文件MD5就可能直接秒传并关联过去等于把A项目的文件泄露给了B项目。解决办法是在FileInfo表上增加OwnerId和ProjectId字段Check接口在处理秒传逻辑时不能只查FileInfo还要校验当前用户对目标文件是否有访问权限。如果当前用户没权限就算文件在服务器上存在也要走完整上传流程重新生成一个归属于当前项目的文件副本。或者换一种做法物理文件只有一个但业务关联记录单独存每条关联都校验权限。版本管理方面我的建议是文件指纹保持不变但每次上传都创建一个新的文件版本记录指向同一个物理文件或新物理文件。这样工程师迭代图纸时系统可以清晰展示版本3在秒传意义上复用了版本2的物理文件既省空间又保留完整的版本演进关系。这个设计在PLM系统里尤其重要评审、签核、变更记录都依赖这些版本数据。6. 常见问题与排错经验6.1 算MD5时页面卡死如果用户反馈页面在选完文件后死了八成是因为在UI线程直接读取大文件算哈希。解决方法是强制走Web Worker并且在Worker里通过FileReader按切片读取每次处理2MB浏览器永远只占用很小的一块内存。还有一种情况是开发时有些浏览器对Web Worker传File对象存在兼容问题。稳妥的做法是在主线程里先拿到文件Blob再把Blob传给Worker。File和Blob在绝大多数现代浏览器里都能结构化克隆但遇到极老的内网浏览器比如旧IE就别指望这套方案了我建议直接要求机械企业统一换Chromium内核浏览器。6.2 上传接口提示413 Payload Too Large这个错误出现的位置决定了问题在哪一层。如果浏览器DevTools里Network面板显示请求没有到达后端那多半是反向代理的限制。Nginx改client_max_body_sizeIIS改web.config里的maxAllowedContentLength。如果请求到达了后端但被拒绝那就是Kestrel的限制没有放开。很多开发者只改了Kestrel却没有同步检查IIS或Nginx的配置结果两头脱节。我的排查习惯是先把后端临时直接监听一个独立端口测试不走代理看大文件上传是否正常这样就容易定位是哪一层拦截的。6.3 合并文件大小不对文件合并后大小和前端上报不一致常见原因有三个前端漏传了某个分片、某个分片被覆盖写错误、合并顺序被ConcurrentDictionary之类的并发容器打乱。排查方式很直接Merge接口里循环检查每个分片文件是否存在缺少哪一块就打日志记录哪一块前端再根据日志精密重试。合并时千万不要用多线程并行合并虽然看起来快点但文件指针和目标文件流的控制会变得特别复杂机械行业的内网环境下这点性能差距不值得冒险。6.4 秒传成功但业务数据没关联上这是业务层最容易忽略的问题。很多团队把上传文件和表单提交做成两个独立步骤用户先选了文件文件秒传成功但他在页面上忘记点保存或者点击保存时Session超时最后文件在服务器上存在订单里却查不到关联。所以我在前端流程上坚持一个原则文件上传只是中间步骤必须等业务单据提交成功整个流程才算完成。秒传接口返回成功时不要立刻提示上传完成而是提示文件已就绪请点击保存完成提交。后端也要能定期清理那些只上传了文件但一直没关联业务记录的孤儿数据避免存储空间被无效文件占满。我习惯在每天凌晨跑一个定时任务把超过30天未关联业务且状态为Completed的文件转入归档区。6.5 文件命名和物理路径的坑最后提醒一个之前提到过但值得重复的问题服务器物理路径一定要用FileMd5做文件名不要用原始文件名。机械行业经常有同名图纸比如装配图最终版.dwg这种名字可能同时出现在几十个订单里。如果用原始文件名做物理文件名迟早会出现覆盖或者同名冲突而且一旦覆盖就是不可恢复的事故。如果你一定要让用户下载时看到原始文件名那就把原始文件名存在数据库里下载接口从库里读取并设置到Content-Disposition头中。这样物理层唯一业务层灵活两边都不耽误。我在这个上传系统上反复磨了几轮之后一个特别深的体会是机械行业的用户根本不在乎你用的是不是最新技术他们在乎的是这个文件传上去到底成没成、断了能不能接着传、下次再传同样的图能不能快一点。技术细节很重要但最终落到车间场景里稳定和确定才是最打动人的。分片大小、并发数、超时时间这些参数我写的只是推荐值建议你上线前拿真实图纸文件在现场网络里跑一轮测试。比如用同一个2GB的STEP文件分别用2MB、4MB、8MB分片测三组看哪个组合在你们那个网络环境里又稳又快然后用那一组作为配置上线。这套方案也不只适用于机械行业检测机构的检测报告上传、设备日志回传、工地现场照片采集凡是Web系统传大附件的场景都可以直接套用。
返回列表