ThinkPHP 6.0实现可靠断点续传需闭环处理分片上传、状态持久化、校验与原子合并:前端按统一chunkSize切片并用MD5生成唯一chunkId,上传前校验存在性,成功后存localStorage;后端严格校验参数防路径穿越,动态接收分片落盘,用ON DUPLICATE KEY UPDATE更新状态,flock加锁合并并校验MD5,最后清理分片与状态。

要在ThinkPHP 6.0中实现真正可用的文件断点续传,必须让前端分片上传与后端状态持久化、分片校验、原子合并形成闭环——不是只写个uploadChunk方法就完事,而是要确保刷新页面后进度不丢、网络中断后能精准续传、并发上传不冲突、10GB文件不崩盘。
前端分片上传与断点状态保存
第一步:用File API按2MB切片,【chunkSize必须与后端配置严格一致】,否则合并时MD5校验失败,整个文件作废。切片代码里别硬写2097152,抽成常量或从后端API动态获取。
第二步:为每个分片生成唯一chunkId,推荐用file.name + file.lastModified + sliceIndex拼接后取MD5——【不可用Math.random()或时间戳单独生成】,否则同一文件多次选择会生成不同ID,导致已传分片被判定为“不存在”而重复上传。
第三步:上传前发GET请求/api/upload/check?chunkId=xxx&chunkIndex=0校验分片是否存在。服务端返回200就跳过,返回404才POST上传。这一步省掉90%以上的无效传输,尤其对Wi-Fi切换到4G时反复重试的场景至关重要。
立即学习“PHP免费学习笔记(深入)”;
第四步:上传成功后立即执行localStorage.setItem('upload_' + chunkId, JSON.stringify({index: i, status: 'success', time: Date.now()}))。别碰sessionStorage——它一刷新就清空,断点续传直接变重新上传。
TP6控制器接收分片并安全落盘
方法一:使用request()->file()接收单个分片
在app/controller/Upload.php中定义uploadChunk方法,接收chunkid、chunkindex、chunkcount、filename参数;【务必校验chunkindex是否为数字且≥0】,否则攻击者构造chunkindex=../../../etc/passwd可触发路径穿越。
方法二:手动读取php://input绕过TP6自动解析
当Nginx限制单次上传大小或分片超50MB时,改用file_get_contents('php://input')获取原始流,再file_put_contents写入runtime/chunks/{date}/{chunkid}/目录下,文件名格式为{chunkid}-{chunkindex}.tmp。注意自行做MIME白名单过滤,比如只允许video/mp4、application/pdf等业务所需类型。
服务端状态跟踪与分片合并
第一步:创建upload_status表,字段含id、chunkid、total_chunks、uploaded_count、status、updated_time。chunkid设为唯一索引,避免同名文件并发写入覆盖状态。
第二步:每次分片上传成功,执行INSERT INTO upload_status ... ON DUPLICATE KEY UPDATE uploaded_count = uploaded_count + 1语句更新计数。不要先SELECT再UPDATE——高并发下会漏计或重复计。
第三步:当uploaded_count等于total_chunks时,触发合并流程。先用flock()对目标文件加写锁,再按chunkindex升序读取所有.tmp分片,用fopen(, 'ab')追加写入最终文件。合并完成后立即校验最终文件MD5是否与前端传来的fileMd5一致,不一致则unlink目标文件并返回错误。
第四步:合并成功后,删除所有.tmp分片文件,并从upload_status表中DELETE该chunkid记录。同时清理localStorage中对应upload_XXX键值——这步漏掉会导致下次上传仍查到“历史成功记录”,但实际分片已被删,上传卡死在最后几片。



















