'jpg', 'image/jpg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', 'image/bmp' => 'bmp', 'image/svg+xml' => 'svg', 'video/mp4' => 'mp4', 'video/quicktime' => 'mov', 'video/x-msvideo' => 'avi', 'video/webm' => 'webm', 'audio/mpeg' => 'mp3', 'audio/mp3' => 'mp3', 'audio/wav' => 'wav', 'audio/x-wav' => 'wav', 'audio/amr' => 'amr', 'audio/aac' => 'aac', 'audio/mp4' => 'm4a', 'audio/ogg' => 'ogg', 'application/pdf' => 'pdf', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/vnd.ms-excel' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/zip' => 'zip', 'application/x-rar-compressed' => 'rar', 'application/x-7z-compressed' => '7z', 'text/plain' => 'txt', ]; public static function isRemoteHttpUrl($url) { if (!is_string($url)) { return false; } return (bool)preg_match('/^https?:\/\//i', trim($url)); } public static function isOwnOssUrl($url) { if (!self::isRemoteHttpUrl($url)) { return false; } $host = strtolower((string)parse_url($url, PHP_URL_HOST)); if (empty($host)) { return false; } $customHost = strtolower((string)parse_url(AliyunOSS::ossUrl, PHP_URL_HOST)); $bucketHost = strtolower(AliyunOSS::BUCKET . '.' . AliyunOSS::ENDPOINT); return $host === $customHost || $host === $bucketHost || strpos($host, strtolower(AliyunOSS::BUCKET . '.')) === 0; } public static function archiveRemoteUrl($url, $bizType, $resourceType, $bizKey, array $options = []) { $url = trim((string)$url); if (!self::isRemoteHttpUrl($url)) { return [ 'success' => false, 'error' => 'URL不是有效的http(s)地址', 'url' => '', ]; } if (self::isOwnOssUrl($url)) { return [ 'success' => true, 'alreadyArchived' => true, 'url' => self::normalizeOssUrl($url), 'originalUrl' => $url, 'object_name' => '', ]; } $tempFile = ''; try { $tempDir = self::ensureTempDirectory(); $tempFile = $tempDir . md5($url . microtime(true) . mt_rand()) . '.tmp'; $downloadResult = self::downloadToLocal($url, $tempFile, (int)($options['timeout'] ?? 60)); if (!$downloadResult['success']) { return $downloadResult; } $extension = self::detectExtension( $url, $tempFile, $downloadResult['contentType'] ?? '', $options['extension'] ?? '' ); $objectName = self::buildObjectName($bizType, $resourceType, $bizKey, $extension); $result = AliyunOSS::uploadFile($tempFile, $objectName); if (!$result['success']) { return [ 'success' => false, 'error' => $result['error'] ?? '上传OSS失败', 'url' => '', ]; } return [ 'success' => true, 'alreadyArchived' => false, 'url' => self::normalizeOssUrl($result['url'] ?? ''), 'originalUrl' => $url, 'object_name' => $objectName, 'extension' => $extension, 'mime_type' => $result['mime_type'] ?? ($downloadResult['contentType'] ?? ''), 'size' => $result['size'] ?? 0, ]; } catch (\Exception $e) { Log::error('媒体资源上传OSS失败:' . $e->getMessage(), [ 'url' => $url, 'bizType' => $bizType, 'resourceType' => $resourceType, 'bizKey' => $bizKey, ]); return [ 'success' => false, 'error' => $e->getMessage(), 'url' => '', ]; } finally { if (!empty($tempFile) && file_exists($tempFile)) { @unlink($tempFile); } } } protected static function ensureTempDirectory() { if (function_exists('runtime_path')) { $baseRuntimePath = rtrim(runtime_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } elseif (defined('RUNTIME_PATH')) { $baseRuntimePath = rtrim(RUNTIME_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } else { $baseRuntimePath = rtrim(ROOT_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR; } $tempDir = $baseRuntimePath . 'temp' . DIRECTORY_SEPARATOR . 'media_archive' . DIRECTORY_SEPARATOR . date('Y' . DIRECTORY_SEPARATOR . 'm' . DIRECTORY_SEPARATOR . 'd') . DIRECTORY_SEPARATOR; if (!is_dir($tempDir)) { mkdir($tempDir, 0755, true); } return $tempDir; } protected static function downloadToLocal($url, $tempFile, $timeout) { $ch = curl_init($url); $fp = fopen($tempFile, 'wb'); curl_setopt($ch, CURLOPT_FILE, $fp); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_USERAGENT, 'CKB-MediaArchive/1.0'); curl_exec($ch); $curlError = curl_error($ch); $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE); curl_close($ch); fclose($fp); if (!empty($curlError)) { @unlink($tempFile); return [ 'success' => false, 'error' => '下载资源失败:' . $curlError, 'url' => '', ]; } if ($httpCode < 200 || $httpCode >= 300 || !file_exists($tempFile) || filesize($tempFile) <= 0) { @unlink($tempFile); return [ 'success' => false, 'error' => '下载资源失败,HTTP状态码:' . $httpCode, 'url' => '', ]; } return [ 'success' => true, 'contentType' => $contentType, 'httpCode' => $httpCode, ]; } protected static function detectExtension($url, $tempFile, $contentType = '', $forcedExtension = '') { $forcedExtension = strtolower(trim((string)$forcedExtension, '. ')); if (!empty($forcedExtension)) { return $forcedExtension; } $pathExtension = strtolower((string)pathinfo((string)parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION)); if (preg_match('/^[a-z0-9]{1,8}$/i', $pathExtension)) { return $pathExtension; } $contentType = strtolower(trim((string)$contentType)); if (!empty($contentType)) { $contentType = trim(explode(';', $contentType)[0]); if (!empty(self::$mimeExtensionMap[$contentType])) { return self::$mimeExtensionMap[$contentType]; } } $mimeType = ''; if (function_exists('mime_content_type')) { $mimeType = strtolower((string)mime_content_type($tempFile)); } if (!empty($mimeType) && !empty(self::$mimeExtensionMap[$mimeType])) { return self::$mimeExtensionMap[$mimeType]; } return 'bin'; } protected static function buildObjectName($bizType, $resourceType, $bizKey, $extension) { $bizType = trim((string)$bizType, '/'); $resourceType = trim((string)$resourceType, '/'); $bizKey = preg_replace('/[^a-zA-Z0-9_\-]/', '_', (string)$bizKey); $extension = trim((string)$extension, '.'); return $bizType . '/' . $resourceType . '/' . date('Y/m/d/') . $bizKey . '_' . substr(md5(uniqid('', true)), 0, 16) . '.' . $extension; } protected static function normalizeOssUrl($url) { $url = trim((string)$url); if (strpos($url, 'http://') === 0) { return 'https://' . substr($url, 7); } return $url; } }