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用户权限策略参考截图:
排查与解决方案
首先修正代码里的语法错误:你贴的上传代码存在书写问题,Storage::disk('s3'))多了一个右括号,file_get_contents($file)末尾缺少闭合括号,正确写法如下:
Storage::disk('s3')->put($filePath, file_get_contents($file));
注:打印S3实例时
url字段为null属于正常情况,该字段仅用于配置S3自定义访问域名,不影响基础的文件读写操作,不是故障原因。
如果修正语法后问题仍存在,按以下优先级排查:
- 先获取AWS原始错误信息:保持s3配置中
'throw' => true开启,直接触发请求拿到完整异常栈,不要只看Flysystem封装后的外层提示。AWS SDK会在异常里返回具体错误码(403权限不足、301区域不匹配、400签名失效等),拿到错误码就能直接定位绝大多数问题。 - 核对IAM权限配置:即使终端用相同凭证可正常操作,也要确认IAM用户绑定的权限策略Resource字段同时覆盖桶本身和桶内所有对象,正确的资源配置需要包含两项:
arn:aws:s3:::veville-images、arn:aws:s3:::veville-images/*,如果只配置了桶本身的ARN,会没有权限操作桶内对象,触发读写失败。另外检查策略是否存在IP、VPC来源限制,确认Laravel运行环境(本地/Heroku)的出口IP在允许范围内。 - 检查运行环境时间同步:如果运行环境系统时间和AWS标准时间差超过15分钟,会导致V4签名验证失败,所有请求都会被拦截。终端CLI工具正常不代表Laravel运行环境时间正常,本地开发环境可以手动校准系统时间后重试。
- 排除配置冲突:你打印的实例配置里同时存在外层key/secret和嵌套credentials数组内的key/secret,检查是否有自定义配置手动注入了credentials项,Laravel默认的S3驱动会自动读取外层key/secret生成凭证数组,手动配置容易出现参数覆盖导致凭证异常。
- 做最小化调用测试:写个临时路由绕过Storage facade,直接调用S3客户端发起请求,排除上层封装的干扰:
如果这个最小化调用也失败,就能直接拿到AWS返回的原始错误信息,无需再盲目排查配置。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); });
内容的提问来源于stack exchange,提问作者Laravel-Dog
相关产品推荐
相关产品推荐

