医疗大文件分块上传与多附件协同方案实践
发布时间:2026/9/28 17:38:47来源:尧图网络
1. 医疗业务对上传功能的真实要求从影像文件到多附件并存做过医疗行业JavaWeb开发的人应该都清楚真正让人头疼的往往不是业务逻辑多复杂反而是最基础的文件上传功能。你面对的可不是几十KB的Word文档而是动不动就几百MB的CT、MRI原始影像以及动辄几十份的会诊材料、检验报告、既往病历扫描件。医疗系统对上传的要求本质上就是大和多两个字的组合单文件体积大一次业务要提交的附件数量多。我刚入行做医疗项目时也天真过以为用传统的input typefile加上一个MultipartFile接口就能搞定。结果第一次在真实环境上传一个400MB的DICOM文件时整整卡了十分钟最后Nginx直接返回504浏览器显示网络错误整包数据全部白传。后来才明白大文件上传和小文件上传是完全不同的两套设计思路。小文件可以赌一次HTTP请求能顺利完成大文件绝对不能赌因为单体请求失败的成本太高了。这引出医疗场景下必须认真对待两个关键词分块上传和多附件上传。分块是解决单个文件太大的手段多附件是解决业务需要一次提交多个文件的手段。它们不是两个孤立功能而是一套组合拳。这篇内容我按实际项目落地顺序把从设计、编码到排坑的完整链路捋一遍给同样在医疗行业做JavaWeb后端、或者前端要对接上传功能的同学一个可以直接参照的方案。1.1 一张CT片的体积决定了你必须走分块这条路先说为什么传统上传方式在医疗场景下会失灵。一个标准的胸部CT检查少则几百幅图像多则上千幅DICOM格式打包之后轻松超过500MB有些三维重建数据、动态影像数据几个GB也不稀奇。如果走传统POST整文件上传会遇到四重问题浏览器内存压力巨大。FileReader读取大文件时如果方案写得粗糙浏览器直接卡死或者崩溃。HTTP代理层有体积限制。Nginx默认的client_max_body_size是1MBTomcat默认的maxSwallowSize是2MB部署环境里这些参数不调文件传到一半就被服务端掐断。网络波动导致整体失败。哪怕已经传了95%断一下就要从头开始对医生来说这就是灾难。上传期间无法展示可靠进度。整包上传只能等后端全部接收完才知道结果中间完全黑盒。分块的核心思路其实很简单把一个大文件切成一堆小块每个小块作为一个独立请求上传后端收齐所有小块之后再按顺序合并成完整文件。类比一下就像搬家时一辆卡车拉不走所有家具就拆成多个小包裹分批运到新家最后合起来摆放。这样每一个小请求体积可控、失败可重试、进度可精确统计。1.2 多附件上传不是多个上传框的堆叠多附件这个词很容易让人误解以为前端放几个input typefile就完事了。实际医疗业务里的多附件是有明确语义结构的。比如一次会诊申请医生要上传的内容可能包括主文件本次的原片或完整的检查报告PDF一个业务必须有一个核心文件。附属材料既往病历扫描件、化验单照片、外院就诊记录可能存在多份。补充说明医生手写的备注图片或者其他佐证材料。这些附件在业务上有关联关系有的必须上传有的选传有的要标识为主附件。前端需要给用户一个统一管理的文件列表每个文件能看状态、能删除、能重传、能看独立进度。与此同时每个文件本身可能又很大也要走分块逻辑。所以多附件和分块必须在一个统一的文件任务模型下协同工作而不是各做各的。这一点直接决定了后面的数据结构和接口设计。2. 分块上传的顶层设计切片、编号、合并这套逻辑是怎么串起来的分块上传听起来不复杂但真正落地时最怕的是一上来就写代码结果前后端各自玩一套联调的时候到处打架。我建议先想清楚整个链路的角色划分和数据流再动手。2.1 分块上传的核心流程与角色划分整个分块上传链路里有三个角色前端浏览器负责切片和发送后端服务负责接收和存储元数据存储我用的是Redis负责记录任务状态。完整的交互过程分四个阶段初始化阶段前端读取文件内容计算MD5值作为文件指纹然后调用初始化接口。后端拿到文件名、文件大小、MD5之后先查一下这个文件是否已经存在如果存在直接返回秒传成功否则创建一个上传任务生成全局唯一的taskId返回给前端。分块上传阶段前端拿到taskId后把文件按固定大小切片给每一块编号然后逐个调用上传分块接口。后端每收到一个分块校验请求里的taskId和块序号把数据落盘到临时目录并记录这个分块已经上传完成。合并阶段前端等所有分块都发完后调用合并接口。后端先检查当前任务的分块是否齐全、大小是否正确然后按序号把临时分块合并成完整文件。完成与清理阶段合并成功之后后端更新文件状态为已完成删除临时分块文件并返回最终文件的访问路径和元数据信息。这个流程前后端各管一段清晰的职责边界是后面所有细节的前提。2.2 分块大小怎么定5MB到10MB是一个比较平衡的区间分块大小不是越细越好我在项目里试过1MB、2MB、5MB、10MB、20MB几档最终医疗场景下我推荐采用5MB到10MB。原因是这样一个区间能在几个约束条件之间取得平衡请求总数可控。一个500MB的文件用5MB分块就是100个请求用1MB分块就是500个请求。请求越多前端并发控制越复杂网络连接建立的开销也越大。单块体积不至于太小避免小文件碎块在网络拥塞时频繁失败。单块5MB在普通医院内网环境下即使弱网也能在几秒内传完失败重试成本低。单块体积也不会太大避免触碰到网络代理层的请求体限制。很多政务医疗环境的网关只允许单请求10MB以内5MB一块留了充足余量。断点续传的粒度合适。断点续传是按块为单位的块越小断点延续时浪费的流量越少。前端切片时chunkSize建议在初始化阶段由后端通过接口下发这样可以在不同的网络环境、不同的部署环境下动态调整不需要改前端代码。我项目里默认是5MB弱网环境可以下发2MB内网高速环境可以调整到10MB。2.3 元数据设计任务Id、分块序号、文件指纹分块上传能平稳跑起来靠的是一套完整的元数据。关键字段如下字段示例值作用taskIduuid(32位无横线)标识一次上传任务fileMd5文件整体MD5秒传判断、完整性校验fileNamect_2025_0001.dcm原始文件名fileSize524288000文件总字节数chunkSize5242880每个分块的字节数chunkTotal100分块总数uploadedChunks[0,1,2,5,6,...]已上传分块的序号集合这些元数据我在项目里放在Redis里key是upload:task:{taskId}用Hash结构存储。选Redis的原因很直接分块上传过程中高频读写某个编号的分块是否已上传Redis自身的SISMEMBER、HSET操作性能极高而且天然支持过期时间。如果任务超过30分钟没有新分块到达可以让Redis自动清理任务防止临时碎文件堆积成山。上传频率不高的场景用MySQL也可以但要注意加索引频繁的查已上传块集合操作在数据量大了之后会很慢。3. 前端实现切片、并发与进度反馈的落地代码前端是整个分块上传的发起方切片质量直接决定后端合并的顺利程度。我不推荐前端同事直接用第三方大文件上传组件很多开源组件在医疗内网这种特殊环境下反而累赘。自己实现核心逻辑并不难还能完全按业务定制。3.1 File对象与slice方法实现切片浏览器原生File对象继承了Blob而Blob自带一个slice()方法可以在不读取整个文件进内存的前提下取出文件某一段范围的二进制数据。这是分块上传的基础能力。function createChunks(file, chunkSize) { const chunks []; let start 0; while (start file.size) { const end Math.min(start chunkSize, file.size); const chunk file.slice(start, end); chunks.push({ index: chunks.length, blob: chunk, size: chunk.size }); start end; } return chunks; }这段代码有几个细节要特别注意slice的end参数是开区间所以最后一个分块要手动用Math.min限制到文件末尾chunk.index从0开始前后端序号必须约定一致chunk.size留作后端校验。切片过程本身不占用大量内存因为Blob是懒加载的只有真正调用FormData.append塞给XMLHttpRequest时才会读取数据这一点正好解决了大文件上传的内存痛点。上传每个分块时的代码我习惯用XMLHttpRequest而不是fetch原因很简单xhr.upload.onprogress能拿到上传进度事件fetch的ReadableStream版本支持度还参差不齐。分块上传对进度的要求是刚需不能用fetch糊弄。function uploadChunk(taskId, chunk, fileMd5) { return new Promise((resolve, reject) { const formData new FormData(); formData.append(file, chunk.blob, chunk); formData.append(taskId, taskId); formData.append(chunkIndex, chunk.index); formData.append(chunkTotal, chunks.length); formData.append(fileMd5, fileMd5); const xhr new XMLHttpRequest(); xhr.open(POST, /api/upload/chunk); xhr.onload () { if (xhr.status 200) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error(分块上传失败)); } }; xhr.onerror () reject(new Error(网络异常)); xhr.send(formData); }); }3.2 并发控制不要一次性把全部分块发出去如果前端一次性把所有分块请求全发出去100个分块就是100个并发请求。浏览器对同一域名的TCP连接数限制是6个左右多出来的请求全部排队后端同时收到上百个写磁盘请求磁盘IO直接打满响应反而更慢。更致命的是一旦网络抖动大量请求同时失败前端重试逻辑会像雪崩一样。我建议用一个小型并发池把同时上传的分块数限制在3到4个。实现方式不复杂核心是队列加计数async function uploadAllChunks(chunks, taskId, fileMd5, concurrency 3) { const results []; let current 0; async function worker() { while (current chunks.length) { const index current; const chunk chunks[index]; try { await uploadChunk(taskId, chunk, fileMd5); results.push(index); } catch (e) { // 单块失败重试3次依然失败则抛出并中断 await retry(() uploadChunk(taskId, chunk, fileMd5), 3); results.push(index); } updateProgress(results.length, chunks.length); } } const workers Array.from({ length: Math.min(concurrency, chunks.length) }, () worker()); await Promise.all(workers); return results; }这里一个关键点是进度回调必须在单个分块成功之后立刻触发而不是整个Promise.all结束之后才统一更新否则用户在界面上看到的就是进度条长时间不动然后突然跳满体验很差。3.3 断点续传与秒传的前端状态管理医疗环境的网络并不稳定医生办公室的Wi-Fi、楼宇间的无线信号随时可能让上传中断。所以断点续传不是加分项而是必选项。前端实现断点续传有两种方式一种是本地存储方式用localStorage保存每个taskId的已上传分块集合。刷新页面之后前端带上taskId重新向上传接口询问哪些分块已经传过跳过这些分块继续传剩下的。另一种需要后端配合后端在上传分块接口里做幂等处理前端重复传某个分块时后端发现已存在就直接返回成功。第二种方式更稳妥我推荐两个一起用。秒传的逻辑在前端很轻初始化阶段调用prepareUpload接口时后端返回的data里如果带了alreadyExists: true和最终的filePath前端直接提示文件已存在并结束任务不执行任何切片逻辑。MD5计算是秒传的前提但计算一个500MB文件的MD5在前端会有明显的耗时我建议在用户选择文件后立刻在requestIdleCallback里查内存计算的空余时间去跑不要阻塞UI渲染。4. 后端实现接收分块、校验完整性、合并文件的完整链路后端是整个分块上传的裁判员和仓库管理员。前端可以写灵活后端必须写严谨。我下面给的代码以Spring Boot为参考其他Java Web框架思路完全一致。4.1 Controller层如何接收分块数据后端接收分块的接口参数设计和校验逻辑比业务代码本身更重要。我的接口定义如下PostMapping(/api/upload/chunk) public Result uploadChunk(RequestParam(file) MultipartFile file, RequestParam(taskId) String taskId, RequestParam(chunkIndex) Integer chunkIndex, RequestParam(chunkTotal) Integer chunkTotal, RequestParam(fileMd5) String fileMd5) { // 基础参数校验 if (StringUtils.isBlank(taskId) || chunkIndex null || chunkTotal null) { return Result.error(参数不完整); } if (chunkIndex 0 || chunkIndex chunkTotal) { return Result.error(分块序号非法); } if (file.getSize() 0) { return Result.error(分块内容为空); } // 从Redis判断任务是否存在 UploadTask task uploadTaskService.getTask(taskId); if (task null) { return Result.error(上传任务不存在请重新初始化); } // 校验MD5格式和分块大小 if (file.getSize() task.getChunkSize() * 1.1) { return Result.error(分块大小超出预期); } // 幂等处理该分块如果已存在直接返回成功 if (uploadTaskService.isChunkUploaded(taskId, chunkIndex)) { return Result.success(该分块已上传); } // 落盘 uploadTaskService.saveChunk(task, chunkIndex, file); return Result.success(分块上传成功); }这里有两处容易踩坑的地方第一MultipartFile的getSize()必须在写入之前校验防止恶意超大分块打爆磁盘第二幂等判断不能省否则前端断点续传时重复发送的分块会覆盖已上传分块在并发上传同一个分块的极端情况下还会产生文件写冲突。4.2 分块如何落盘、怎么避免重复处理分块落盘的核心目录结构按taskId隔离我在项目里用的是两层目录/upload/tmp/{yyyyMMdd}/{taskId}/ ├── chunk_0.part ├── chunk_1.part ├── chunk_2.part └── ...按日期再分一层是因为临时目录需要配合定时任务定期清理按日期分目录可以让清理逻辑直接按文件夹删除不需要逐条查元数据。分块的写入代码如下public void saveChunk(UploadTask task, int chunkIndex, MultipartFile file) throws IOException { String chunkDir buildChunkDir(task.getTaskId()); File dir new File(chunkDir); if (!dir.exists()) { dir.mkdirs(); } File chunkFile new File(dir, chunk_ chunkIndex .part); // 如果文件已存在说明该分块之前已成功写入直接跳过 if (chunkFile.exists() chunkFile.length() file.getSize()) { return; } try (InputStream in file.getInputStream(); FileOutputStream out new FileOutputStream(chunkFile)) { IOUtils.copy(in, out); } // 落盘成功后更新Redis中的已上传分块集合 uploadTaskService.markChunkUploaded(task.getTaskId(), chunkIndex); }注意这里最后必须更新Redis里的uploadedChunks集合。这个集合不只是给前端做断点续传查询用的更是合并前校验分块是否齐全的依据。如果漏了这一步会出现分块文件在磁盘上存在但元数据缺失合并阶段误判为分块缺失而拒绝合并。4.3 合并时如何避免大文件内存溢出合并是整个链路里最危险的一步。最容易写出的问题代码是这样的把每个分块都用Files.readAllBytes()读进内存然后拼接再写到目标文件。一个500MB的文件如果直接全部读进内存JVM的堆内存设置稍微保守一点OutOfMemoryError就来了。这就相当于你搬家时把所有箱子一次性搬进客厅结果客厅根本装不下整个房子都废了。正确做法是用FileChannel做流式合并操作系统底层会通过DMA零拷贝完成数据搬移既不占用JVM堆内存速度还快。示例代码如下public File mergeChunks(String taskId, String targetFileName) throws IOException { UploadTask task uploadTaskService.getTask(taskId); // 校验分块完整性 checkChunksComplete(task); File targetFile new File(buildFinalDir(taskId), targetFileName); if (targetFile.exists()) { targetFile.delete(); } try (FileOutputStream fos new FileOutputStream(targetFile); FileChannel outChannel fos.getChannel()) { for (int i 0; i task.getChunkTotal(); i) { File chunkFile new File(buildChunkDir(taskId), chunk_ i .part); if (!chunkFile.exists()) { throw new IllegalStateException(分块缺失: i); } try (FileInputStream fis new FileInputStream(chunkFile); FileChannel inChannel fis.getChannel()) { inChannel.transferTo(0, inChannel.size(), outChannel); } } } // 合并后校验最终文件大小 if (targetFile.length() ! task.getFileSize()) { throw new IllegalStateException(合并后文件大小不一致); } // 清理临时分块 deleteChunkDir(taskId); return targetFile; }transferTo每次调用搬运一个分块由操作系统负责把文件内容从源channel拷到目标channel应用层完全不用碰数据。合并一个500MB的文件耗时通常在一秒到几秒之间取决于磁盘性能。4.4 接口返回结构与异常设计整个上传链路的接口返回结构前后端必须有一个共同约定。我通常用三段式结构{ code: 200, message: success, data: { taskId: xxx, uploadedChunks: [0, 1, 2], status: UPLOADING } }异常场景不能用笼统的500分块重复上传返回200反而更合理因为幂等就是成功的任务不存在返回400分块过大返回413合并时损坏返回500并附带明确错误信息。前端对不同错误码的处置方式是完全不同的例如413要提示用户文件超过限制而任务不存在要提示用户重新初始化任务。5. 多附件上传场景的工程化批量、排序、重试与总进度如果上面只做单文件分块上传那还只是完成了一半。多附件上传系统的核心难点在于同时管理多个文件的上传任务并且处理好它们之间的关系。5.1 多附件上传的任务模型我在前端维护一个文件队列每个队列元素的结构大致如下{ uid: file_202501011230_001, file: File对象, taskId: null, // 调用prepare接口后由后端生成 status: WAITING, // WAITING | UPLOADING | PAUSED | DONE | FAILED progress: 0, // 0-100 attachmentType: MAIN, // MAIN | SUB }后端则在上传完成后把最终文件路径和附件类型、排序号一起绑定到业务单据上。比如一个会诊申请单主文件绑定到mainFileId字段附属材料绑定到attachmentFileIds列表。这种绑定的动作不一定在上传接口里做更常见的做法是前端先完成所有文件上传用户点击提交申请时再把所有文件ID和业务字段一起提交给业务保存接口。这样可以把文件上传和业务单据提交解耦用户上传过程中即使反复调整附件列表也不会产生脏数据。5.2 总进度计算分块进度乘以文件进度的两层汇总多附件场景下的进度显示至少要有两个维度整体进度条和单个文件进度。整体进度不能简单对所有文件的progress取平均应该按文件大小加权否则一张200MB的CT原片和一张200KB的化验单照片权重一样进度条会失真。计算公式如下function calcTotalProgress(fileList) { const totalSize fileList.reduce((sum, f) sum f.file.size, 0); const uploadedSize fileList.reduce((sum, f) { // f.progress 是0到1之间的小数 return sum f.file.size * f.progress; }, 0); return Math.round((uploadedSize / totalSize) * 100); }单个文件的progress计算也要走已上传分块数/总分块数这个口径不要用已上传字节数/总字节数。因为字节数在分块边界处可能出现最后一个分块传完但还没合并的情况显示99%和100%之间来回跳体验很怪。5.3 失败重试与用户中断的恢复策略多附件上传的失败处理要分层次来单分块失败重试3次重试仍失败整个文件标记为FAILED界面允许用户单独点击重试用户主动取消某个文件已上传分块保留在服务端一定时间后续重新选择该文件后通过秒传逻辑补齐分块而不是从头传。我在前端还做了一个自动恢复策略当某个文件因为网络波动失败时不立刻中断整个队列而是先把这个文件挂到队尾优先处理后续健康的文件最后一轮再回来重试失败文件。这样用户会感觉大部分文件都先传完了只有一两个慢的还在跑不会因为一个文件卡住而让整个页面的上传进度停摆。6. 医疗数据合规视角下的安全设计要点医疗系统里的文件上传技术做完了不代表就合格了还有一道安全合规的门槛。医疗数据的敏感性不用多说影像文件里包含患者的完整姓名、检查号、甚至身体部位的图像信息一旦泄露就是事故。6.1 传输与存储环节的加密处理传输层面生产环境必须使用HTTPS这个没有讨价还价的余地。内网部署也不例外哪怕内网环境里有其他系统在用明文HTTP上传影像文件的链路也必须是HTTPS。因为大文件分块上传的每个请求里都带着taskId和块数据明文传输时网络抓包工具可以轻易重组出整个文件。存储层面我建议对最终落库的医疗文件做一层加密。Java实现起来不复杂推荐用AES-GCM算法密钥从配置中心获取。但要特别注意先合并再加密不要分块加密。分块加密会出现密文分块之间需要额外处理IV和Padding的问题还会影响合并的可靠性。合并完成后用CipherOutputStream对整个文件做一次流式加密加密后同样能保持较高的吞吐性能。临时目录里的分块文件我建议用随机化命名或直接用taskId命名不做明文业务命名避免在传输过程中中间人就能看出这个分块属于哪个患者。6.2 权限控制与操作审计分块上传接口必须在每个请求上都做登录态校验绝对不能只校验初始化接口。我见过有人只在prepare接口校验token后续分块接口因为想省校验时间就放开了结果被脚本直接往临时目录灌文件恶意占满磁盘。正确的做法是在Spring的拦截器或Filter里统一校验所有上传相关路径的tokentoken里包含了用户ID和科室ID上传分块时再校验用户是否有该业务类型的上传权限。审计日志是整个环节里最容易被遗漏但合规审计时最被看重的部分。每次上传成功之后我建议至少记录以下字段字段示例操作人userId_0123操作时间2025-06-01 10:00:00业务类型CONSULTATION文件名ct_2025_0001.dcm文件大小524288000文件MD59f86d081884c7d659a2feaa0c55ad015最终存储路径/data/medical/2025/06/01/xxx这些日志用单独的日志表存储保留周期按医疗行业规范来。另外临时目录的自动清理也要做成定时任务比如每天凌晨扫描/upload/tmp/下超过24小时未合并的任务目录直接删除防止长期占用磁盘空间。清理时先检查Redis中对应任务的状态避免误删正在上传的任务。7. 实际落地中踩过的坑与调优实录最后这部分我把自己在这个方案上真实踩过的坑写出来。这些坑在教材和官方文档里基本看不到但遇到一个就能让人加班到深夜。7.1 网关超时和请求体限制的坑第一个坑来自反向代理层。很多医疗项目的前后端之间有Nginx、OAM或者其他网关。它们通常做两件事限制请求体大小设置请求超时时间。分块上传之后单块请求5MBclient_max_body_size只要设置成20m就不会挡路但超时时间坑得很隐蔽。如果网关的proxy_read_timeout只有60秒而某个分块在弱网环境下刚好传了65秒网关就会抢在业务接口返回之前切断连接前端看着是网络错误后端日志里却根本没有这次请求的记录。排查了很久才定位到是超时参数问题。解决方式是在前端把单块上传的超时时间设置得比网关超时时间短比如网关是60秒前端就设置50秒同时在后端启动参数和Nginx配置中把proxy_read_timeout统一调整为5分钟。前后端和中间件三处的超时时间要拉齐否则你排查时根本不知道是谁先断了连接。7.2 分块合并时文件损坏的排查过程合并后文件损坏是我踩得最痛的一个坑。现象是100个分块全部上传成功合并接口也返回了success但下载下来的文件打开报错用fciv校验MD5和原始文件不一致。当时我怀疑是合并顺序写错了但代码里for (int i 0; i chunkTotal; i)的顺序循环看不出问题。后来在临时目录里发现chunk_5.part和chunk_7.part的文件大小完全一致而且chunk_5.part的内容其实是文档第8块的内容。原因终于浮出水面前端在并发上传时某个分块请求失败后重试但重试的逻辑里没有重新读取对应的chunk.blob而是直接用了一个全局的currentFile变量去slice。由于代码中闭包变量引用问题失败的请求重试时拿到了错误的块数据。修复方式很简单重试时严格使用原始chunk对象里的blob并且后端在后置校验时增加分块大小逐块比对逻辑。前端写好代码是一回事后端在合并前的完整性校验是保护系统的最后一道防线绝对不能省。7.3 多人同时上传同一业务文件的竞争问题医疗系统里经常出现同一个医生反复上传同一份文件或者多个护士同时上传同一个患者的检查材料的情况。如果两人同时调用prepare接口都拿到了同一个taskId那后到的分块就会覆盖先到的分块合并出的文件是两个人数据的混合体MD5必然不一致。解决办法是prepare接口在后端做一次文件指纹查重。两个相同MD5的文件前者创建任务A后者创建任务B也没关系不需要强制复用同一个taskId。关键是合并时根据taskId的完整分块集合来合并互不干扰。另外合并接口内部要用Redis锁或者数据库行锁给taskId加锁防止同一个taskId被两个请求同时触发合并逻辑造成重复合并。7.4 前端切片的隐形内存陷阱前端切片最后还有一个小坑用Array.prototype.slice.call()或者Array.from(new Uint8Array(arrayBuffer))处理分块数据会直接把整个ArrayBuffer复制进内存200MB的文件就能让浏览器内存飙升到1GB以上。我在代码里完全避开了直接读取ArrayBuffer的方式只用file.slice(start, end)生成Blob传给FormData这样整个上传过程的内存占用始终很低。用户如果上传的文件包含敏感信息前端在上传之前弹窗提醒一次请确认文件已做脱敏处理这个小交互在医疗现场非常受欢迎。回到整体设计我在医疗系统里落地这套分块加多附件上传方案之后最明显的感受是不要照搬网上的demo代码尤其不能照搬那种只有单文件分块、没有多附件队列、没有断点续传的简化版本。医疗业务对可靠性的要求远高于普通互联网应用用户一旦开始依赖这个功能你就必须保证它在弱网、大文件、高并发三种恶劣条件下都能稳稳当当。把每个环节的校验、幂等、补偿和清理都做扎实了这个功能才能在临床环境里真正站得住。
网站建设高端定制企业官网