WebUploader与PHP实现分片断点续传:能源化工监控视频上传实战
2026/9/24 23:19:17 网站建设 项目流程

监控视频上传这件事,放在能源化工行业里,远比想象中要棘手。厂区点位多、视频文件动辄几个GB甚至几十GB、网络环境复杂,再加上安全环保监管对视频留存时限的硬性要求,一套稳定可靠的上传链路就是整个视频管理平台的地基。我在多个项目里用过不少方案,目前最顺手、也最适合这类企业存量技术栈的,还是 WebUploader + PHP 的分片断点续传组合。这篇文章我把自己从架构设计到前后端实现、再到上线后排障的全过程整理出来,给正在做类似系统的朋友一个能直接参考的样本。

1. 为什么能源化工企业的监控视频必须用分片断点续传

1.1 监控视频文件的特殊性决定了上传方式

先说清楚监控视频这类文件到底特殊在哪。能源化工企业的监控视频来源非常杂:有厂区固定点位的安防摄像头、有DCS系统联动的工艺录像、有移动布控球机的临时录制,还有无人机巡检拍回来的素材。它们的共同点是单文件体积大、码率高、录制时间长。拿一个1080P摄像头举例,按H.264编码、4Mbps码率算,录一个小时大概是1.8GB,一个厂区一天的录像轻松到几十GB甚至更多。

关键是这些视频不只是存下来就完事。重大危险源监管要求视频保存期限不少于30天,有些重点装置的要求更长。视频归档后还要支持追溯、抽帧取证、快速定位到某个时间段,这意味着上传、存储、索引是一条完整的链路,任何一环出问题都会直接影响后续的业务使用。

如果按传统方式处理,一次POST一个完整文件,最直接的后果就是:网络稍微抖一下,或者操作员不小心关掉了浏览器标签页,整个文件就废了,又得从头再来。在跨厂区远程上传、弱网环境下,这种失败几乎是必然的。我早期项目里就吃过这个亏,运维人员传一个8GB的文件,传了三个小时,断了一次网,一切归零。

1.2 分片断点续传到底解决了什么痛点

分片断点续传的核心逻辑,是把一个大文件切成若干小块,每一块独立上传,服务端记录哪些块已经收到。下次继续传的时候,服务端告诉前端“哪几块已经有了”,前端只需要补传缺失的部分。这样一来,即使断网、页面崩溃、甚至关机重启,最多只需要重传最后几个分片,而不是整个文件。

这个思路用一个生活化类比解释就是搬家:一整面书架不能一次性搬过去,路上碎了一块就前功尽弃;但把书一箱一箱搬,中途坏掉一箱,只需要重新搬那一箱,整体进度不受影响。放到监控视频的场景里,价值非常直观:

  • 几十GB的文件可以分多次、分时段传完,充分利用闲时带宽。
  • 网络中断后恢复成本极低,实测续传比整体重传能节省90%以上的流量。
  • 上传进度可视化,操作人员能直观看到当前状态,不会因为长时间没有反馈而误判失败。
  • 操作员可以主动暂停,比如下班前暂停上传,第二天上班继续,系统不需要从头来过。

1.3 技术选型:为什么是 WebUploader 搭配 PHP

有人会问,现在方案这么多,对象存储自带分片上传、MinIO、OSS SDK,为什么还要用 WebUploader + PHP 自己搭?这个问题得分场景看。

WebUploader 是百度FEX团队开源的上传组件,基于HTML5实现,兼容性做得好,即使在老一点的浏览器环境也能退回到Flash模式(现在基本都用HTML5了)。它内置了分片、并发控制、队列管理、进度回调这些能力,前端不需要从零写一套上传调度逻辑,而且这个组件不绑定后端语言,按约定的接口格式喂数据就行。

后端选 PHP,主要原因是能源化工企业的IT系统存量里,PHP应用非常多。很多厂区的内部管理系统、OA、运维平台都是PHP写的,统一技术栈,团队维护成本低。PHP处理分片上传并不吃力,关键点只要落在文件二进制安全操作、并发控制、状态记录上,性能完全够用。

