飞凤互联 发表于 3 天前

完整的ZIP文件处理解决方案(兼容PHP 8.3)

/**
* 完整的ZIP文件处理解决方案(兼容PHP 8.3)
* 功能:下载ZIP文件、验证MD5、解压加密ZIP
*/
class ZipProcessor {
    private $tempDir;

    public function __construct() {
      $this->tempDir = sys_get_temp_dir();
      $this->checkDependencies();
    }
    private function checkDependencies() {
      if (!extension_loaded('curl')) {
            throw new Exception("需要CURL扩展支持");
      }

      if (!extension_loaded('zip')) {
            throw new Exception("需要ZIP扩展支持");
      }
    }

    public function processApiResponse($apiResponse, $extractDir) {
      try {
         
            $responseData = $this->parseApiResponse($apiResponse);

            
            $zipFile = $this->downloadZipFile($responseData['download_url']);

         
            $this->verifyFileIntegrity($zipFile, $responseData['zip_md5'], $responseData['zip_size']);

            
            $this->extractZipFile($zipFile, $extractDir, $responseData['zip_password']);

            
            $this->cleanupTempFiles($zipFile);

            return [
                'success' => true,
                'message' => 'ZIP文件处理成功',
                'version' => $responseData['version'],
                'extract_dir' => realpath($extractDir)
            ];

      } catch (Exception $e) {
            if (isset($zipFile) && file_exists($zipFile)) {
                $this->cleanupTempFiles($zipFile);
            }

            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
      }
    }

    /**
   * 解析API响应
   */
    private function parseApiResponse($apiResponse) {
      $responseData = json_decode($apiResponse, true);

      if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("JSON解析失败: " . json_last_error_msg());
      }

      if (!isset($responseData['code']) || $responseData['code'] !== 0) {
            throw new Exception("API错误: " . ($responseData['msg'] ?? '未知错误'));
      }

      if (!isset($responseData['data'])) {
            throw new Exception("API响应格式错误: 缺少data字段");
      }

      $requiredFields = ['download_url', 'zip_password', 'zip_md5', 'version'];
      foreach ($requiredFields as $field) {
            if (!isset($responseData['data'][$field])) {
                throw new Exception("API响应格式错误: 缺少data字段 $field");
            }
      }

