☰
金融项目Vue大文件上传DEMO:分片、断点续传与合规实践
2026/10/7 18:05:46 网站建设 项目流程

作金融项目前端这些年,最怕听到的一句话就是"就一个上传功能,找个控件接一下不就行了"。大文件上传,尤其是金融行业里动辄几百MB、甚至几个G的报表、合同影像、交易流水文件,远不是一个<input type="file">加个进度条就能糊弄过去的。所以我每次遇到这类需求,第一件事不是选控件,而是先花两三天做一个DEMO,用真实文件、真实接口把方案跑通。这个DEMO不是给领导看的炫技页面,而是用来回答"分片怎么切、断点怎么续、并发怎么控、异常怎么恢复、合规怎么满足"这些问题的。这篇文章就把我在金融场景下做Vue大文件上传控件DEMO的完整思路、代码和踩过的坑都摊开来讲,给打算接这类活儿的同学一个能直接参考的底子。

1. 金融场景的"大文件上传"到底难在哪:先想清楚再动手

1.1 一个普通上传需求背后的隐性要求

很多从外包或者互联网项目转过来的前端同事,一开始都觉得大文件上传不就是"把文件变大、超时时间调长"吗?这种想法在金融项目里会死得很难看。先说几个金融场景的硬约束,你就知道为什么不能拿普通项目那套来套:

第一,文件体量不是一般的大。银行间交易流水导出,一个对账文件几百MB算是中等水平;基金托管业务里的估值表、清算文件,动辄1GB以上;再碰上审计影像压缩包,几个G都有的。普通表单上传顶多撑个几十MB,超过200MB,不管是nginx的client_max_body_size,还是网关的超时时间,都会直接把连接掐断。

第二,业务连续性要求极高。银行、券商、保险的报送系统都有窗口期,比如日终批处理只有凌晨几个小时。如果上传中断了整个重来,可能就赶不上监管报送截止时间,这是事故级别的问题,不是刷新重试就能交差的。

第三,安全与合规审计一条不能少。文件传输要加密、要有操作留痕、要能追溯谁在什么时间传了什么文件,甚至文件内容本身的关键字段要做脱敏或完整性校验。普通的第三方上传组件根本不带这些能力,你后期拿胶水糊也糊不严实。

第四,网络环境不可控。不要以为政企客户的内网带宽都很大。证券营业部到总部的专线、移动展业人员在4G/5G环境下传尽调材料,弱网、抖动、断线都是常态。方案必须能"断了接着传",而不是"断了从头来"。

所以你会发现,金融场景的"大文件上传",本质上不是"控件选择"问题,而是一整套容错、安全、可控的传输方案问题。DEMO要验证的,正是这套方案能不能在真实约束下跑通。

1.2 "先做DEMO"到底是为了验证什么

我见过不少团队跳过DEMO直接上控件,结果联调阶段问题爆发的例子。等你在测试环境发现分片顺序错乱、服务器合并超时、并发连接被网关限流的时候,再回头改方案,成本至少翻三倍。

一个合格的大文件上传DEMO,至少要能回答这七个问题:

  • 大文件在前端如何切片,切片大小怎么定?
  • 文件唯一标识怎么生成?并发上传时后端如何识别是同一个文件?
  • 断点续传靠什么实现?秒传靠什么实现?
  • 多个分片并发上传时,浏览器的并发上限怎么控制?
  • 传一半失败了,重试机制怎么设计?
  • 服务端怎么接收分片、校验分片、合并分片?
  • 传输过程中文件内容如何加密?审计日志怎么记?

这些问题里任何一个没有想清楚,直接上生产都是给自己埋雷。DEMO的意义,就是用最小成本把这七个问题全部验证一遍,把雷提前排掉。

2. 为什么不直接引一个现成的控件:选型之前的交锋

2.1 现成组件在金融场景里的硬伤