我的技术方案原则一向是:先看生产环境里有什么、团队会什么,再选最稳妥的路线。方案再先进,团队维护不了,落地就是一句空话。

2. 整体架构与分片续传的核心原理

2.1 前端与后端的完整交互流程

在贴代码之前,先把整条上传链路走一遍,心里有个全局图,后面看细节才不会乱。完整的一次分片上传,大致分六步:

  1. 前端拿到文件后,先计算整个文件的MD5值,作为这个文件的唯一指纹标识。
  2. 前端把MD5传给后端,发起“查询上传状态”请求,后端返回当前已存在哪些分片序号。
  3. 前端根据返回结果,把还没传的分片排进队列,按设定好的并发数逐个或同时上传。
  4. 后端每收到一个分片,先校验分片序号和大小是否符合约定,再写入临时目录。
  5. 所有分片都传完后,前端调用“合并文件”接口,后端把所有分片按顺序拼接成最终文件。
  6. 合并完成后,清理临时分片文件,把文件元数据(MD5、原始文件名、大小、上传时间、来源摄像头编号)写入数据库或元数据文件。

2.2 三个核心机制:分片、状态记录、合并

分片机制的核心在于约定一个固定大小的分片,前端用 Blob.slice 或 WebUploader 内部的分片能力按这个大小切割。服务端必须知道每块分片属于哪个文件、是第几个分片,才能在合并时按正确顺序拼接,而不是靠文件名排序这种不可靠的方式。这也是为什么MD5作为文件标识如此重要,它保证了同一个文件在不同设备、不同时间被识别为同一个上传任务。

状态记录机制是断点续传的灵魂,服务端用两种手段配合。一是接收分片时,在数据库或文件系统里记录“哪个文件ID、哪个分片序号已收到”;二是前端本地也会缓存上传进度。前端优先以服务端返回的状态为准,避免不同浏览器、不同设备之间的状态不一致。这里我强调一句:本地缓存只能用来提升体验,不能作为续传的依据,服务端状态才是唯一事实来源。

合并机制要特别注意二进制安全。PHP里最常见的坑就是先用 file_get_contents 把分片读成字符串再拼接,最后 file_put_contents 写出去。二进制内容一旦被当字符串处理,遇到特殊字节很容易出问题,轻则数据错乱,重则文件头损坏、视频无法解码。正确做法是用 fopen 以二进制模式打开目标文件句柄,然后 fwrite 逐块写入,这是我在生产中反复验证过的。

2.3 目录结构与数据存储设计

目录设计的原则是:临时分片目录和最终存储目录严格隔离。临时目录只放分片和合并过程中的中间数据,最终目录是正式的归档成品。分开的好处是方便做定时清理,避免临时分片混入正式存储,同时也方便做权限控制——临时目录不对业务开放,最终目录通过鉴权接口访问。

我常用的目录结构是这样的:

/data/video_upload/ ├── tmp/ │ └── {file_md5}/ │ ├── 0.part │ ├── 1.part │ └── 2.part ├── done/ │ └── {yyyy}/{mm}/ │ └── {file_md5}.mp4 └── meta/ └── {file_md5}.json

每个人的归档方式不一样,有的企业按摄像头编号归档,有的按时间归档,有的需要把视频和工艺参数关联,存储路径和元数据字段需要单独设计。但核心原则不会变:分片存储与最终存储隔离、元数据结构化、文件命名用MD5标识而不是原始文件名。原始文件名在能源化工场景下往往会带有中文、特殊字符、摄像头点位描述等信息,直接当存储文件名用,迟早会出路径问题。

3. 前端实现:WebUploader 的配置与实战

3.1 引入 WebUploader 与核心配置项

前端我用的是官方0.1.5版本,jQuery页面里直接引入 uploader.js,不再依赖Flash模式。初始化时最关键的一组配置项是 chunked、chunkSize、threads、fileVal、timeout。下面是一份可以直接拿去改的初始化代码:

var uploader = WebUploader.create({ swf: '/static/Uploader.swf', server: '/api/upload/chunk', pick: '#picker', accept: { title: 'Videos', extensions: 'mp4,avi,mov,mkv,flv', mimeTypes: 'video/*' }, chunked: true, chunkSize: 5 * 1024 * 1024, threads: 3, formData: { token: getToken(), scene: 'monitor_video' }, auto: false, fileVal: 'file', duplicate: true, timeout: 0 });

