ThinkPHP分片上传需显式获取chunk参数,存入安全路径并校验索引与大小;合并前须双重验证分片完整性,流式写入防内存溢出;合并后通过文件MD5去重,并在事务中清理分片。

ThinkPHP 后端如何接收并校验分片文件
分片上传不是直接把整个文件扔给服务器,而是前端按固定大小切出多个 chunk,每个带编号和唯一标识(如 file_id 或 md5),后端要能独立接收、存临时目录、不覆盖、不混片。
- 必须用
$_FILES之外的方式接收:ThinkPHP 的request()->file()对单次上传友好,但分片常走POST表单或multipart/form-data中的普通字段传chunk内容,建议用input('chunk')+input('chunk_index')+input('file_id')显式取值 - 临时存储路径别硬写
./runtime/chunk/,用Env::get('root_path') . 'public/upload/chunk/'更稳妥,避免 runtime 被清空导致断点失效 - 文件名必须包含
file_id和chunk_index,例如{$file_id}_{$chunk_index}.part,否则多个用户同时传同名文件会互相覆盖 - 务必校验
chunk_index是数字、chunk_index >= 0,防止恶意请求写任意路径(如../../../etc/passwd)
合并前怎么确认所有分片已完整到达
不能一收到最后一个分片就合并——网络抖动可能让某个中间分片延迟到达,或前端误报总片数。必须主动检查“该 file_id 下应有 N 片,实际落盘 M 片”,且每片大小符合预期(最后一片可略小,其余必须严格等于切片大小)。
- 用
glob(Env::get('root_path') . 'public/upload/chunk/' . $file_id . '_*.part')列出全部分片文件,再用count()比对total_chunks - 逐个
filesize()校验:除最后一片外,其余必须等于前端传来的chunk_size(比如 5242880 字节),否则是传输损坏 - 别依赖前端传的
total_chunks值做唯一判断——它可能被篡改,应以实际文件数量 + 文件大小双重验证为准 - 建议加个超时清理:查到某
file_id的分片 2 小时内没收齐,就删掉已有分片,避免磁盘被碎片占满
合并文件时 PHP 的内存与超时风险怎么绕开
用 file_get_contents() 读所有分片再 file_put_contents() 写入,1GB 文件就可能爆内存;用 fopen() + fwrite() 流式合并更安全,但要注意文件锁和追加顺序。
- 绝对不要用
file_get_contents($chunk)把整个分片读进内存——哪怕只有 5MB,100 片就是 500MB - 正确做法:用
fopen($target_file, 'wb')创建目标文件,然后对每个分片按chunk_index升序fopen($chunk, 'rb')→fread()分块(如每次 8192 字节)→fwrite()追加 - 合并过程中若出错(如某分片被删),要立刻
unlink($target_file),否则留下不完整文件干扰后续逻辑 - PHP 的
max_execution_time默认 30 秒,大文件合并很容易超时,应在合并前加set_time_limit(0),但必须配超时兜底(比如记录开始时间,每处理一片检查是否超 10 分钟)
如何防止同一文件重复上传成功
前端可能因网络重试多次触发“上传完成”请求,后端若没幂等控制,就会生成多个相同文件,甚至覆盖原文件。
立即学习“PHP免费学习笔记(深入)”;
- 核心是用文件内容指纹(如全量
md5_file())做最终校验,而不是靠file_id或文件名 - 合并完成后立即计算
md5_file($target_file),查数据库看该 md5 是否已存在;存在则删新文件,返回已有文件 URL - 注意:
md5_file()对 2GB+ 文件很慢,可先用分片md5拼接成“分片指纹”快速初筛,再对通过初筛的做全量校验 - 数据库里存
md5、original_name、size、upload_time四个字段就够了,别存路径——路径可能随部署变,而 md5 是唯一锚点
最易被忽略的是分片清理时机:必须在合并成功且 md5 去重完成后,才批量 unlink() 所有 .part 文件;少删一个,下次同名上传就可能误用旧碎片;多删一个,合并过程就中断。这个动作得包在事务性逻辑里,不能靠“大概率成功”的直觉。


















