服务端须用文件指纹(如md5/sha256)校验唯一性,前端计算并传递identify;thinkphp需手动处理分片接收、按序落盘、锁机制合并及及时清理。

分片上传时如何校验文件唯一性
服务端必须靠文件指纹(如 MD5 或 SHA256)识别重复上传,不能只依赖文件名或前端传来的 fileId。浏览器可伪造请求,name 和 size 都不可信。
- 前端计算整个文件的
md5(用spark-md5等库),在首片请求中传给后端作为identify字段 - 后端收到首片时,查数据库或 Redis 是否已存在该
identify对应的上传记录;若存在,直接返回已上传的分片列表,跳过重复写入 - 不要用
$_FILES['file']['name']做去重,同名文件可能内容完全不同 - ThinkPHP 的
Request::file()拿到的是临时文件,无法用于计算全量哈希——必须由前端计算并传递
ThinkPHP 接收分片并合并的正确流程
ThinkPHP 本身不内置分片处理逻辑,需手动解析参数、校验、落盘、合并。关键在于:分片必须按序存储,且合并前要确认所有分片已到位。
- 约定前端传参:
identify(文件标识)、chunkIndex(当前分片序号,从 0 开始)、totalChunks(总片数)、chunkSize(单片大小) - 服务端接收后,将分片存为
runtime/upload/chunks/{identify}/{chunkIndex},用moveTo()而非saveAs(),避免自动重命名 - 合并时机:当收到最后一片(
chunkIndex == totalChunks - 1)时,检查0到totalChunks - 1是否全部存在;缺片则返回409 Conflict,提示前端重传 - 合并用
fopen(..., 'wb')+fwrite()逐片追加,别用file_put_contents($file, file_get_contents($chunk), FILE_APPEND),大文件会爆内存
断点续传时如何返回已上传分片列表
前端初始化上传前,必须先发一个「查询上传状态」请求,后端据此决定是新建还是续传。这个接口不能只返回“是否完成”,而要返回具体哪些分片已存在。
- 接口路径建议为
/api/upload/status?identify=xxx,返回 JSON 如{"uploaded": [0,2,3], "total": 5} - ThinkPHP 中用
scandir()扫描runtime/upload/chunks/{identify}/目录,过滤出数字命名的文件,转为整型数组并排序 - 注意权限问题:
runtime目录需 webserver 可读,但不能被 HTTP 直接访问——务必在 Web 服务器配置中禁止访问/runtime/下任何路径 - 别用
Db::table('upload_records')->where('identify', $identify)->column('chunk_index')存分片索引,IO 压力大;文件系统本身已是天然分片索引
大文件合并后的清理与并发风险
合并成功后,临时分片目录若不及时清理,磁盘会迅速占满;多个请求同时操作同一 identify 还可能引发竞态。
- 合并完成后立即
rrmdir(runtime/upload/chunks/{identify}),别等定时任务——用户不会等你清理 - 用 Redis 的
SETNX对identify加锁,防止两个请求同时进入合并逻辑;锁过期时间设为 5 分钟足够 - ThinkPHP 的
Cache::store('redis')->set($key, $val, 300)可封装加锁逻辑,但注意set必须带['nx', 'ex' => 300]参数才原子 - 不要在合并过程中把最终文件写到
public/uploads/,而是先写到runtime/temp/,重命名为目标路径后再清理分片——避免出现半成品文件被外部访问
事情说清了就结束。真正难的不是写代码,是设计好 identify 的生成规则、锁粒度和清理触发点——这三处一错,上传服务就会在高并发下静默失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










