You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

spatie/laravel-medialibrary上传faker-images图片时出现URL无法访问错误

问题:Laravel Medialibrary 无法下载 Picsum 图片返回403错误

在Laravel 9项目中使用mmo/faker-images生成图片URL,再通过spatie/laravel-medialibrary上传时,抛出UnreachableUrl异常,提示URL无法访问,但该URL在浏览器中可正常打开。

代码示例

$faker = \Faker\Factory::create();

$faker->addProvider(new \Mmo\Faker\PicsumProvider($faker));
$faker->addProvider(new \Mmo\Faker\LoremSpaceProvider($faker));

$pageUploadedImageFile= $faker->picsumUrl(400, 400);

$imageMedia = $this->modelItem
     ->addMediaFromUrl($pageUploadedImageFile)
     ->usingFileName($imageFilename)
     ->toMediaCollection('media');

生成的URL示例:

https://picsum.photos/400/400?random=83611  

错误信息

Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl 

Url `https://picsum.photos/400/400?random=83611` cannot be reached

at vendor/spatie/laravel-medialibrary/src/MediaCollections/Exceptions/UnreachableUrl.php:9
    5▕ class UnreachableUrl extends FileCannotBeAdded
    6▕ {
    7▕     public static function create(string $url): self
    8▕     {
➜   9▕         return new static("Url `{$url}` cannot be reached");
   10▕     }
   11▕ }
   12▕ 

项目依赖版本

"laravel/framework": "^9.26.1",
"laravel/tinker": "^2.7.2",
"spatie/laravel-medialibrary": "^10.4.4",
"spatie/laravel-permission": "^5.5.5",
"squizlabs/php_codesniffer": "*",

"barryvdh/laravel-ide-helper": "^2.12",
"fakerphp/faker": "^1.9.1",
"laravel/sail": "^1.0.1",
"mmo/faker-images": "^0.6.0",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^6.1"

调试发现

追踪源码后发现,错误来自Spatie\MediaLibrary\Downloaders\DefaultDownloader的getTempFile方法,使用fopen请求URL时返回403 Forbidden:

<?php

namespace Spatie\MediaLibrary\Downloaders;

use Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl;

class DefaultDownloader implements Downloader
{
    public function getTempFile(string $url): string
    {
        \Log::info( ' getTempFile $url::');
        \Log::info( $url);
        $stream = fopen($url, 'r');
        \Log::info( ' getTempFile $stream::');
        \Log::info( $stream);
        if (! $stream ) {
            throw UnreachableUrl::create($url);
        }

        $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');

        file_put_contents($temporaryFile, $stream);

        return $temporaryFile;
    }
}

日志输出:

[2022-09-29 04:10:51] local.INFO:  getTempFile $url::  
[2022-09-29 04:10:51] local.INFO: https://picsum.photos/800/600?random=86375  
[2022-09-29 04:10:51] local.ERROR: fopen(https://picsum.photos/800/600?random=86375): Failed to open stream: HTTP request failed! HTTP/1.1 403 Forbidden
 {"userId":7,"exception":"[object] (ErrorException(code: 0): fopen(https://picsum.photos/800/600?random=86375): Failed to open stream: HTTP request failed! HTTP/1.1 403 Forbidden

原因分析

这不是Medialibrary的防护机制,而是Picsum.photos服务器会拦截没有User-Agent标识的请求。浏览器请求时会自动带上User-Agent,所以能正常访问;而fopen默认不带任何请求头,因此被服务器拒绝,返回403。

解决方案

方案1:自定义下载器,添加User-Agent请求头

创建自定义下载器类,使用带请求头的方式获取文件:

<?php

namespace App\Services\MediaLibrary;

use Spatie\MediaLibrary\Downloaders\Downloader;
use Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl;

class CustomDownloader implements Downloader
{
    public function getTempFile(string $url): string
    {
        $context = stream_context_create([
            'http' => [
                'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
            ]
        ]);

        $stream = fopen($url, 'r', false, $context);

        if (! $stream) {
            throw UnreachableUrl::create($url);
        }

        $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');

        file_put_contents($temporaryFile, $stream);

        return $temporaryFile;
    }
}

然后在AppServiceProvider的boot方法中注册自定义下载器:

use Spatie\MediaLibrary\MediaCollections\Downloaders\Downloader;
use App\Services\MediaLibrary\CustomDownloader;

public function boot()
{
    $this->app->bind(Downloader::class, CustomDownloader::class);
}

方案2:直接生成本地临时文件再上传

使用mmo/faker-images的picsum方法直接生成本地临时图片,再通过addMedia上传:

// 生成本地临时图片
$tempImage = $faker->picsum(storage_path('app/temp'), 400, 400);

// 上传到媒体库
$imageMedia = $this->modelItem
    ->addMedia($tempImage)
    ->usingFileName($imageFilename)
    ->toMediaCollection('media');

// 可选:删除临时文件
unlink($tempImage);

这个方法绕开了远程URL下载,直接操作本地文件,更稳定。

内容的提问来源于stack exchange,提问作者Petro Gromovo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 07:50:29