      return $responseData['data'];
    }

    /**
   * 下载ZIP文件
   */
    private function downloadZipFile($url) {
      $tempFile = tempnam($this->tempDir, 'zip_download_');

      $ch = curl_init($url);
      $fp = fopen($tempFile, 'w');

      if (!$fp) {
            throw new Exception("无法创建临时文件: $tempFile");
      }

      curl_setopt_array($ch, [
            CURLOPT_FILE => $fp,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_AUTOREFERER => true,
            CURLOPT_MAXREDIRS => 5,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_USERAGENT => 'PHP-ZipProcessor/1.0'
      ]);

      $result = curl_exec($ch);

      if ($result === false) {
            $error = curl_error($ch);
            fclose($fp);
            unlink($tempFile);
            curl_close($ch);
            throw new Exception("下载失败: $error");
      }

      $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

      curl_close($ch);
      fclose($fp);

      if ($httpCode < 200 || $httpCode >= 300) {
            unlink($tempFile);
            throw new Exception("HTTP错误: $httpCode");
      }

      if (!in_array($contentType, ['application/zip', 'application/x-zip-compressed', 'application/octet-stream'])) {
            unlink($tempFile);
            throw new Exception("下载的文件不是ZIP格式: $contentType");
      }

      if (filesize($tempFile) === 0) {
            unlink($tempFile);
            throw new Exception("下载的文件为空");
      }

      return $tempFile;
    }

    /**
   * 验证文件完整性
   */
    private function verifyFileIntegrity($filePath, $expectedMd5, $expectedSize = null) {
      // 验证MD5
      $actualMd5 = md5_file($filePath);
      if (strtolower($actualMd5) !== strtolower($expectedMd5)) {
            throw new Exception("MD5验证失败\n实际: $actualMd5\n期望: $expectedMd5");
      }

      // 验证文件大小(如果提供)
      if ($expectedSize !== null && $expectedSize !== '0') {
            $actualSize = filesize($filePath);
            $expectedBytes = $this->humanToBytes($expectedSize);

            if ($expectedBytes > 0 && abs($actualSize - $expectedBytes) > 1024) { // 允许1KB误差
                throw new Exception("文件大小不匹配\n实际: " . $this->bytesToHuman($actualSize) .
                                  "\n期望: $expectedSize");
            }
      }
    }

    /**
   * 解压ZIP文件(兼容PHP 8.3)
   */
    private function extractZipFile($zipFile, $extractDir, $password) {
      // 创建解压目录
      if (!file_exists($extractDir)) {
            if (!mkdir($extractDir, 0755, true)) {
                throw new Exception("无法创建解压目录: $extractDir");
            }
      }

      if (!is_writable($extractDir)) {
            throw new Exception("解压目录不可写: $extractDir");
      }

      $zip = new ZipArchive();
      $openResult = $zip->open($zipFile);

      if ($openResult !== true) {
            throw new Exception("无法打开ZIP文件: " . $this->getZipErrorText($openResult));
      }

      try {
            // 尝试使用密码(兼容PHP 8.3)
            $zip->setPassword($password);

            // 检查ZIP文件是否有内容
            if ($zip->numFiles === 0) {
                throw new Exception("ZIP文件为空");
            }

            // 尝试解压所有文件
            $success = false;

            // 先尝试解压第一个文件测试密码
            $firstFileName = $zip->getNameIndex(0);
            if ($zip->extractTo($extractDir, $firstFileName)) {
                // 密码正确,继续解压其他文件
                for ($i = 1; $i < $zip->numFiles; $i++) {
                  $fileName = $zip->getNameIndex($i);
                  $zip->extractTo($extractDir, $fileName);
                }
                $success = true;
            } else {
                // 可能是密码错误,尝试不使用密码
                $zip->setPassword('');
                if ($zip->extractTo($extractDir)) {
                  $success = true;
                } else {
                  // 检查具体错误
                  if ($openResult === ZipArchive::ER_WRONGPASSWD) {
                        throw new Exception("ZIP文件密码错误");
                  }
                  throw new Exception("解压失败");
                }
            }

            if (!$success) {
                throw new Exception("解压失败");
            }

      } finally {
            $zip->close();
      }

      return true;
    }

    private function cleanupTempFiles($filePath) {
      if (file_exists($filePath)) {
            unlink($filePath);
      }
    }

    /**
   * 获取ZIP错误信息
   */
    private function getZipErrorText($errorCode) {
      $errors = [
            ZipArchive::ER_EXISTS => '文件已存在',
            ZipArchive::ER_INCONS => 'Zip文件不一致',
            ZipArchive::ER_INVAL => '无效的参数',
            ZipArchive::ER_MEMORY => '内存分配失败',
            ZipArchive::ER_NOENT => '没有这样的文件',
            ZipArchive::ER_NOZIP => '不是zip归档',
            ZipArchive::ER_OPEN => '无法打开文件',
            ZipArchive::ER_READ => '读取错误',
            ZipArchive::ER_SEEK => '查找错误',
            ZipArchive::ER_WRONGPASSWD => '密码错误'
      ];

      return $errors[$errorCode] ?? "未知错误: $errorCode";
    }

    /**
   * 人类可读大小转字节
   */
    private function humanToBytes($size) {
      if ($size === null || $size === '') return 0;

      $units = ['B', 'KB', 'MB', 'GB', 'TB'];
      $unit = strtoupper(substr($size, -2));

      if (!in_array($unit, $units)) {
            $unit = strtoupper(substr($size, -1));
      }

      $value = (float)trim(rtrim($size, $unit));

      $power = array_search($unit, $units);
      return $value * pow(1024, $power);
    }

    /**
   * 字节转人类可读大小
   */
    private function bytesToHuman($bytes) {
      $units = ['B', 'KB', 'MB', 'GB', 'TB'];
      $power = floor(log($bytes, 1024));
      return round($bytes / pow(1024, $power), 2) . $units[$power];
    }
}

// 使用示例
try {
    // API响应数据
    $apiResponse = '{
      "code": 0,
      "msg": "加密更新包生成成功",
      "data": {
            "download_url": ",
            "zip_password": "e26d556899c456915a61341802dad137",
            "zip_size": "3.56KB",
            "zip_md5": "90e356e66c38a0c5c9d023e94aa750e1",
            "version": "3.1.20250928",
            "cxid": 2
      }
    }';
    $extractDirectory = ROOT;
    $processor = new ZipProcessor();

    // 执行处理流程
    $result = $processor->processApiResponse($apiResponse, $extractDirectory);

    if ($result['success']) {
      echo "✅ 处理成功!\n";
      echo "📁 解压目录: " . $result['extract_dir'] . "\n";
      echo "🔢 版本号: " . $result['version'] . "\n";
    } else {
      echo "❌ 处理失败: " . $result['message'] . "\n";
    }

} catch (Exception $e) {
    echo "⚠️系统错误: " . $e->getMessage() . "\n";
}


页: [1]
查看完整版本: 完整的ZIP文件处理解决方案(兼容PHP 8.3)