这几个参数重点解释一下:

  • chunked: 开启分片上传,这是整个方案的前提。
  • chunkSize: 每个分片的大小,我建议5MB或10MB。太小会大幅增加请求次数,浪费握手开销;太大又失去分片的意义,弱网重传成本高。
  • threads: 并发上传的分片数,建议3到5。太少浪费带宽,太多会打满服务端进程池,而且容易导致分片乱序。
  • fileVal: 后端接收文件的字段名,默认是file,保持和前端一置即可。
  • duplicate: 允许重复选择同一个文件,否则同名文件会被过滤掉,影响续传操作。
  • timeout: 建议设置为0,不启用超时。分片上传本来就是应对长时间传输的,超时反而会成为中断的诱因。

3.2 分片大小与并发数到底怎么选

分片大小不是拍脑袋定的,我做过多轮实测。在网络带宽受限、往返时延较高的跨厂区场景下,5MB分片比1MB分片整体吞吐高不少,因为HTTP请求次数减少了,握手和响应等待时间被摊薄。但如果网络差到偶发丢包率偏高,1MB分片的重传成本又比5MB低。折中下来,5MB是大部分场景下比较稳妥的取值。

并发数则要考虑服务端能同时打开的文件句柄数和数据库连接数。PHP-FPM默认进程池配置下,并发太高会把进程全部占满,导致后面的状态查询和合并请求都排不上队。我生产上用的是3并发,普通虚拟机配置下,日上传几百个文件毫无压力。

还有一个细节:如果确定了分片大小,总分片数也就能估算出来。服务端在接收分片时,强烈建议校验前端传上来的 chunks 总数和分片实际大小是否合理。比如固定5MB分片,最后一个分片可以小于5MB,但前面任何一个分片如果等于或大于5MB,基本可以判定数据有问题,直接拒绝。

3.3 断点续传关键:MD5指纹与已上传分片查询

WebUploader 官方并不直接提供断点续传的完整实现,它只是一个上传调度器。真正的续传逻辑需要我们在发送分片前,先和后端做一次状态同步。我的做法是:文件加入队列后、正式传之前,先对整个文件计算MD5,然后通过 before-send-file 钩子把MD5传给后端,获取后端返回的已上传分片序号数组。

计算几十GB文件的MD5是有耗时的,这个成本避不开。为了不让用户干等着,我通常加一个loading提示层,显示“正在计算文件指纹,请稍候”。

