ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP大视频分段分享实战:分片上传、Range响应与HLS流媒体

PHP大视频分段分享实战:分片上传、Range响应与HLS流媒体 前阵子帮朋友维护一个视频分享小站用户反馈传不上2GB的课程视频我第一反应是PHP上传不就是调大点参数么改完upload_max_filesize、post_max_size、nginx 的client_max_body_size之后请求倒是进来了结果等了十几分钟页面还在转圈。回头看这个问题核心其实不在“能不能收”而在“怎么收才稳”。超大视频文件走 HTTP 协议做分段分享方向上有三件事客户端切片上传、服务端支持 Range 响应、以及视频流媒体化。这篇文章把我完整折腾一遍的细节写出来包括代码、参数和坑位。1. 先把“分段分享”拆开一个 HTTP 场景三种分段需求“分段分享”这个词容易让人误以为只有一种做法实际上在 PHP 项目里大视频的“分段”可能指三种完全不同的东西。不先把这三件事分清后面的方案设计一定跑偏。第一种是上传分段也就是客户端把一个超大视频文件用File.slice()切成一堆几 MB 的小块挨个发给服务器最后在服务器端把小块按顺序合并回完整文件。这种分段解决的是“大请求容易被网络中断、被服务器拒绝”的问题。一个 2GB 的 POST 请求中间掉线一次就全部重来切成 400 多个 5MB 的请求后任何一片失败都只重传那一片代价小得多。第二种是下载/播放分段也就是 HTTP 协议里的 Range 请求。客户端浏览器里的 video 标签、下载工具可以在请求头里带上Range: bytes0-1048575服务器只返回文件指定区间的内容响应码是 206。这种分段解决的是“在线播放拖动进度条”“下载中断后从断点续传”的问题。第三种是流媒体切片通常指 HLS 方案。视频文件先被 ffmpeg 转码并切成很多个 10 秒左右的.ts小片段再生成一个.m3u8索引文件播放器按索引逐个拉取切片。这种分段解决的是“长视频在手机弱网下播放”“多清晰度切换”的问题。三者的关系可以这样理解HTTP 协议本身的传输机制Transfer-Encoding: chunked是网络层的分段它不感知业务上面说的三种分段都是业务层的选择HTTP 层始终只看到一个个独立的请求。很多人把“上传分片”和“Range 流式播放”混在一起聊实际上一个发生在客户端到服务器方向一个发生在服务器到客户端方向用的参数和代码也完全不同。对一个 PHP 项目来说最合理的路线通常是上传阶段用分片方案收文件文件落地后用 Range 方案支持在线播放和下载如果视频很长且强调移动端体验再在后台补一条 HLS 转码任务。下面每一章说清楚一个方向的落地细节。2. PHP 和服务器默认参数为什么扛不住大视频2.1 php.ini 里最容易配错的三个参数PHP 默认配置对文件上传相当保守这是安全考量但很多人第一次被拦在门外时根本不知道该改哪个参数。核心有三个参数默认值作用upload_max_filesize2M单次请求中单个文件的大小上限post_max_size8M整个 POST 请求体的大小上限包含所有文件和其他字段max_execution_time30单个 PHP 脚本最长执行时间单位秒最容易被忽略的是post_max_size和upload_max_filesize的关系。upload_max_filesize只是限制单个文件字段但 HTTP 请求体里除了文件还有表单字段、文件头信息PHP 是在请求体整体接收阶段就按post_max_size判断的。所以post_max_size必须大于等于upload_max_filesize最好留出 1-2MB 的余量。只调upload_max_filesize不调post_max_size大文件照样传不上去。如果 PHP 前面还挂着 nginx那还要过client_max_body_size这一关。nginx 默认只允许 1MB 请求体不改的话请求在到达 PHP 之前就被 413 挡掉了。Apache 场景下则是LimitRequestBody。这些年我看过太多“改了 php.ini 没用”的帖子最后发现是 nginx 这一层的问题。2.2 nginx 和 php-fpm 各自的门槛除了client_max_body_sizenginx 还有一个容易被忽略的行为接收大型上传请求时如果请求体大于client_body_buffer_sizenginx 会把剩余部分写入临时文件。这个临时文件位置由client_body_temp_path指定默认在/tmp下。如果/tmp所在分区空间不够上传会直接失败。别问我为什么知道问就是经历过磁盘写满。php-fpm 模式下max_execution_time对应的其实是request_terminate_timeout和 nginx 的fastcgi_read_timeout。这两个超时时间如果设置太小上传脚本可能在处理到一半时被掐断。默认情况下fastcgi_read_timeout是 60 秒一个 2GB 文件走普通 POST 上传在中等带宽下根本跑不完。2.3 为什么把参数拉满不是解决思路直接把upload_max_filesize调到 10G、client_max_body_size调到 10G看起来能解决“单请求传大文件”的问题但实际运行时会有新的麻烦大请求长时间占用 PHP-FPM worker 进程。假设一台服务器有 16 个 worker3 个人同时上传大文件其他请求的响应速度会肉眼可见地变慢。一旦中途断网整个上传失败用户需要重新再来没有断点续传能力。nginx 把大请求体写入临时文件的过程是串行的请求体越大写临时文件消耗的磁盘 IO 越高严重时会影响同机上的其他站点。所以分片方案不是“把参数调小后的妥协”而是工程设计上更合理的选择。它把“一个大请求”拆成“多个小请求”每个请求都能在几秒内完成服务器不需要为单个上传保持长连接失败重试的代价也很低。提示如果你最后决定采用分片方案那么 php.ini 里的upload_max_filesize和post_max_size只需要设置成“略大于单个分片大小”即可不需要按完整文件大小设置。nginx 的client_max_body_size同理。这是分片带来的直接收益。3. 上传方向的分段实现切片、拼片、断点续传与校验3.1 前端把文件切开切多大、怎么传前端负责把文件切成小块并逐个提交。核心 API 是File.prototype.slice()没有兼容性问题现代浏览器都支持。分片大小建议在 5MB 到 10MB 之间。切得太小比如 1MB一个 2GB 文件要发 2000 个请求HTTP 握手和表单解析的开销会变得很明显切得太大比如 100MB单片失败后的重传代价又不划算。我常用 5MB兼顾可靠性和请求数量。完整的上传流程代码大概长这样const CHUNK_SIZE 5 * 1024 * 1024; const file document.getElementById(videoFile).files[0]; // fileKey 用于标识同一个文件用文件名大小修改时间组合足够区分大多数场景 const fileKey ${file.name}-${file.size}-${file.lastModified}; let index 0; let offset 0; async function uploadChunks() { while (offset file.size) { const blob file.slice(offset, offset CHUNK_SIZE); const fd new FormData(); fd.append(fileKey, fileKey); fd.append(index, index); fd.append(chunk, blob, chunk.bin); // 这里可以加一个简单的重试逻辑比如失败后最多重试3次 await fetch(/upload_chunk.php, { method: POST, body: fd, }); index; offset CHUNK_SIZE; } // 所有分片传完后请求后端执行合并 await fetch(/merge.php, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ fileKey, fileName: file.name, fileSize: file.size, totalChunks: index, }), }); } uploadChunks();这段代码是串行上传。串行的好处是逻辑简单、顺序天然有序适合绝大多数内部项目。如果同一个文件的多个分片并发上传速度会更快但后端需要额外处理“分片乱序到达”的情况而且服务器同时收到的并发请求数可能失控。我一般建议最多开 2-3 个并发且必须用记录的序号来处理合并顺序不然很容易拼出损坏文件。3.2 后端接收分片目录隔离与顺序命名后端接收分片的代码不复杂但设计上有几个细节决定了合并时是否顺利。每个分片属于哪个文件、是第几片必须记录清楚。我用fileKey做目录隔离用固定位数的序号做文件名比如00000000.part。// upload_chunk.php $fileKey $_POST[fileKey] ?? ; $index (int)($_POST[index] ?? -1); $chunk $_FILES[chunk] ?? null; if (!$fileKey || $index 0 || !$chunk || $chunk[error] ! UPLOAD_ERR_OK) { http_response_code(400); echo json_encode([error invalid params]); exit; } // 分片目录/data/chunk_files/文件标识 $dir /data/chunk_files/ . sha1($fileKey); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 关键用 sprintf(%08d) 补零保证按文件名排序时顺序正确 $chunkFile $dir . / . sprintf(%08d, $index) . .part; // move_uploaded_file 比 copy 更高效且只接受 PHP 临时目录的文件 if (!move_uploaded_file($chunk[tmp_name], $chunkFile)) { http_response_code(500); echo json_encode([error save chunk failed]); exit; } echo json_encode([ok true, index $index]);这里有一个新手很容易踩的坑分片文件名如果直接命名为1.part、2.part、10.part看文件名排序时顺序就乱了。字符串排序里10会排在2前面合并时会把第 10 片接到第 1 片后面文件直接损坏。用sprintf(%08d, $index)补零成00000001.part、00000010.part按字符串排序的顺序才和实际顺序一致。3.3 合并分片边合并边校验合并接口要做的事基本是固定的校验分片数量是否齐全按序号依次把每个分片追加写入目标文件然后删除分片目录。// merge.php $input json_decode(file_get_contents(php://input), true); $fileKey $input[fileKey] ?? ; $fileName $input[fileName] ?? video.mp4; $fileSize (int)($input[fileSize] ?? 0); $totalChunks (int)($input[totalChunks] ?? 0); if (!$fileKey || $totalChunks 0) { http_response_code(400); exit; } $dir /data/chunk_files/ . sha1($fileKey); // 校验分片是否全部就位 for ($i 0; $i $totalChunks; $i) { $chunkFile $dir . / . sprintf(%08d, $i) . .part; if (!file_exists($chunkFile)) { http_response_code(400); echo json_encode([error chunk {$i} missing]); exit; } } // 生成随机文件名避免使用用户原始文件名 $ext strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $targetPath /data/videos/ . date(Ymd) . / . bin2hex(random_bytes(8)) . . . $ext; if (!is_dir(dirname($targetPath))) { mkdir(dirname($targetPath), 0755, true); } $out fopen($targetPath, wb); if (!$out) { http_response_code(500); exit; } for ($i 0; $i $totalChunks; $i) { $chunkFile $dir . / . sprintf(%08d, $i) . .part; $in fopen($chunkFile, rb); if (!$in) { fclose($out); http_response_code(500); exit; } // stream_copy_to_stream 是 PHP 里流式复制文件内容最高效的方式 stream_copy_to_stream($in, $out); fclose($in); unlink($chunkFile); // 合并完一片删一片减轻磁盘压力 } fclose($out); // 合并完成后删除临时目录 rmdir($dir); // 校验合并后的文件大小 $actualSize filesize($targetPath); if ($actualSize ! $fileSize) { unlink($targetPath); http_response_code(500); echo json_encode([error file size mismatch]); exit; } echo json_encode([ok true, path $targetPath]);合并时的 IO 开销是真刀真枪的。一个大文件的所有分片要完整读写一遍几百 GB 的视频合并时磁盘负载会很高。我建议合并前先用disk_free_space()检查目标分区剩余空间不够就直接拒绝合并别等到磁盘写满才报错。3.4 断点续传与秒传让用户体验上一个台阶分片方案一个很大的优势就是天然支持断点续传。前端上传前先向后端询问“这个 fileKey 已经传了哪些分片”然后从缺失的分片开始继续传。// status.php $fileKey $_GET[fileKey] ?? ; $dir /data/chunk_files/ . sha1($fileKey); $uploaded []; if (is_dir($dir)) { foreach (glob($dir . /*.part) as $f) { $uploaded[] (int)basename($f, .part); } } echo json_encode([uploaded $uploaded]);前端拿到uploaded数组后跳过已上传的序号只对剩余的分片发起上传请求。这样用户网络断了、浏览器关了再打开页面重新选择同一个文件就可以从上次失败的进度继续。秒传的逻辑和断点续传不太一样。断点续传是“文件已经传了一半接着传完”秒传是“这个文件别人已经传过了服务器直接复用”。实现秒传需要在文件上传前计算整个文件的哈希值比如 MD5 或 SHA1提交给后端查表确认是否已经存在。浏览器端计算大文件哈希可以用 SparkMD5 之类的库2GB 文件大概需要几十秒虽然不算快但比重新上传一次省太多。如果觉得全量哈希太重也可以使用抽样哈希方案取文件前 1MB、中间 1MB、后 1MB 拼接后计算哈希误判概率极低代价是可能会撞上极端情况。我自己的做法是抽样哈希用于“秒传判断”合并完成后在后台再计算全量 SHA256 做最终校验两边都稳妥。3.5 分片方案解决不了的问题分片方案不是银弹。如果视频文件已经大到几十 GB分片本身没问题但合并时需要占用同等大小的临时磁盘空间加上最终文件同一时间总空间占用是文件大小的两倍。这时候更合适的思路是“分片后不合并”直接按分片对象管理或者把文件直接放到对象存储里让存储系统去处理分片和合并。PHP 只负责生成上传凭证和回调处理不要自己用fopen去拼超大文件。另外如果合并操作耗时太长比如几分钟就不应该通过 Web 请求同步等待。让 merge 接口把合并任务丢进队列前端轮询合并状态是更符合工程实践的做法。4. 分享方向的分段响应HTTP Range 与流式播放4.1 HTTP Range 的工作原理上传解决了接下来是别人怎么从服务器上拿视频。在线播放时用户一拖动进度条播放器就会向服务器发一个带 Range 头的请求。比如用户想跳到第 10 分钟浏览器会测算出对应的字节偏移发出类似这样的请求GET /video.mp4 HTTP/1.1 Range: bytes1048576-2097151服务器如果支持 Range就返回文件指定区间的数据响应码是 206 Partial Content并在响应头里带上这个区间和文件总大小HTTP/1.1 206 Partial Content Accept-Ranges: bytes Content-Range: bytes 1048576-2097151/8589934592 Content-Length: 1048576 Content-Type: video/mp4浏览器看到 206就知道服务器支持分段读取于是进度条拖动、拖动前的预加载、边下边播都变得顺畅。如果服务器不支持 Range 或者返回的是完整 200播放器通常只能从头缓冲用户一拖动就卡在转圈。4.2 PHP 输出 Range 响应的实现PHP 原生实现 Range 响应并不难核心是解析请求头里的HTTP_RANGE计算正确的起始位置用fseek跳到对应偏移再输出数据。// video.php $filePath /data/videos/ . basename($_GET[file]); // 实际项目里应从数据库映射不能直接拼路径 $size filesize($filePath); $start 0; $end $size - 1; $isPartial false; $range $_SERVER[HTTP_RANGE] ?? ; if ($range preg_match(/bytes(\d*)-(\d*)/, $range, $m)) { if ($m[1] ! ) { $start (int)$m[1]; } if ($m[2] ! ) { $end min((int)$m[2], $size - 1); } if ($start $end || $start $size) { http_response_code(416); header(Content-Range: bytes */{$size}); exit; } $isPartial true; } if ($isPartial) { http_response_code(206); header(Content-Range: bytes {$start}-{$end}/{$size}); } else { http_response_code(200); } header(Accept-Ranges: bytes); header(Content-Type: video/mp4); header(Content-Length: . ($end - $start 1)); $fp fopen($filePath, rb); if ($start 0) { fseek($fp, $start); } $remaining $end - $start 1; while ($remaining 0 !feof($fp)) { $readLen min(8192, $remaining); echo fread($fp, $readLen); flush(); $remaining - $readLen; } fclose($fp);这个实现有几个注意点。正则解析 Range 头时bytes0-1023、bytes1024-、bytes-1023三种形式都要考虑上面的正则对前两种做了基本处理bytes-1023表示“最后 1023 字节”实际项目中可以再补一个分支。多 Range 请求bytes0-100,200-300的响应规范比较复杂通常的做法是直接返回 200 完整文件对播放器体验影响不大。if (strpos($range, ,) ! false) { // 多 Range 请求直接按完整文件输出不做 206 $start 0; $end $size - 1; $isPartial false; }4.3 为什么推荐用 nginx 的 X-Accel-Redirect 发文件上面那段 PHP 代码能跑但只适合小规模应用。PHP 本身不太适合直接输出大文件流因为它要经过 PHP 进程再转发内存和 CPU 都会有一定的额外开销。更严重的是如果客户端下载到一半断开PHP 进程可能还在傻乎乎地继续读文件往外写白白浪费 IO。nginx 提供了一个专门干这活的机制X-Accel-Redirect。思路很简单——PHP 不输出文件内容只设置响应头nginx 收到特定响应头后接管请求直接由 nginx 从磁盘读取文件发送给客户端。因为 nginx 的sendfile机制是经过深度优化的性能比 PHP 转发高得多而且天然支持 Range、断点续传和高效的内存管理。// download.php // 这里做权限校验 if (!userHasPermission($videoId)) { http_response_code(403); exit; } // 数据库里查出真实文件路径 $fileRealPath getVideoPath($videoId); header(Content-Type: video/mp4); // X-Accel-Redirect 指向 nginx 配置的 internal 路径 header(X-Accel-Redirect: /protected/ . basename($fileRealPath)); exit;nginx 配置对应如下location /download/ { # 这里是 PHP 入口 include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/html/download.php; fastcgi_pass unix:/run/php/php-fpm.sock; } location /protected/ { # internal 表示这个路径只能通过 X-Accel-Redirect 访问外部直接请求会返回 404 internal; root /data/videos; }只要文件实际路径是/data/videos/xxx.mp4nginx 收到/protected/xxx.mp4的 X-Accel-Redirect 后就会把这个文件直接发给客户端。外部请求/protected/下面的地址会直接被 nginx 拒绝PHP 又做了权限校验所以鉴权逻辑是完整的。这套方案在我的项目里稳定跑了很久凡是 PHP 处理大视频下载的场景建议都这样接。4.4 MP4 在线播放必须处理的 moov 问题Range 响应支持到位了还有一个和视频格式强相关的坑MP4 文件的索引信息moov box默认可能放在文件末尾。如果 moov 在文件尾播放器无法在加载开头时就拿到视频时长、总帧数等元数据在线播放时表现为“进度条拖不动”“播放器必须先下载大量数据才能开始播”。处理方式是用 ffmpeg 做一次快速流拷贝把 moov 挪到文件头ffmpeg -i input.mp4 -movflags faststart -codec copy output.mp4-codec copy表示不重新编码只做容器层面的调整速度非常快。上传完成且合并后后台跑一遍这个命令再上线是播放体验的兜底保障。我遇到过一个案例视频用网页播放器播放总是卡在加载折腾了半天配置最后发现就是 moov 位置的问题处理完瞬间流畅了。5. 追求播放体验HLS 切片和 PHP 的定位5.1 HLS 是怎么“分段”的Range 方案适合点播和下载但如果你要一个视频支持多清晰度切换、在弱网手机下流畅播放Range 就不够了。HLS 的思路是把一个视频从物理上切成许多小片段默认每个片段时长 10 秒左右再用一个.m3u8索引文件统一管理。播放器看到索引文件后按顺序下载片段逐个播放整个过程对用户是无感的。HLS 和 Range 最本质的区别在于Range 是“同一个文件按偏移量切”HLS 是“文件本身已经被切成了许多独立小文件”。打个比方Range 像看书时来回翻指定页HLS 像把一本书撕成几百页散页想读哪页拿哪页。散页分发起来显然更灵活但需要事先准备好。5.2 转码切片后的文件结构用 ffmpeg 做 HLS 切片命令如下ffmpeg -i input.mp4 \ -profile:v baseline \ -level 3.0 \ -hls_time 10 \ -hls_list_size 0 \ -hls_segment_filename video_%03d.ts \ output.m3u8参数含义分别是-hls_time 10指定每段时长约 10 秒-hls_list_size 0表示所有切片都保留不清理历史-hls_segment_filename指定切片文件的命名规则。生成的output.m3u8内容大致如下#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXTINF:10.000000, video_000.ts #EXTINF:9.360000, video_001.ts #EXTINF:8.480000, video_002.ts #EXT-X-ENDLIST浏览器里的 video 标签天然支持 HLS只要把src指向.m3u8地址即可不需要额外开发播放器。移动端 iOS 和 Android 的 Safari/Chrome 对 HLS 的支持度也很好。5.3 PHP 在 HLS 分享里的职责范围HLS 方案上线后切片的生成和存储通常由后台任务完成PHP 在里面更多承担控制面和鉴权的工作。一个常见的做法视频上传并且合并完成后往任务队列里塞一个转码任务。后台 worker 调用 ffmpeg 生成 HLS 切片把.m3u8和.ts文件落到存储目录。用户请求播放页面时PHP 校验权限后返回带签名参数的播放地址。播放器加载索引文件后再去请求各个切片。切片请求如果全走 PHP 会比较吃力通常还是交给 nginx 或者 CDN 直接分发。例如用户拿到带签名的master.m3u8后PHP 校验签名通过后用上一章说的 X-Accel-Redirect 方式让 nginx 把索引文件发给客户端切片.ts的请求如果也要鉴权可以给每个切片 URL 加签名参数nginx 层用脚本或者 Lua 模块统一校验。如果是纯 PHP 项目最简单的方案是 PHP 入口统一派发ts请求权限通过后转发给 nginx 静态路径。这片云可能不是最优解但对大多数中小项目完全够用。5.4 什么时候选 HLS什么时候选 Range我把两个方案的选型标准写成表格方便直接对照场景推荐方案原因短视频10 分钟内、电脑端在线播放Range MP4 faststart无需转码、实现简单、体验足够长视频、移动端弱网播放HLS切片短小弱网下切换片段更平滑需要多清晰度切换HLSHLS 支持多码率嵌套索引用户要下载原始文件Range 或完整响应下载场景要完整文件不是片段视频转成 HLS 后需要保留原文件Range HLS 共存上传时保留原 MP4另存一套 HLS 文件实际项目中这两种方案经常是共存的。原视频文件保留一份供付费用户下载同时转出一套 HLS 文件供在线播放相当于把需求拆成了两条互不干扰的链路。6. 参数搭配、超时、安全红线与真实翻车现场6.1 上传进度与分片请求的搭配前端实现进度条时很多人会直接监听 XMLHttpRequest 的upload.onprogress。但分片上传时每个分片单独请求单片的进度意义不大更直观的做法是按“已上传分片数 / 总分片数”计算整体进度let uploadedChunks 0; async function uploadChunks() { const totalChunks Math.ceil(file.size / CHUNK_SIZE); while (offset file.size) { // ... 上传单片 uploadedChunks; const percent Math.floor((uploadedChunks / totalChunks) * 100); document.getElementById(progress).textContent percent %; } }这样即使某个分片因为网络问题重试了整体的进度也不会回退。比单请求的进度条稳健得多。6.2 文件落地前的安全检查清单文件上传是安全重灾区分片方案不会天然规避这些风险反而因为临时文件较多更容易造成混乱。我的安全红线清单如下不要信任用户提供的文件名。合并后的目标文件名使用随机字符串原始文件名只存入数据库用于下载接口输出Content-Disposition附件名。检查文件的真实类型。不能只看扩展名使用finfo_file()读取 MIME 类型必要时检查文件头字节。MP4 文件的前几个字节应该包含ftyp错误的内容直接拒绝。上传目录和最终文件目录要禁止执行 PHP。如果是 nginx不需要在 upload 目录配置fastcgi_pass如果是 Apache使用php_flag engine off或者.htaccess配合Require all denied。分片目录要定期清理。总有人传了几片就断线再也不回来了这些.part文件会一直留在服务器上。写一个 crontab 任务定期删除 N 天前未合并的临时目录。6.3 合并流程的清理与异步化合并操作本身在 PHP 里是同步完成的文件小时还好文件大时可能耗时数分钟。我建议合并接口在接到请求后先做判断如果分片总数不多且预估合并时间在 1 秒内直接同步合并否则把合并任务丢到 Redis 队列由后台 worker 异步执行前端通过轮询一个状态接口获取合并结果。注意合并完以后务必要清理临时分片文件否则磁盘上堆积的.part文件会让服务器越来越卡。6.4 一份可以照抄的参数参考表假设你要支持单次上传最大 2GB 的视频并且采用了 5MB 分片方案参数可以参考这样配配置位置参数推荐值nginxclient_max_body_size20mnginxfastcgi_read_timeout300sphp.iniupload_max_filesize8Mphp.inipost_max_size8Mphp.inimax_execution_time60php.inimax_file_uploads5PHP-FPMrequest_terminate_timeout60注意这里client_max_body_size、upload_max_filesize、post_max_size都只按“单个分片的大小”来设置所以看起来很小。这是分片方案带来的巨大安全收益——服务器不需要给单个请求开放超大配额即使有人恶意提交超大请求也进不来。6.5 坦白讲什么时候别用 PHP 硬扛这篇文章写的方案能解决“超大视频文件的分段分享”里绝大多数场景但我的坦白是如果文件真的到了几十 GB 级别或者上传并发非常高PHP 自己管理分片目录和合并不是一个好主意。对象存储服务的分片上传接口、开源的网盘系统组件、甚至直接用现成的文件共享工具都比自己写一套 PHP 分片合并方案更成熟、更省心。PHP 在这些方案里的角色是“业务大脑”生成上传凭证、接收回调、做权限控制、处理转码任务。文件流本身交给更专业的底层去扛。说到底能把 HTTP 协议下的分段分享做扎实对中小项目来说已经是很有价值的工程能力了。但如果哪天需求膨胀到“百 GB 级、万人并发”那说明你该换个赛道了不是 PHP 不够好而是它的定位根本不在这里。最后再分享一个实际操作中的体会凡是“单个文件超过 2GB”的站点无论看起来多简单都值得花半小时提前规划分段凡是“上传后给人在线看”的视频默认加一步-movflags faststart处理凡是“想让用户顺畅拖动进度条”的 MP4务必确认服务器确实返回了 206 Partial Content用浏览器的开发者工具看一眼响应头比什么排查都直接。这三条从实际项目里踩出来照着做能少走一大半弯路。
返回列表