什么是文件存储
Laravel 基于 Flysystem 这一 PHP 扩展,提供了一层强大的文件系统抽象。
本地磁盘、SFTP、Amazon S3 等不同的存储后端都可以通过相同的 API 操作,因此在不同环境间切换时无需修改代码。
即便更换驱动,代码也不用改。开发用本地磁盘、生产用 S3 的组合可以轻松实现。
文件系统的配置集中在 config/filesystems.php 中。
你可以在这里定义“磁盘(disk)”,也就是一组“驱动 + 存储位置”的组合。
主要驱动如下:
| 驱动 | 用途 |
|---|
local | 服务器本地文件系统 |
public | 面向公网访问的本地磁盘 |
s3 | Amazon S3(或兼容 S3 的服务) |
sftp | SFTP 服务器 |
ftp | FTP 服务器 |
local 驱动
使用 local 驱动时,操作会以 filesystems 中配置的 root 目录为基准。
默认情况下 root 是 storage/app/private。
use Illuminate\Support\Facades\Storage;
Storage::disk('local')->put('example.txt', 'Contents');
// 会写入到 storage/app/private/example.txt
修改默认磁盘
可以通过 FILESYSTEM_DISK 环境变量切换默认磁盘。
public 磁盘与符号链接
public 磁盘用于存放需要通过 Web 访问的文件。
默认会保存到 storage/app/public 目录。
要让 Web 服务器可以访问,需要从 public/storage 建立到 storage/app/public 的符号链接。
获取文件 URL
创建符号链接后,可以用 asset 辅助函数生成 URL。echo asset('storage/file.txt');
如果需要更多符号链接,可以在 config/filesystems.php 的 links 数组中配置。'links' => [
public_path('storage') => storage_path('app/public'),
public_path('images') => storage_path('app/images'),
],
要删除符号链接,可以使用 storage:unlink。
php artisan storage:unlink
Storage Facade 的基本操作
读取文件
use Illuminate\Support\Facades\Storage;
// 以字符串形式获取文件内容
$contents = Storage::get('file.jpg');
// 读取并解析 JSON 文件
$data = Storage::json('orders.json');
// 判断文件是否存在
if (Storage::exists('file.jpg')) {
// 文件存在
}
// 判断文件是否不存在
if (Storage::missing('file.jpg')) {
// 文件不存在
}
写入文件
// 写入文件
Storage::put('file.jpg', $contents);
// 以流的方式写入资源
Storage::put('file.jpg', $resource);
// 在开头或末尾追加
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');
// 复制与移动
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
put 失败时默认返回 false。
可以在磁盘配置中设置 'throw' => true 来改为抛出异常。
删除文件
// 删除单个文件
Storage::delete('file.jpg');
// 删除多个文件
Storage::delete(['file.jpg', 'file2.jpg']);
// 从指定磁盘删除文件
Storage::disk('s3')->delete('path/file.jpg');
文件下载响应
// 返回一个促使浏览器下载文件的响应
return Storage::download('file.jpg');
// 指定文件名与响应头
return Storage::download('file.jpg', 'my-file.jpg', $headers);
生成 URL
普通 URL
use Illuminate\Support\Facades\Storage;
$url = Storage::url('file.jpg');
local 驱动会返回类似 /storage/file.jpg 的相对 URL。
s3 驱动会返回完整的远程 URL。
临时 URL(Temporary URL)
要生成带有有效期的 URL,可以使用 temporaryUrl 方法。
local 与 s3 驱动都支持。
use Illuminate\Support\Facades\Storage;
$url = Storage::temporaryUrl(
'file.jpg',
now()->plus(minutes: 5)
);
对 S3,还可以附加额外的请求参数。
$url = Storage::temporaryUrl(
'file.jpg',
now()->plus(minutes: 5),
[
'ResponseContentType' => 'application/octet-stream',
'ResponseContentDisposition' => 'attachment; filename=file.jpg',
]
);
需要临时上传 URL 时,可以使用 temporaryUploadUrl。
在客户端直接向 S3 上传的无服务器架构中非常有用。['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
'file.jpg', now()->plus(minutes: 5)
);
文件上传
以下是保存用户表单上传文件的常见模式。
store 方法(自动生成文件名)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserAvatarController extends Controller
{
public function update(Request $request): string
{
// 会根据 MIME 类型自动推断扩展名并生成文件名
$path = $request->file('avatar')->store('avatars');
return $path;
}
}
storeAs 方法(指定文件名)
$path = $request->file('avatar')->storeAs(
'avatars',
$request->user()->id
);
指定磁盘上传
// 保存到 s3 磁盘
$path = $request->file('avatar')->store(
'avatars/' . $request->user()->id,
's3'
);
通过 Storage Facade 上传
use Illuminate\Support\Facades\Storage;
// 自动生成文件名
$path = Storage::putFile('avatars', $request->file('avatar'));
// 指定文件名
$path = Storage::putFileAs(
'avatars',
$request->file('avatar'),
$request->user()->id
);
getClientOriginalName() 与 getClientOriginalExtension() 依赖用户提供的信息,可能被伪造,因此并不安全。
建议用 hashName() 生成文件名,用 extension() 判断扩展名。$file = $request->file('avatar');
$name = $file->hashName(); // 生成唯一的随机文件名
$extension = $file->extension(); // 根据 MIME 类型推断扩展名
文件可见性(Visibility)
Flysystem 通过 visibility 管理文件的公开/私有状态。
use Illuminate\Support\Facades\Storage;
// 写入时指定可见性
Storage::put('file.jpg', $contents, 'public');
// 获取和修改可见性
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');
上传的文件如果需要以公开状态保存,可以使用 storePublicly。
$path = $request->file('avatar')->storePublicly('avatars', 's3');
使用多个磁盘
通过 disk 方法可以切换要操作的磁盘。
// 保存到默认磁盘
Storage::put('avatars/1', $content);
// 保存到 s3 磁盘
Storage::disk('s3')->put('avatars/1', $content);
// 按需构建一个磁盘
$disk = Storage::build([
'driver' => 'local',
'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
云存储(S3)配置
安装扩展包
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
环境变量配置
在 .env 中配置 S3 的凭证信息。
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_USE_PATH_STYLE_ENDPOINT=false
DigitalOcean Spaces、Cloudflare R2、Vultr Object Storage 等兼容 S3 的服务也可以使用 s3 驱动。
在 endpoint 选项中指定服务的 endpoint URL 即可。'endpoint' => env('AWS_ENDPOINT', 'https://your-endpoint.example.com'),
获取文件元数据
use Illuminate\Support\Facades\Storage;
// 文件大小(字节)
$size = Storage::size('file.jpg');
// 最后修改时间(UNIX 时间戳)
$time = Storage::lastModified('file.jpg');
// MIME 类型
$mime = Storage::mimeType('file.jpg');
// 文件的绝对路径(local 驱动)
$path = Storage::path('file.jpg');
目录操作
use Illuminate\Support\Facades\Storage;
// 目录下的文件列表
$files = Storage::files($directory);
// 包含子目录的文件列表
$files = Storage::allFiles($directory);
// 目录列表
$directories = Storage::directories($directory);
// 创建目录
Storage::makeDirectory($directory);
// 删除目录及其中的所有文件
Storage::deleteDirectory($directory);
使用 Storage::fake() 可以在不触碰真实磁盘的情况下编写文件操作的测试。
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('可以上传头像', function () {
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$this->post('/user/avatar', ['avatar' => $file]);
// 确认文件已保存
Storage::disk('avatars')->assertExists('avatar.jpg');
// 确认文件未被保存
Storage::disk('avatars')->assertMissing('other.jpg');
});
使用 UploadedFile::fake()->image() 需要 PHP 的 GD 扩展。
实战示例:头像上传
下面是一个综合了校验、保存、路径写入数据库的实战控制器示例。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
public function updateAvatar(Request $request): RedirectResponse
{
$request->validate([
'avatar' => ['required', 'image', 'max:2048'],
]);
$user = $request->user();
// 删除已有的头像
if ($user->avatar_path) {
Storage::disk('public')->delete($user->avatar_path);
}
// 保存新头像
$path = $request->file('avatar')->store('avatars', 'public');
// 将路径写入数据库
$user->update(['avatar_path' => $path]);
return back()->with('status', 'avatar-updated');
}
}
在模板中通过 Storage::url() 获取 URL。
<img src="{{ Storage::disk('public')->url($user->avatar_path) }}" alt="头像">