'Mozilla/5.0 (compatible; SlakeSchool/1.0)', 'Accept' => 'image/*,*/*', ]; if ($referer && preg_match('#^https?://#i', $referer)) { $headers['Referer'] = $referer; } try { $response = Http::timeout(30) ->withHeaders($headers) ->get($url); } catch (\Throwable $e) { throw new RuntimeException('图片下载失败:'.$e->getMessage()); } if (! $response->successful()) { throw new RuntimeException('图片下载失败,HTTP '.$response->status()); } $body = $response->body(); if ($body === '') { throw new RuntimeException('图片内容为空'); } $contentType = $response->header('Content-Type'); if ($contentType && ! str_contains(strtolower($contentType), 'image')) { throw new RuntimeException('远程资源不是图片'); } $ext = $this->guessExtension($url, $contentType); $filename = basename(parse_url($url, PHP_URL_PATH) ?: '') ?: 'import'; $filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename) ?: 'import'; $filename = Str::limit($filename, 64, ''); if (! preg_match('/\.(png|jpe?g|gif|webp)$/i', $filename)) { $filename .= '.'.$ext; } $path = 'courses/'.$subdir.'/'.date('Ym').'/'.Str::random(16).'_'.$filename; Storage::disk('public')->put($path, $body); $publicUrl = CourseMedia::publicAssetUrl($path) ?? ''; return CourseMedia::query()->create([ 'disk' => 'public', 'path' => $path, 'url' => $publicUrl, 'category' => $subdir, 'original_name' => $filename, 'mime_type' => $this->guessMimeType($ext, $contentType), 'size_bytes' => strlen($body), ]); } protected function guessExtension(string $url, ?string $contentType): string { if ($contentType) { $lower = strtolower($contentType); if (str_contains($lower, 'png')) { return 'png'; } if (str_contains($lower, 'jpeg') || str_contains($lower, 'jpg')) { return 'jpg'; } if (str_contains($lower, 'gif')) { return 'gif'; } if (str_contains($lower, 'webp')) { return 'webp'; } } if (preg_match('#\.(png|jpe?g|gif|webp)(\?|$)#i', $url, $m)) { return strtolower($m[1] === 'jpeg' ? 'jpg' : $m[1]); } return 'jpg'; } protected function guessMimeType(string $ext, ?string $contentType): ?string { if ($contentType && str_contains(strtolower($contentType), 'image')) { return strtok($contentType, ';') ?: null; } return match ($ext) { 'png' => 'image/png', 'jpg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 'webp' => 'image/webp', default => null, }; } }