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

Laravel 9接入AWS S3报UnableToWriteFile无法写入文件问题

问题背景

基于Laravel框架开发CRUD应用,因计划将项目部署到Heroku平台,Laravel原生本地存储方案无法满足持久化存储需求,因此对接AWS S3对象存储服务。参照教程完成配置后,程序抛出League \ Flysystem \ UnableToWriteFile错误,提示为:Unable to write file at location: nameOfMyPicture.jpg。
除文件写入失败外,调用文件存在性检测接口时同样报错,提示为:Unable to check existence for: myFileName。经测试,使用同一组AWS凭证在终端操作时,可正常访问目标存储桶,完成文件的上传、下载操作,但Laravel侧所有S3读写操作均失败。

已完成排查操作
  • 执行composer require league/flysystem-aws-s3-v3安装对应S3适配依赖
  • 多次执行php artisan config:clear、php artisan cache:clear清理配置缓存,多次执行composer update更新依赖
  • 已核对.env文件中AWS相关配置信息准确,也曾尝试跳过.env读取,直接将所有配置参数写入filesystems.php配置文件的s3磁盘配置项中,排除配置读取问题
相关代码与配置

业务侧S3上传代码

if ($request->hasfile('photo')) {
    $file = $request->file('photo');

    $name = time() . '.' . $file->getClientOriginalExtension();
    $filePath = 'locations/' . $name;

    Storage::disk('s3'))->put($filePath, file_get_contents($file);

    return back()->with('msg', 'Image Uploaded successfully');
}

文件存在性检测代码

if (Storage::disk('s3')->exists('my-file-name.jpg')) {
    dd("hello");
}

S3磁盘实例打印结果

执行dd(Storage::disk('s3'))打印S3磁盘实例时,返回结果中url字段为null,但可正确读取到配置的存储桶名称、.env中配置的访问凭证、区域等信息:

