必须在服务端用内容哈希校验音频文件完整性,因move_uploaded_file()不验证内容一致性,网络截断、nginx截断、nfs写入失败等均会导致文件损坏却返回成功。

上传音频时校验文件完整性,不能只依赖前端传来的文件名或 $_FILES['audio']['size'],必须在服务端用内容哈希做真实校验。否则中间网络截断、临时文件损坏、Nginx缓冲异常等情况都会导致“上传成功但音频无法播放”。
为什么不能只靠 move_uploaded_file() 成功就认为文件完整
PHP 的 move_uploaded_file() 只保证临时文件被移动到目标路径,不校验内容是否与原始上传一致。常见问题包括:
- 客户端上传中途断开,
$_FILES['audio']['size']显示 5MB,但实际tmp_name文件只有 3.2MB(error却仍是UPLOAD_ERR_OK) - Nginx 的
client_max_body_size或fastcgi_read_timeout触发截断,PHP 层无感知 - 共享存储(如 NFS)写入延迟或失败,
move_uploaded_file()返回 true 但磁盘上文件不全
用 hash_file() 校验上传后音频内容
必须在 move_uploaded_file() 后立即计算哈希,并与客户端传来的指纹比对(若支持);或至少保存服务端生成的指纹用于后续验证。
- 优先选
sha256或sha384:比md5_file()更抗碰撞,适合音频这类可能被篡改的媒体文件 - 校验前先确认文件可读:
if (!is_readable($tmpPath)) { /* 拒绝处理 */ } - 避免大音频阻塞请求:100MB 的 WAV 文件调用
hash_file('sha256', $tmpPath)可能耗时数秒,建议异步落库后校验,或加超时控制 - 示例代码片段:
if (move_uploaded_file($_FILES['audio']['tmp_name'], $finalPath)) {
$hash = hash_file('sha256', $finalPath);
if ($hash === false) {
unlink($finalPath);
die('音频文件损坏,哈希计算失败');
}
// 存入数据库或返回给前端
error_log("音频 SHA256: $hash");
}
配合客户端传入的指纹做双向验证
如果前端能生成并传递指纹(例如用 Web Crypto API 计算 SHA-256),服务端应严格比对,而非仅作参考:
- 客户端需在上传前计算整个音频 Blob 的
sha256,通过额外字段提交,如audio_hash=abc123... - 服务端接收后,**必须等
move_uploaded_file()完成且is_readable()为 true 后再计算**,否则比对无意义 - 不要用
$_FILES['audio']['name']或type做校验依据——它们完全可伪造 - 注意编码:客户端 base64 编码的哈希,服务端要用
base64_decode()转换后再比对二进制,或统一用 hex 字符串传输
分片上传场景下如何保证最终完整性
对于 >50MB 的音频,通常走分片上传,此时完整性校验要分两层:
- 每个分片上传后单独校验其哈希(防止单片损坏)
- 所有分片合并完成后,**必须对最终文件整体再算一次
hash_file()** —— 合并逻辑出错(如偏移写错、分片顺序乱)会导致文件结构错误,但单个分片哈希全对 - 推荐在合并后执行
ffprobe -v quiet -show_entries format=duration -of default=nw=1 $finalPath辅助验证:能正常解析时长,说明文件头和基本结构是完好的
真正容易被忽略的是:哈希校验必须发生在文件落盘完成、权限正确、且未被其他进程修改之后。哪怕只是多一个 sleep(0.1) 或少一次 clearstatcache(),都可能导致 hash_file() 读到旧缓存或截断内容。音频不像文本,损坏往往静默发生,直到用户点播放才暴露。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