我知道很多人的第一反应是:GitHub上一堆Vue上传组件,Star上万,拿来即用不香吗?香,但那是在普通业务场景下。金融项目里,现成组件有几个绕不过去的硬伤:

  • 传输协议固定:大部分组件走的是HTTP POST把文件交给后端,后端接收后写磁盘。对于大文件,缺少服务端配合的预检、断点记录、分片管理能力,或者有也是浅尝辄止。
  • 安全能力缺失:文件加密、国密算法支持、审计日志埋点,这些商业级能力基本没有。你总不能指望一个开源组件帮你做等保合规。
  • 改造难度不可控:为了适配内部网关、统一身份认证(IAM)、单点登录(SSO)、操作日志平台,你往往需要魔改源码。改到最后,版本都升不上去,维护成本极高。
  • 大文件场景没经过压测:大多数开源组件在几百MB的文件下表现尚可,但到了1GB以上、并发10个分片时,内存占用、超时控制、错误处理都很容易出问题。

还有一个金融行业特别在意的问题:组件依赖的可控性。很多机构对第三方依赖有严格的安全审查,一旦引入的组件被发现高危漏洞,你自己又没能力修,那就非常被动。

2.2 自研控件的边界:DEMO跑通哪些就可以动手

那是不是意味着全自研?也不需要。我的建议是:传输内核自研,界面组件自研,但底层的加密库、Hash计算库、HTTP客户端可以选经过安全审查的成熟依赖。也就是说,DEMO要跑通的核心链路(切片、预检、并发、合并、续传)必须自己掌控,这是方案的核心竞争力,不能靠黑盒组件。至于UI上的拖拽、缩略图、进度动画,这些反而无所谓,简单实现即可。

在我实际负责的项目里,最终形态是一个内部npm包形式的Vue插件,封装了PendingUpload组件、useUploader组合式API,以及一套和后端约定的协议。DEMO阶段就用这套API写了个最简页面,验证核心逻辑,后续接业务系统时,业务方只需要传文件、监听事件,不需要关心分片和续传的细节。这就叫"控件"——不是界面上一个框,而是整套可复用的上传能力。

3. DEMO的端到端方案设计:五层结构先把边界划清楚

3.1 整体架构的核心设计原则

大文件上传DEMO的整体方案,我习惯分成五层:接入层、策略层、传输层、存储层、审计层。

接入层负责和业务系统对接,暴露的是"一句话上传"的接口。策略层处理文件的预检、分片大小的动态计算、是否启用并发、重试次数等规则。传输层是内核,负责分片的实际发送、收到响应后的确认、进度汇总。存储层对接对象存储或文件服务,既要保证写入速度,又要保证合并后文件完整。审计层则把谁、什么时候、传了什么文件、每个分片的耗时、是否有重试、最终结果,全部记录到审计系统。

分层的好处是职责清晰。比如金融客户问"分片大小为什么是8MB",你可以在策略层解释:这取决于带宽、服务端性能和你设定的并发数。换成2MB或者16MB,只是改策略,不会动传输内核。

3.2 为什么分片必须是"字节级"的精确设计

分片上传的核心是前端把文件切成多个二进制块,分别上传。关键在于切片的边界必须基于字节位置,而不是基于行、页或者其他业务维度。

以8MB分片为例,假设文件总大小是100MB,那么分片顺序是:

  • 第1片:字节 0 到 8MB-1
  • 第2片:字节 8MB 到 16MB-1
  • 以此类推,直到最后一片(大小不超过8MB)

前端用Blob.prototype.slice()来切,这个方法在浏览器里非常成熟,效率也很高。切完每片后,前端为该片生成一个序号(从0开始),连同文件唯一标识一起发给后端。后端收到后,严格按序号写入临时文件块的对应偏移位置。注意这里的偏移位置,应该用"该片在整个文件中的起始字节位置"来计算,也就是总片数之前的字节总数,这样即使中间有片丢了或者重传了,也不会覆盖错位置。

我见过一个常见的错误:有人用数据库表的自增ID来记录分片顺序,结果并发上传时,先到的分片ID是5,后到的ID是3,合并时按错误的ID排序,文件就损坏了。所以DEMO里我始终坚持的是:分片序号和文件字节偏移强绑定,由前端算好带过去,后端只认这个偏移量。

4. Vue端核心实现:从切片到断点续传的完整代码拆解

4.1 环境准备与依赖选型

DEMO我用的技术栈是Vue 3.4 + Vite 5 + TypeScript。为什么用TypeScript?因为金融项目对代码可维护性要求高,上传这种核心模块,接口类型定义清楚,后续接手的同事不容易改坏。