uploader.on('before-send-file', function(file) { var deferred = WebUploader.Deferred(); uploader.md5File(file, 0, 10 * 1024 * 1024) .then(function(hash) { // 超大文件可以分段计算,这里简化为整体MD5 file.md5 = hash; uploader.option('formData.md5', hash); $.post('/api/upload/status', { md5: hash }) .done(function(resp) { if (resp.code === 0) { // 告诉上传器跳过这些已上传的分片 uploader.skipFile(file, resp.data.uploadedChunks); } deferred.resolve(); }) .fail(function() { deferred.resolve(); }); }); return deferred.promise(); });

skipFile 这个方法很关键,它告诉上传器哪些分片不需要再传了。后续那些缺失的片会自动进入传输队列。这样一来,前端不需要自己维护复杂的分片重传逻辑,续传体验非常顺滑。

还要注意一个效率细节:MD5计算过程中,如果用户等得不耐烦又选了一次同一个文件,可能会导致重复计算。我会在文件选择阶段先用“文件大小+文件名”做一次本地缓存标记,同一批操作里不重复计算MD5。

3.4 进度展示与队列交互的经验

WebUploader 默认提供了进度事件。上传面板上我一般展示四样信息:整体进度条、瞬时网速、已传大小/总大小、剩余分片数。progress 回调里可以拿到三个关键值:file.size 是总大小,file.loaded 是已加载大小,file.uploaded 是当前分片内已上传字节数。把这些值组合起来,渲染出来的进度条既能看到当前分片的传输速度,又能让用户清楚整体还差多少。

暂停和取消按钮也必须接上,使用 uploader.stop(true) 可以暂停整个队列。断点续传的价值不只是应对网络故障,操作员主动暂停、第二天回来继续传,也是高频需求。我见过一个场景:厂区夜间带宽空闲,操作员下班前把当天所有录像拖进队列,点暂停,凌晨再远程点继续,第二天早上所有视频都归档完了。

4. 后端实现:PHP 处理分片接收与合并

4.1 接收分片的接口设计

后端接收接口要同时处理两个信息维度:文件标识和分片序号。WebUploader 约定好的字段有三个:md5(文件指纹)、chunk(当前分片序号)、chunks(总分片数),这些字段前端发送时会自动带过来。后端按约定读取即可,不需要发明新的协议。

一个可靠的分片接收接口,至少需要做这几件事:

  1. 校验必传参数:md5、chunk、chunks、上传文件。
  2. 校验文件的MIME类型和后缀名,拒绝非视频内容。
  3. 按MD5建临时目录,目录不存在则创建。
  4. 把分片移动到临时目录,命名为 {chunk}.part。
  5. 更新分片状态记录。
  6. 把最新的已上传分片列表返回给前端。

核心代码如下:

public function uploadChunk() { $md5 = $this->request->post('md5'); $chunk = (int)$this->request->post('chunk', 0); $chunks = (int)$this->request->post('chunks', 1); $file = $this->request->file('file'); if (!$md5 || !$file || $chunk >= $chunks) { return $this->fail('参数错误'); } // 校验文件类型 if (!$this->checkVideoFile($file)) { return $this->fail('不支持的文件类型'); } $tmpDir = UPLOAD_TMP_DIR . '/' . $md5; if (!is_dir($tmpDir)) { mkdir($tmpDir, 0755, true); } $chunkFile = $tmpDir . '/' . $chunk . '.part'; $file->moveTo($chunkFile, true); // 更新分片状态记录 $this->markChunkUploaded($md5, $chunk, $chunks); return $this->success([ 'md5' => $md5, 'chunk' => $chunk, 'chunks' => $chunks, 'uploadedChunks' => $this->getUploadedChunks($md5, $chunks) ]); }

有两个非常容易踩的坑。第一,分片落盘后的临时文件名一定不要用原始文件名拼接,否则遇到中文、空格、特殊符号会造成路径问题。第二,分片目录必须按MD5深度隔离,防止不同文件的分片互相覆盖,一旦覆盖就是文件损坏的根源。

4.2 分片状态记录:文件标记还是数据库

分片状态记录有两条路线。一条是建数据库表,字段包含file_md5、chunk_index、create_time、file_size;另一条是直接在临时目录里维护一个状态文件,记录当前已收到的分片序号。数据库方案适合多实例、分布式部署,状态可以在多台机器之间共享;文件标记方案适合单机部署,简单直接,不依赖数据库连接。

我在生产环境用单机落地,选择的是文件标记方式,每个MD5目录里维护一个 status.json:

{ "md5": "abc123...", "total_chunks": 2458, "uploaded_chunks": [0, 1, 2, 3, 5, 6, 7, 8], "last_update": 1700000000 }

每次收到分片就更新这个文件。查询状态接口只需要读这个文件返回给前端。要注意并发写入的问题:多个分片并发上传时,如果同一时刻有两个请求同时更新同状态文件,可能写坏或丢失数据。我用的是 flock(LOCK_EX) 文件锁,确保同一时间只有一个请求在更新。

4.3 合并分片:二进制安全是第一原则

合并接口在收到前端“所有分片已上传”的请求后触发。这个环节老生常谈的问题就是二进制安全。很多PHP开发习惯用 file_get_contents 读分片、拼接成字符串、再 file_put_contents 写出去。这种做法在小型文本文件上碰巧没问题,但视频文件里各种非文本字节一旦多起来,大文件合并后极大概率出现文件头损坏或解码失败。

正确的合并方式是流式写入:

public function mergeChunks() { $md5 = $this->request->post('md5'); $chunks = (int)$this->request->post('chunks'); $tmpDir = UPLOAD_TMP_DIR . '/' . $md5; $finalDir = UPLOAD_DONE_DIR . '/' . date('Y/m'); $finalPath = $finalDir . '/' . $md5 . '.mp4'; // 确保所有分片都到齐 for ($i = 0; $i < $chunks; $i++) { if (!file_exists($tmpDir . '/' . $i . '.part')) { return $this->fail('缺少分片:' . $i); } } if (!is_dir($finalDir)) { mkdir($finalDir, 0755, true); } $fp = fopen($finalPath, 'wb'); for ($i = 0; $i < $chunks; $i++) { $partPath = $tmpDir . '/' . $i . '.part'; $partFp = fopen($partPath, 'rb'); while (!feof($partFp)) { // 一次读1MB写入,避免大分片占用过多内存 fwrite($fp, fread($partFp, 1024 * 1024)); } fclose($partFp); } fclose($fp); clearstatcache(); $finalSize = filesize($finalPath); if ($finalSize <= 0) { return $this->fail('合并失败,文件为空'); } // 记录文件元数据 $this->saveMetaInfo($md5, $finalSize); // 清理临时分片 $this->cleanTmpDir($tmpDir); return $this->success([ 'md5' => $md5, 'size' => $finalSize, 'url' => $this->getFileUrl($md5) ]); }

这里我用 fread 每次只读1MB再写入,而不是一次性把整个分片加载进内存。5MB分片单看不吓人,但并发多时每个请求都全量加载内存,PHP-FPM的内存占用会非常难看。流式写入既兼顾了内存,又保证了二进制的原样落盘。

4.4 断点续传状态查询接口

状态查询接口的逻辑很简单:根据前端传来的MD5,返回这个文件当前已收到哪些分片序号。前端拿到列表后跳过已传分片,只补传缺失部分。

public function uploadStatus() { $md5 = $this->request->get('md5'); $chunks = (int)$this->request->get('chunks', 0); $uploadedChunks = $this->getUploadedChunks($md5, $chunks); return $this->success([ 'md5' => $md5, 'uploadedChunks' => $uploadedChunks, 'isCompleted' => count($uploadedChunks) === $chunks ]); }

如果 isCompleted 为 true,说明这个文件已经完整上传过了,前端可以直接提示用户,不需要重复上传。这个对“操作员手滑重新选择了历史视频”的场景很有用,同时也给服务端省掉了无谓的存储。

4.5 权限校验与文件类型安全

能源化工企业的监控视频属于敏感生产数据,上传接口不能裸奔。我至少做了两层校验:第一层是登录态校验,接口必须校验用户会话token,以及该用户是否有上传权限;第二层是业务权限校验,比如上传时附带摄像头编号,需要校验用户是否有该摄像头的上传权限。

文件类型安全也不能省。前端的 accept 限制只对操作体验有效,不能作为安全边界,因为请求可以绕过前端直接被构造。后端必须额外校验后缀名白名单、MIME类型、以及合并后文件的头部签名。

我一般用 finfo 或者直接读取文件头字节判断类型。MP4文件的头几个字节通常是 66 74 79 70(即"ftyp"),AVI是 52 49 46 46(即"RIFF"),如果文件头不符合任何视频格式特征,直接拒绝落地。这个检查在合并后做一次最稳妥,因为分片阶段的文件头不一定完整。

5. 实测过程与关键参数选择

5.1 测试环境与测试目标

这套系统上线前,我在测试环境做了充分验证。服务器是CentOS 7.9,4核8G,PHP 7.4,Nginx 1.20,WebUploader 0.1.5。前端是一台普通办公电脑,Chrome浏览器。测试文件是某厂区球机录制的1080P视频,大小约12GB,时长3小时,H.264编码,MP4容器。

测试目标很明确:12GB文件能否完整传完、模拟断网后断点续传是否真正生效、分片并发下服务端是否稳定、合并后的视频能否正常播放、时间轴是否连续。

5.2 分片大小和并发数的实测对比

我把网络限到2Mbps模拟弱网,分片大小分别设1MB、5MB、10MB,并发数分别用1、3、5,做了几组对照。结论整理成一张表:

分片大小并发数12GB文件整体耗时断点恢复后的最大重传量服务端负载感受
1MB3较长,请求数过多最多重传1MB日志和IO频繁
5MB3较平稳最多重传5MB均衡
10MB5速度较快最多重传10MBCPU偏高

没有绝对最优的组合,只有最适合自己环境的参数。内网带宽充足、网络稳定时,可以适当调大分片和并发;跨公网、跨区域的弱网环境,建议保守一点,5MB加并发3是经过反复验证的稳妥组合。

5.3 模拟断网断点续传的完整流程

模拟断网我用了两种方式:直接拔网线,以及Chrome开发者工具里把Network切到Offline。12GB文件传到约45%位置断开网络,恢复网络后,前端自动发起状态查询,服务端返回“第0到第562块已上传、剩余分片序号列表”(按5MB分片,总共约2458块)。前端调用 skipFile 跳过已传分片,直接从第563块继续传。整个续传衔接大概10秒内完成,没有从头再来。

这个结果对运维人员来说,体验提升是质变的。以前断一次网,之前几个小时的传输全部作废;现在断网恢复,基本无缝衔接。

还有一个容易被忽略的运维细节:断网时间如果很长,比如隔了一夜才恢复,服务端临时目录会残留大量不完整的分片MD5目录。我写了一个定时任务,清理超过24小时还没有触发合并的临时目录,同时保留合并操作日志,方便追溯。

5.4 业务层面要注意的上传时段规划

能源化工企业带宽资源有限,监控视频归档又不能占用正常办公网络。我在实施时还会在业务侧做一层调度:设置上传时段白名单,默认允许24小时上传,但优先级和限速策略不同。白天办公时段限制上传带宽,避免影响MES、OA等业务系统;夜间放开带宽,大批量历史录像在闲时完成归档。这个策略不需要改代码,在Nginx层做限速即可实现。

6. 常见问题与排查技巧实录

6.1 常见问题速查表

项目上线运维大半年,我整理了一份高频问题速查表,这里直接分享:

现象可能原因排查方法解决方案
上传到一半提示网络异常请求超时或连接被重置查看Nginx error.log和PHP-FPM日志调整Nginx超时参数,设置WebUploader timeout为0
断点续传无效,总是从头传MD5前后端计算不一致打印前端hash和服务端存储的md5对比统一计算格式、大小写,避免编码差异
合并后视频无法播放分片顺序错乱或用了字符串拼接检查合并代码是否使用fopen/fwrite改用二进制流式合并
状态文件损坏并发更新写冲突查看status.json内容是否残缺加flock文件锁
服务器磁盘被临时分片占满缺少清理机制查看tmp目录下文件大小写定时任务定期清理
上传接口被非授权调用缺少权限校验查看访问日志中接口来源增加token鉴权和IP白名单
页面提示文件数超出限制WebUploader文件数默认限制查看console报错设置fileNumLimit

6.2 分片合并后文件损坏的经典场景

有个现场印象很深。合并后的MP4文件大小完全正确,但播放到某个时间点画面花屏,然后整个文件崩溃。一开始怀疑源视频有问题,后来逐块比对每个分片的哈希,发现某块分片在传输过程中出现了损坏。

根因是上传接口默认没有对分片做内容校验。前端把分片传上去后,后端只记录了“收到”,没有校验“收到的内容是不是正确的”。网络协议虽然保证传输层的完整性,但中间经过路由器、代理、防火墙,极端情况下仍可能出现数据损坏。我的补救方案是:前端发送分片时,把每个分片的MD5作为附加参数带上,后端收到分片后重新计算一次MD5,不一致直接拒绝并让前端重传该分片。加上这层校验后,再没出现过合并后文件损坏的问题。

6.3 断点续传失效的另一个原因:文件被重复选中

还有一个很隐蔽的坑。操作员在续传时,如果不小心重新选择了一次文件,WebUploader默认会把它当新文件处理,重新计算MD5,之前的进度全部作废。

解决办法是在 before-send-file 钩子里加判断:如果同一个MD5已经在上传队列中存在,且状态是暂停或等待中,就不重复创建任务,而是定位到原任务继续。这个逻辑看起来不起眼,但实际生产中很多用户不会刻意区分“续传”和“重新上传”,少了这一步,断点续传形同虚设。

6.4 大文件上传的内存与超时问题

PHP默认配置里,memory_limit、upload_max_filesize、post_max_size 经常让人误以为不能传大文件。分片上传场景下,单次请求只传一个分片,所以这几个参数不需要调很大。我通常把 upload_max_filesize 调到32M、post_max_size 调到35M,足够容纳5MB分片加上额外表单字段的余量。

Nginx侧要关注 client_max_body_size。这个值如果设置过小,大于该值的分片请求会直接返回413错误。我一般设置成 client_max_body_size 100m,给分片大小留出充足余量。PHP-FPM的 request_terminate_timeout 也建议调大或者直接设为0,防止批量上传时请求处理时间超过限制而被强制杀掉。

6.5 弱网专项:重试机制和动态分片

项目如果覆盖厂区间的跨网传输,弱网优化还要多做几件事。第一,开启WebUploader的失败重试机制,send失败后自动重试,我设置为3次。第二,观察一段时间内的平均传输速度,如果长时间处于低速,就把后续分片大小动态降低。虽然请求数增加了,但每个分片的重传成本变小,整体成功率明显提升。第三,给断线重连设置合理的退避时间,避免同时有大量客户端恢复后疯狂重试,把服务端打垮。

7. 安全与运维:这些细节必须在生产环境落实

7.1 上传接口的安全边界

除登录态校验和文件类型校验外,还有几个安全细节要一并做掉。上传接口要加频率限制,防止恶意脚本对接口发起大量垃圾请求;临时目录不要放在Web根目录下,避免用户通过URL直接访问到别人的临时分片文件;最终存储目录的访问也不建议直接对外开放,如果需要在线预览视频,要走一层带鉴权的播放接口。

所有上传过程中的文件名、MD5、摄像头编号、上传时间都要记录日志。监控视频出了事故是要倒查的,没有日志就回答不了“这段视频是什么时候、由谁上传的、来源是哪台摄像头”这类问题。我在生产里是把日志单独归档,保留周期和视频存储周期保持一致。

7.2 磁盘空间与临时分片清理策略

临时分片合并完成后必须立即清理,不能拖。同时用crontab写一条定时任务,扫描临时目录,凡是目录最后修改时间超过24小时且没有合并记录的,直接删除。这个任务在凌晨跑,基本不干扰业务。

监控视频归档目录要接入企业现有的存储告警。可以写一个简单脚本,统计最终存储目录总大小,超过阈值发告警。磁盘满导致的连锁故障比网络故障更可怕,因为磁盘满后不只是上传无法继续,还可能拖垮正在运行的业务系统。

7.3 视频文件的后续处理方向

上传只是第一步。能源化工企业的监控视频通常还要做后续处理:抽帧生成缩略图、按时间段切分、转码成统一的分辨率和码率、与报警事件联动。我这边在合并完成后,会把任务丢进一个队列里,由后端的Worker进程去做转码和抽帧,不阻塞上传流程。

如果后续要做AI分析,比如安全帽识别、明火检测、区域入侵,文件落地以后还要对接分析平台。上传模块和算法分析是解耦的,上传模块只需要保证文件完整、格式正确、元数据齐全,至于分析系统的需求,属于另一条业务线。

最后分享一点我的个人体会。做这类功能,不要一上来就追求技术上的“完美解”。我见过一些团队为了显得架构先进,直接上微服务、消息队列、对象存储网关,结果项目落地后运维成本极高,出了问题没人能维护。分片断点续传的本质,就是把一个简单可靠的交互协议做扎实。先把核心链路的稳定性跑通,再逐步叠加功能,这才是能源化工这类对稳定性要求极高的行业里最务实的做法。

还有一个联调技巧:调试时务必抓包工具和浏览器开发者工具双开,重点观察分片请求的响应内容、上传状态字段,以及断网恢复后的第一次状态查询请求。我很多次靠这三条信息快速定位问题,几乎没有走过弯路。希望这篇梳理能帮到正在做同类系统的朋友。

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

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

立即咨询