'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'bmp' => 'image/bmp', 'svg' => 'image/svg+xml' ]; // 默认配置 private const DEFAULT_CONFIG = [ 'cache_dir' => 'cache', 'timeout' => 30, 'connect_timeout' => 15, 'max_redirects' => 5, 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ]; private $config; private $logger; /** * 构造函数 * * @param array $config 配置参数 */ public function __construct(array $config = []) { $this->config = array_merge(self::DEFAULT_CONFIG, $config); $this->initCacheDir(); $this->initLogger(); } /** * 初始化缓存目录 */ private function initCacheDir() { if (!file_exists($this->config['cache_dir'])) { mkdir($this->config['cache_dir'], 0755, true); } } /** * 初始化日志记录器 */ private function initLogger() { $logDir = $this->config['cache_dir'] . '/logs'; if (!file_exists($logDir)) { mkdir($logDir, 0755, true); } $this->logger = new Logger($logDir); } /** * 处理图片请求 * * @param string $url 图片URL * @return void */ public function processRequest(string $url) { try { if (!$this->validateUrl($url)) { throw new Exception('Invalid URL format'); } $cachePath = $this->getCachePath($url); // 检查缓存 if ($this->isCached($cachePath)) { $this->serveFromCache($cachePath); return; } // 获取远程图片 $imageData = $this->fetchRemoteImage($url); // 验证图片数据 if (!$this->validateImageData($imageData)) { throw new Exception('Invalid image data'); } // 保存到缓存 $this->saveToCache($cachePath, $imageData); // 输出图片 $this->outputImage($imageData, $this->getImageType($url)); } catch (Exception $e) { $this->logger->error('Image processing error: ' . $e->getMessage()); $this->outputError(); } } /** * 验证URL格式 * * @param string $url URL地址 * @return bool */ private function validateUrl(string $url): bool { return filter_var($url, FILTER_VALIDATE_URL) !== false && preg_match('/^https?:\/\//i', $url); } /** * 获取缓存路径 * * @param string $url 图片URL * @return string */ private function getCachePath(string $url): string { $parsedUrl = parse_url($url); $domain = $parsedUrl['host']; $path = $parsedUrl['path']; $cacheSubDir = $this->config['cache_dir'] . '/' . $domain; if (!file_exists($cacheSubDir)) { mkdir($cacheSubDir, 0755, true); } return $cacheSubDir . '/' . md5($url) . '.' . pathinfo($path, PATHINFO_EXTENSION); } /** * 检查图片是否已缓存 * * @param string $cachePath 缓存路径 * @return bool */ private function isCached(string $cachePath): bool { return file_exists($cachePath) && (time() - filemtime($cachePath)) < 86400; // 缓存24小时 } /** * 从缓存输出图片 * * @param string $cachePath 缓存路径 */ private function serveFromCache(string $cachePath) { $type = $this->getImageType($cachePath); header("Content-type: " . $type); readfile($cachePath); } /** * 获取远程图片 * * @param string $url 图片URL * @return string */ private function fetchRemoteImage(string $url): string { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => $this->config['max_redirects'], CURLOPT_CONNECTTIMEOUT => $this->config['connect_timeout'], CURLOPT_TIMEOUT => $this->config['timeout'], CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_USERAGENT => $this->config['user_agent'], CURLOPT_REFERER => parse_url($url, PHP_URL_SCHEME) . '://' . parse_url($url, PHP_URL_HOST) . '/', CURLOPT_HTTPHEADER => ['Accept: image/*'] ]); $data = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || empty($data)) { throw new Exception('Failed to fetch image: HTTP ' . $httpCode); } return $data; } /** * 验证图片数据 * * @param string $data 图片数据 * @return bool */ private function validateImageData(string $data): bool { if (empty($data)) { return false; } // 检查文件头 $headers = [ 'image/jpeg' => "\xFF\xD8\xFF", 'image/png' => "\x89PNG\r\n\x1a\n", 'image/gif' => "GIF", 'image/webp' => "RIFF....WEBP", 'image/bmp' => "BM", 'image/svg+xml' => ' $header) { if (strpos($data, $header) === 0) { return true; } } return false; } /** * 保存到缓存 * * @param string $cachePath 缓存路径 * @param string $data 图片数据 */ private function saveToCache(string $cachePath, string $data) { file_put_contents($cachePath, $data); } /** * 输出图片 * * @param string $data 图片数据 * @param string $type 图片类型 */ private function outputImage(string $data, string $type) { header("Content-type: " . $type); header("Cache-Control: public, max-age=86400"); echo $data; } /** * 获取图片类型 * * @param string $url 图片URL * @return string */ private function getImageType(string $url): string { $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); return self::SUPPORTED_TYPES[$ext] ?? 'image/jpeg'; } /** * 输出错误信息 */ private function outputError() { header("HTTP/1.1 500 Internal Server Error"); header("Content-type: image/jpeg"); // 输出一个1x1的透明图片 echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); } } /** * 简单的日志记录类 */ class Logger { private $logDir; public function __construct(string $logDir) { $this->logDir = $logDir; } public function error(string $message) { $this->log('ERROR', $message); } public function info(string $message) { $this->log('INFO', $message); } private function log(string $level, string $message) { $date = date('Y-m-d'); $time = date('Y-m-d H:i:s'); $logFile = $this->logDir . "/{$date}.log"; $logMessage = "[{$time}] [{$level}] {$message}\n"; file_put_contents($logFile, $logMessage, FILE_APPEND); } }