需要引入的核心依赖其实很少:

  • spark-md5:用来计算整个文件的MD5(或者更严谨一点,用SHA-256),作为文件的唯一标识。计算Hash的耗时操作放到Web Worker里做,避免阻塞UI线程。
  • axios:做HTTP请求,实际上手写XMLHttpRequest或fetch也行,但axios在取消请求、超时控制、并发限制上更省事。
  • 可选crypto-js或@electron/remote相关加密能力,取决于你是否需要在浏览器侧对分片内容做加密。金融场景通常更推荐HTTPS链路加密+服务端加密存储,尽量不要在浏览器侧做重量级加密,性能损耗大。

安装命令很简单:

npm create vite@latest bigfile-upload-demo -- --template vue-ts cd bigfile-upload-demo npm install axios spark-md5

4.2 文件唯一标识的计算:为什么必须在Worker里算Hash

文件唯一标识就是文件的Hash值。它的用途有两个:一是做断点续传时,服务端靠它识别"这个文件之前传过哪些分片";二是做秒传,服务端发现整个文件的Hash已存在,直接返回"上传成功",省掉所有分片传输。

大文件计算Hash最忌讳的就是在主线程做。一个500MB的文件,即使只算一次全量MD5,在一般笔记本上也要好几秒甚至十几秒,期间UI直接卡死,用户以为页面崩溃了。所以DEMO里的标准姿势是:把文件对象postMessage给Web Worker,在Worker里读文件、算Hash,算完再把结果传回主线程。

下面是一个简单的Web Worker文件(hash-worker.ts):

/// <reference lib="webworker" /> import SparkMD5 from 'spark-md5'; self.onmessage = (e: MessageEvent<File>) => { const file = e.data; const chunkSize = 2 * 1024 * 1024; // 读文件时每次2MB,避免一次占用太多内存 const spark = new SparkMD5.ArrayBuffer(); let currentChunk = 0; const totalChunks = Math.ceil(file.size / chunkSize); const fileReader = new FileReader(); const loadNext = () => { const start = currentChunk * chunkSize; const end = Math.min(start + chunkSize, file.size); fileReader.readAsArrayBuffer(file.slice(start, end)); }; fileReader.onload = (event) => { spark.append(event.target!.result as ArrayBuffer); currentChunk++; if (currentChunk < totalChunks) { loadNext(); } else { const fileHash = spark.end(); self.postMessage({ fileHash, fileSize: file.size, fileName: file.name }); } }; fileReader.onerror = () => { self.postMessage({ error: '文件读取失败' }); }; loadNext(); };

主线程里通过new Worker来调用,Worker结束后要记得terminate()释放资源。

提示:如果用Vite打包,Worker文件要用new Worker(new URL('./hash-worker.ts', import.meta.url), { type: 'module' })这种形式引入,否则生产环境打包容易404。

4.3 分片上传器的核心逻辑:预检、切片、并发、续传

DEMO的核心是一个useUploader组合式函数,它把上传状态机完整封装起来。先看状态设计:

  • pending:已选择文件,正在算Hash,还没开始上传。
  • checking:正在调后端预检接口。
  • uploading:至少一个分片正在上传。
  • paused:用户主动暂停,或前端主动停止。
  • completed:所有分片上传完成,且服务端合并成功。
  • error:出现了无法自动恢复的错误。

预检接口的协议很关键,我设计的请求和响应如下:

// 请求 interface CheckRequest { fileHash: string; fileName: string; fileSize: number; } // 响应 interface CheckResponse { uploadedChunks: number[]; // 服务端已收到的分片序号 chunkSize: number; // 服务端要求的分片大小(两端必须一致) needUpload: boolean; // 是否需要继续上传 uploadId: string; // 本次上传任务的ID,后续所有分片请求都带上 }

为什么预检要返回uploadId?因为同一个文件,可能不同人、不同时间上传过,服务端要保证每个上传任务有独立的上下文,避免分片数据互相覆盖。uploadId相当于这个上传任务的身份证。

主线程的上传流程伪代码如下:

async function upload(file: File) { const fileHash = await calculateHash(file); // Worker计算 const checkResult = await checkExist({ fileHash, fileName: file.name, fileSize: file.size }); if (!checkResult.needUpload) { status.value = 'completed'; // 秒传 return; } const chunkSize = checkResult.chunkSize; const chunkList = createChunks(file, chunkSize, checkResult.uploadedChunks); status.value = 'uploading'; await concurrentUpload(chunkList, { uploadId: checkResult.uploadId, fileHash, concurrency: 3, // 并发数 }); await notifyMerge(uploadId, fileHash); status.value = 'completed'; }

createChunks这一步会过滤掉uploadedChunks里已有的分片,只上传没传过的部分,这就是断点续传的落地逻辑。

4.4 并发上传与进度计算:没有进度条的上传是反人类的

并发上传的实现在DEMO里是重头戏。浏览器对同一个域名的并发HTTP连接数是有限制的(HTTP/1.1一般是6个,HTTP/2虽然是多路复用,但服务端依然可以限流),所以我们要自己维护一个请求池,控制同时最多3到5个分片在上传。

核心思路是:维护一个待上传队列和一个正在上传的Set,每结束一个就从队列里取一个新的补上,直到队列清空。

async function concurrentUpload(chunkList, options) { const { concurrency, uploadId, fileHash } = options; const queue = [...chunkList]; const active = new Set<Promise<void>>(); const uploadOne = async (chunk) => { const formData = new FormData(); formData.append('uploadId', uploadId); formData.append('fileHash', fileHash); formData.append('chunkIndex', String(chunk.index)); formData.append('chunkTotal', String(chunk.total)); formData.append('file', chunk.blob, `chunk-${chunk.index}`); const response = await axios.post('/api/upload/chunk', formData, { timeout: 60_000, onUploadProgress: (e) => { // 累加到当前分片的上传进度,再汇总更新整体进度 }, }); return response.data; }; for (let i = 0; i < concurrency && queue.length > 0; i++) { const task = uploadOne(queue.shift()!).catch((err) => { // 失败进重试队列或标记失败 throw err; }); active.add(task); task.finally(() => { active.delete(task); if (queue.length > 0 && status.value === 'uploading') { const next = uploadOne(queue.shift()!); active.add(next); } }); } await Promise.allSettled(active); }

这里有个细节值得说:进度条怎么算才准确?不能只看"已上传字节 / 总字节",因为并发时你只有每个分片自己的onUploadProgress事件。正确做法是每个分片维护一个loaded,整体进度 = 所有分片的loaded之和 / 文件总大小。所以我通常会用一个Map<chunkIndex, number>来记录每个分片已经上传的字节数,然后每100ms汇总一次,触发Vue的响应式更新。

4.5 失败重试与暂停恢复:什么该自动,什么该给人决定

弱网环境下分片上传失败是常态,DEMO里必须验证重试策略。我的原则是:

  • 网络请求失败(超时、断连):自动重试,最多3次,退避策略采用指数退避,第一次等1秒,第二次等2秒,第三次等4秒。
  • 服务端返回业务错误(如分片校验失败):不自动重试,直接报错提示用户。因为业务错误往往意味着协议的bug或数据损坏,重试也没用。
  • 用户主动暂停:暂停状态下,queue和active里的任务立即停掉(利用AbortController),但已传成功且收到确认的分片保留。恢复时,重新调用预检接口,把已上传的分片序号拿回来,只传剩余部分。

暂停恢复的代码里最容易踩的坑是:axios的CancelToken已经废弃了,要用AbortController。每个请求创建时绑定一个signal,暂停时触发abort,浏览器会自动断开这个HTTP请求,后端收到的就是不完整的请求,但不会写坏文件,因为后端只有在完整收到某一个分片的所有字节后才会记录"该分片已上传"。

5. 后端配合与联调:接口约定不能拍脑袋定

5.1 服务端接口的最小集:预检、单分片、合并、进度查询

前端做得再好,后端接口设计不合理也白搭。DEMO阶段,后端只需要提供四个接口:

接口方法路径作用
预检POST/api/upload/check传文件Hash,返回已上传分片、分片大小、uploadId
上传分片POST/api/upload/chunk上传单个分片,携带uploadId、分片序号、文件blob
合并POST/api/upload/merge通知后端可以合并分片,返回最终文件访问地址
进度查询GET/api/upload/progress/:uploadId查询服务端收到的分片列表,可用来校准前端进度

这四个接口缺一不可。很多开发图省事,把"合并"和"最后一片上传"合并成一个动作,即最后一片传上去后后端自动触发合并。听着挺聪明,实际上很危险:最后一片传到了,但结合前面的分片还没有完全落盘,误触发合并就会拿到不完整的文件。宁可多请求一次,也不要合并时机含糊。

5.2 分片落盘与合并校验:如何避免合并出损坏文件

服务端收到每个分片后,必须做两件事:一是校验分片序号是否在合法范围内,二是校验分片的实际字节数是否和文件头记录的一致。金融场景对文件完整性要求极高,我甚至在DEMO里就让后端为每个分片额外计算一次CRC32或MD5,和前端随分片携带的校验值比对,不匹配直接拒绝。

分片落盘策略我推荐"临时文件块"方案:

  • 每个上传任务在存储目录下创建/uploadTemp/{uploadId}/目录
  • 分片按chunkIndex命名,例如000.part、001.part
  • 每个分片写入完成后,记录一条分片完成的元数据(文件路径、字节数、校验值)
  • 合并时,按分片序号顺序,把每个.part文件按二进制拼接成大文件

合并过程有一个性能问题必须提前知道:如果直接用fs.readFileSync挨个读再写,几百个分片性能很差。建议用流式管道,例如Node.js里的fs.createReadStream配合pipe方法。另外合并完成之后,要对整个文件再算一次Hash,和前端算出的文件Hash比对,一致才算合并成功。这一步每个金融项目都不能省,否则文件内容在传输过程中被篡改或损坏,等业务跑起来才发现就晚了。

5.3 网关、超时、限流:联调阶段最常见的三个坑

第一个坑是代理网关的body大小限制。很多金融机构的前端请求要经过统一的API网关,网关默认限制POST请求体大小,通常只有几MB。分片方案本身已经把单次请求体控制在分片大小以内,但如果你的分片是64MB,网关照样拦。要么把分片改小,要么让运维给这个上传接口单独放开限制。DEMO阶段我就吃过亏,调了一天发现所有分片都超时,后来抓包看到网关直接返回413 Payload Too Large。

第二个坑是请求超时时间。一般业务接口的网关超时是30秒,但对上传分片来说,即使是8MB的分片,在弱网下完全可能传超过30秒。所以联调时一定要确认这个上传接口的超时时间单独调长,通常是2到5分钟。前端axios里的timeout也要相应调长,不然你自己的请求先断了。

第三个坑是并发连接数。如果生产环境走的是HTTP/1.1,且前面还有Nginx,那并发4个分片已经是比较稳妥的上限。并发数不是越大越好,越大越容易触发服务端的连接数限制和Nginx的worker_connections瓶颈。DEMO里建议从3开始压,再逐步调大,结合服务端监控的CPU和内存表现找一个平衡值。

6. 金融合规视角:DEMO阶段就要把加密、审计、权限设计进去

6.1 传输与存储加密:不是"加了HTTPS"就万事大吉

很多开发认为"HTTPS上了,传输就安全了",这在金融合规面前明显不够。HTTPS保护的是链路,但文件到了服务端之后,如果不做应用层加密,任何能接触到磁盘的人都可以直接拿走原始文件。

金融场景常见的做法是:

  • 链路层:全链路HTTPS,内部网关再做双向TLS。
  • 应用层:敏感文件在上传前,由前端(或用服务端下发密钥)用国密SM4或AES-256-GCM对分片内容进行加密;服务端存的是密文,读取时再解密。
  • 存储层:对象存储开启服务端加密(SSE)或KMS密钥管理,确保磁盘上的文件也是密文。

在DEMO里,我不会让前端真的做完整加密——因为浏览器侧加密性能有限,一GB文件逐片加解密会非常慢。更合理的做法是让加密集中在服务端入口统一处理,即前端上传的是原始分片,服务端在接收到之后、落盘之前执行加密。这样既保证了存储安全,又不影响前端的传输性能。

但这里需要特别说明:如果业务要求文件在传输过程中任何节点都不可见明文,那就只能前端加密、服务端解密存储。代价是CPU消耗和复杂度大增,DEMO阶段至少要把这条链路的性能跑出一个参考数据。你可以在DEMO验证报告里列一张表,记录加密与不加密情况下,1GB文件总耗时的差异,让业务方来做决策。这才是金融人该有的做事方式:用数据说话,而不是空谈合规。

6.2 审计日志:上传流程里必须记的九个字段

金融行业的审计要求一般会追溯到"何时、何人、何操作、结果如何"。上传模块的审计日志,我在DEMO里预留了这些字段:

字段说明
操作人ID当前登录用户唯一标识
操作时间开始上传时间
文件名原始文件名
文件大小字节数
文件Hash完整性校验用
uploadId关联到具体上传任务
分片总数总片数
成功分片数最终成功的分片数
上传结果成功/失败/暂停/超时重试次数等

日志的采集点建议埋在上传流程的关键节点上:预检通过、第一个分片开始、最后一个分片完成、合并且校验成功、任一分片失败重试、最终异常终止。这些日志不要只记异常,正常流程也要记,否则审计方无法核对"为什么这个任务中间断了一天"。日志上报推荐的模式是:前端异步批量上报,不阻塞上传主流程,如果审计接口挂了也不能影响业务。

6.3 权限与身份传递:前端不要自己决定"谁能传"

金融系统通常已经接入了统一权限中心,前端在上传前要通过HTTP头或cookie把登录态传递过去,由后端来校验这个用户有没有上传权限、文件类型是否在白名单里、目录是否允许写入。前端千万不要做"根据角色hide上传按钮"这种逻辑就以为安全了,因为接口是可以被直接调用的。DEMO里的做法是:用户选择文件后先调预检接口,预检接口内部完成权限校验,没有权限就直接返回403,前端提示"无上传权限,请联系管理员",根本不会进入分片上传流程。

文件类型校验同理:不能只在前端用suffix判断,服务端必须用文件魔数(Magic Number)等二进制特征识别真实类型。为什么?很简单,一个攻击者可以随便把一个exe文件改名为.pdf传上来,前端看到.pdf就放行,但服务端如果也只看后缀,那就出了大问题。DEMO阶段我就在服务端预检接口里加了对常见金融文件类型的魔数校验,比如PDF的%PDF、ZIP的PK、Excel的D0CF11E0等。这个设计并不复杂,但能堵住非常大的一类风险。

7. DEMO实测与踩坑实录:从能跑到能用的距离

7.1 我跑的三个典型测试场景和结果

DEMO写完之后,光看"能传上去"还不算完,你得用真实场景去压它。我记录过三个有代表性的实验:

第一个场景是500MB的银行流水文本文件,普通内网环境,带宽约50MB/s。分片大小为8MB,并发3。实测结果:总耗时约11秒,CPU和内存都平稳,整个上传过程页面无卡顿,因为Hash计算在Worker里,上传也是异步并发。这个场景验证了基本性能。

第二个场景是模拟弱网:用Chrome DevTools的网络面板把带宽限到1MB/s,并设置20%的请求失败率。结果很快暴露了一个问题:失败重试的退避逻辑没加抖动脉冲(jitter),多个分片同时重试导致服务端瞬时压力飙升。后来在重试间隔上加了随机抖动,压力才降下来。

第三个场景是强制中断:2GB的压缩包传到60%时,直接把电脑休眠再唤醒。恢复后重新进入页面,选择同一个文件,预检接口返回了已上传分片列表,前端精准跳过60%的分片,只传剩下40%,最后合并校验的Hash和前端计算的一致。这个场景如果过了,断点续传的核心价值才算真正被验证。

7.2 我在DEMO里踩过的五个具体的坑

第一个坑是Blob.slice在火狐浏览器的兼容性问题。早期火狐不支持blob.slice(),要用blob.mozSlice()来兼容。现在主流浏览器都支持了,但如果你要兼容老旧政企环境里的浏览器内核,还是得做特性判断。

第二个坑是FileReader在读取超大文件时的内存问题。如果你一次性把整个500MB文件readAsArrayBuffer,浏览器内存直接飙升,甚至崩溃。正确做法是分片读取,就像我在Worker里写的那样,每次只读2MB。这和上传分片是两个维度的事情,别混淆。

第三个坑是FormData.append('file', blob, filename)时,如果blob的type为空,某些服务端框架可能无法正确识别Content-Type。我后来都手动给blob指定type: 'application/octet-stream',避免后端解析错误。

第四个坑是并发数为1时反而不稳定。听起来反直觉,但实际情况是:某些浏览器在串行上传时,如果前一个请求因为网络原因pending住,后面的请求永远发不出去。加一点并发反而能通过多路传输规避单连接被掐断的问题。

第五个坑是进度条回退。服务端合并分片也需要时间,如果合并完成后才置进度100%,用户在传完最后一片后会看到进度条卡在99%好几秒。我处理的方式是:第一,合并前先把进度置为100%,文案显示"等待服务端合并";第二,服务端在merge接口里返回一个任务状态字段,前端轮询或等通知后再更新状态。用户感知会好很多。

7.3 验收时建议用到的一套完整检查清单

DEMO阶段结束、准备进正式开发前,我建议团队过一遍这个清单,逐项打钩:

  • 功能类:1GB以上文件能完整上传;断网恢复后能续传;重复上传同一文件能秒传;暂停之后能恢复;删除文件后重新选择不影响任务。
  • 性能类:上传超大文件时页面不卡顿;内存占用不超过500MB(以目标浏览器为准);并发分片数可配置,且能稳定运行;日志上报不拖慢主流程。
  • 安全类:无权限用户被预检接口拒绝;上传接口限制了文件类型;文件名带有危险字符(如../、..\\)被拦截;分片内容校验失败能重传或终止;所有关键操作已产生审计日志。
  • 兼容类:Chrome最新两个版本、Edge、火狐、企业内网常用的国产浏览器(如奇安信、360安全浏览器)都能正常上传。
  • 故障演练类:手动杀掉服务端进程,观察前端是否会自动重试并保留已传分片;重启服务端后,能通过预检接口找回进度。

这套清单是我从多个金融项目里总结出来的,直接拿去用不会错。每过一项就代表DEMO离"能上生产"近了一步。

8. 后续演进的可能性:从"能用"到"好用"的几点建议

DEMO只是起点,真正落地到业务系统里,还有几件事值得投入。第一是文件的秒传策略可以做得更细,比如按文件Hash做内容去重,不仅同一个用户重复传可以秒传,不同用户传相同文件也可以秒传。金融机构的很多报表都是系统生成的,内容相同的概率极高,去重能省下大量存储和带宽成本。

第二是上传任务的持久化。如果业务系统需要展示"历史上传记录""失败任务重新提交",上传任务的关键状态就要落到业务数据库里,而不只是靠服务端临时目录里的分片状态推断。我建议在上传任务结束时,把uploadId、文件Hash、结果等同步一份到业务系统的文件管理表里,这样后续要做查询、统计、导出门槛都很低。

第三是断点续传和任务列表结合。用户传了一半退出系统,下次进来应该在哪里看到这个未完成任务?体验好的产品会有一个"草稿箱"式的待传列表,点击就重新上传。这需要前端把未完成任务持久化到localStorage或者IndexedDB,重新打开页面时读取并调用预检接口恢复。这个功能在移动展业场景下特别有用,推销员在客户那里拍到一半影像资料,回到办公室连上WiFi可以接着传。

至于要不要上WebRTC点对点传输、要不要接对象存储的多段上传接口(比如S3 Multipart Upload),这些属于更进一步的话题,取决于你的部署环境。但我始终认为,先把"分片、续传、校验、审计"这套基础能力做扎实,后面的优化才有资格谈。金融行业最忌讳的就是花架子,一个能稳定跑通全链路、出了问题有日志可查、被人质疑安全能拿出证据的DEMO,比一百个花里胡哨的界面都值钱。

我自己的体会是,做这类项目最怕的不是技术难点,而是"不知道要验证什么"就闷头写代码。先把上面那七个问题列出来,再对着设计做DEMO,每一步都有验证的抓手,后期生产环境的坑自然就少了大半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询