^ Illuminate\Filesystem\AwsS3V3Adapter {#1388 ▼
  #driver: League\Flysystem\Filesystem {#1379 ▼
    -adapter: League\Flysystem\AwsS3V3\AwsS3V3Adapter {#1382 ▶}
    -config: League\Flysystem\Config {#1378 ▼
      -options: array:1 [▼
        "url" => null
      ]
    }
    -pathNormalizer: League\Flysystem\WhitespacePathNormalizer {#1374}
  }
  #adapter: League\Flysystem\AwsS3V3\AwsS3V3Adapter {#1382 ▼
    -client: Aws\S3\S3Client {#1343 ▼
      -aliases: null
      -config: array:9 [▶]
      -region: "eu-west-3"
      -endpoint: GuzzleHttp\Psr7\Uri {#1450 ▼
        -scheme: "https"
        -userInfo: ""
        -host: "s3.eu-west-3.amazonaws.com"
        -port: null
        -path: ""
        -query: ""
        -fragment: ""
        -composedComponents: null
      }
      -api: Aws\Api\Service {#1355 ▼
        #definition: array:4 [▶]
        #shapeMap: Aws\Api\ShapeMap {#1356 ▶}
        -apiProvider: Aws\Api\ApiProvider {#1354 ▶}
        -serviceName: "s3"
        -apiVersion: "2006-03-01"
        -operations: []
        -paginators: null
        -waiters: null
      }
      -signatureProvider: Closure($version, $service, $region) {#1353 ▶}
      -credentialProvider: Closure() {#1401 ▶}
      -handlerList: Aws\HandlerList {#1350 ▶}
      -defaultRequestOptions: []
    }
    -prefixer: League\Flysystem\PathPrefixer {#1376 ▶}
    -bucket: "veville-images"
    -visibility: League\Flysystem\AwsS3V3\PortableVisibilityConverter {#1339 ▶}
    -mimeTypeDetector: League\MimeTypeDetection\FinfoMimeTypeDetector {#1381 ▶}
    -options: []
    -streamReads: false
  }
  #config: array:11 [▼
    "driver" => "s3"
    "key" => "keyFromEnvFile"
    "secret" => "keyFromEnvFile"
    "region" => "eu-west-3"
    "bucket" => "veville-images"
    "url" => null
    "endpoint" => null
    "use_path_style_endpoint" => false
    "throw" => true
    "version" => "latest"
    "credentials" => array:2 [▼
      "key" => "keyFromEnvFile"
      "secret" => "keyFromEnvFile"
    ]
  ]
  #prefixer: League\Flysystem\PathPrefixer {#1377 ▶}
  #temporaryUrlCallback: null
  #client: Aws\S3\S3Client {#1343 ▶}

filesystems.php中s3相关配置

's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
    'throw' => true,
],

.env文件中AWS相关配置

AWS_ACCESS_KEY_ID=rightkey
AWS_SECRET_ACCESS_KEY=rightsecret
AWS_DEFAULT_REGION=eu-west-3
AWS_BUCKET=veville-images
AWS_USE_PATH_STYLE_ENDPOINT=false

当前使用的AWS用户权限策略参考截图:
AWS用户权限策略截图


排查与解决方案

首先修正代码里的语法错误:你贴的上传代码存在书写问题,Storage::disk('s3'))多了一个右括号,file_get_contents($file)末尾缺少闭合括号,正确写法如下:

Storage::disk('s3')->put($filePath, file_get_contents($file));

注:打印S3实例时url字段为null属于正常情况,该字段仅用于配置S3自定义访问域名,不影响基础的文件读写操作,不是故障原因。

如果修正语法后问题仍存在,按以下优先级排查:

  1. 先获取AWS原始错误信息:保持s3配置中'throw' => true开启,直接触发请求拿到完整异常栈,不要只看Flysystem封装后的外层提示。AWS SDK会在异常里返回具体错误码(403权限不足、301区域不匹配、400签名失效等),拿到错误码就能直接定位绝大多数问题。
  2. 核对IAM权限配置:即使终端用相同凭证可正常操作,也要确认IAM用户绑定的权限策略Resource字段同时覆盖桶本身和桶内所有对象,正确的资源配置需要包含两项:arn:aws:s3:::veville-images、arn:aws:s3:::veville-images/*,如果只配置了桶本身的ARN,会没有权限操作桶内对象,触发读写失败。另外检查策略是否存在IP、VPC来源限制,确认Laravel运行环境(本地/Heroku)的出口IP在允许范围内。
  3. 检查运行环境时间同步:如果运行环境系统时间和AWS标准时间差超过15分钟,会导致V4签名验证失败,所有请求都会被拦截。终端CLI工具正常不代表Laravel运行环境时间正常,本地开发环境可以手动校准系统时间后重试。
  4. 排除配置冲突:你打印的实例配置里同时存在外层key/secret和嵌套credentials数组内的key/secret,检查是否有自定义配置手动注入了credentials项,Laravel默认的S3驱动会自动读取外层key/secret生成凭证数组,手动配置容易出现参数覆盖导致凭证异常。
  5. 做最小化调用测试:写个临时路由绕过Storage facade,直接调用S3客户端发起请求,排除上层封装的干扰:
    Route::get('/s3-test', function () {
        $client = new Aws\S3\S3Client([
            'version' => 'latest',
            'region'  => 'eu-west-3',
            'credentials' => [
                'key'    => '你的AWS_ACCESS_KEY_ID',
                'secret' => '你的AWS_SECRET_ACCESS_KEY',
            ],
        ]);
        $result = $client->putObject([
            'Bucket' => 'veville-images',
            'Key'    => 'test.txt',
            'Body'   => 'hello s3',
        ]);
        dd($result);
    });
    
    如果这个最小化调用也失败,就能直接拿到AWS返回的原始错误信息,无需再盲目排查配置。

内容的提问来源于stack exchange,提问作者Laravel-Dog

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:18:17