Skip to content

下载大文件

传入url和保存路径,下载大文件

php
public function downloadVideo($url, $savePath, $headers = [], $timeout = 600)
{
    $fp = fopen($savePath, 'cb');
    if (!$fp) {
        throw new \RuntimeException("无法创建本地文件: {$savePath}");
    }

    // 断点续传:已存在则追加
    $offset = filesize($savePath);
    if ($offset > 0) {
        fseek($fp, $offset);
    }

        $ch = curl_init($url);
        curl_setopt_array($ch, [
        CURLOPT_FILE         => $fp,               // 直接写入文件句柄
        CURLOPT_FOLLOWLOCATION => true,            // 跟随 3xx
        CURLOPT_TIMEOUT      => $timeout,          // 整体超时
        CURLOPT_CONNECTTIMEOUT => 60,              // 连接超时
        CURLOPT_USERAGENT    => 'Mozilla/5.0 (compatible; Downloader/1.0)',
        CURLOPT_SSL_VERIFYPEER  => false,           // 仅用于测试/内网:跳过对等证书验证
        CURLOPT_SSL_VERIFYHOST  => false,           // 仅用于测试/内网:跳过主机名校验
        //CURLOPT_SSL_VERIFYPEER => true,            // 生产环境建议开启
        //CURLOPT_SSL_VERIFYHOST => 2,               // 生产环境建议开启
        CURLOPT_FAILONERROR  => true,              // 4xx/5xx 直接失败
        CURLOPT_HTTPHEADER   => $headers,          // 如 Referer/Cookie
        CURLOPT_NOPROGRESS   => false,
        CURLOPT_PROGRESSFUNCTION => function ($ch, $dlTotal, $dlNow, $ulTotal, $ulNow) use ($offset) {
            // 首次回调时发送 Range 头(仅当存在本地残片时)
            static $sentRange = false;
            if (!$sentRange && $offset > 0 && $dlNow > 0) {
                $from = $offset + $dlNow;
                $to   = $offset + ($dlTotal ?? PHP_INT_MAX) - 1;
                curl_setopt($ch, CURLOPT_RANGE, "{$from}-{$to}");
                $sentRange = true;
            }
            return 0;
        },
    ]);

    if (curl_exec($ch) === false) {
        fclose($fp);
        @unlink($savePath); // 失败清理残片
        $err = curl_error($ch);
        curl_close($ch);
        throw new \RuntimeException("cURL 错误: {$err}");
    }

    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    fclose($fp);

    // 若是 206 部分内容,或首次完整下载成功
    if (($httpCode >= 200 && $httpCode < 300) || $httpCode === 206) {
        clearstatcache(true, $savePath);
        $finalSize = filesize($savePath);
        echo "完成:{$finalSize} 字节 已保存至:{$savePath} 来源:{$effectiveUrl}\n";
    } else {
        @unlink($savePath); // 兜底清理
        throw new \RuntimeException("下载失败,HTTP 状态码: {$httpCode},类型: {$contentType}");
    }
}