Skip to content

文件上传

Livewire 提供了强大的支持来在组件中上传文件。

首先,将 WithFileUploads trait 添加到你的组件。一旦将此 trait 添加到组件,你就可以像使用任何其他输入类型一样在文件输入上使用 wire:model,Livewire 将处理其余的事情。

这是一个处理照片上传的简单组件示例:

php
<?php // resources/views/components/⚡upload-photo.blade.php

use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;

new class extends Component {
    use WithFileUploads;

    #[Validate('image|max:1024')] // 最大 1MB
    public $photo;

    public function save()
    {
        $this->photo->store(path: 'photos');
    }
};
blade
<form wire:submit="save">
    <input type="file" wire:model="photo">

    @error('photo') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">保存照片</button>
</form>

"upload" 方法是保留的

注意上面的示例使用"save"方法而不是"upload"方法。这是一个常见的"陷阱"。术语"upload"被 Livewire 保留。你不能在组件中将其用作方法或属性名称。

从开发者的角度来看,处理文件输入与处理任何其他输入类型没有区别:将 wire:model 添加到 <input> 标签,其他一切都会为你处理好。

然而,在幕后发生了更多事情来使文件上传在 Livewire 中工作。以下是用户选择要上传的文件时发生的事情概览:

  1. 当选择新文件时,Livewire 的 JavaScript 向服务器上的组件发出初始请求以获取临时"签名"上传 URL。
  2. 收到 URL 后,JavaScript 执行实际的"上传"到签名 URL,将上传存储在 Livewire 指定的临时目录中并返回新临时文件的唯一哈希 ID。
  3. 文件上传并生成唯一哈希 ID 后,Livewire 的 JavaScript 向服务器上的组件发出最终请求,告诉它将所需的公共属性"设置"为新的临时文件。
  4. 现在,公共属性(在本例中为 $photo)被设置为临时文件上传,并且随时可以存储或验证。

存储上传的文件

前面的示例演示了最基本的存储场景:将临时上传的文件移动到应用程序默认文件系统磁盘上的"photos"目录。

然而,你可能想要自定义存储文件的文件名,甚至指定特定的存储"磁盘"来保存文件(如 S3)。

原始文件名

你可以通过调用其 ->getClientOriginalName() 方法来访问临时上传的原始文件名。

Livewire 遵循 Laravel 用于存储上传文件的相同 API,因此请随意查阅 Laravel 的文件上传文档。然而,下面是一些常见的存储场景和示例:

php
public function save()
{
    // 将文件存储在默认文件系统磁盘的"photos"目录中
    $this->photo->store(path: 'photos');

    // 将文件存储在配置的"s3"磁盘的"photos"目录中
    $this->photo->store(path: 'photos', options: 's3');

    // 将文件以文件名"avatar.png"存储在"photos"目录中
    $this->photo->storeAs(path: 'photos', name: 'avatar');

    // 将文件以文件名"avatar.png"存储在配置的"s3"磁盘的"photos"目录中
    $this->photo->storeAs(path: 'photos', name: 'avatar', options: 's3');

    // 将文件以"public"可见性存储在配置的"s3"磁盘的"photos"目录中
    $this->photo->storePublicly(path: 'photos', options: 's3');

    // 将文件以名称"avatar.png"、"public"可见性存储在配置的"s3"磁盘的"photos"目录中
    $this->photo->storePubliclyAs(path: 'photos', name: 'avatar', options: 's3');
}

处理多个文件

Livewire 通过检测 <input> 标签上的 multiple 属性自动处理多文件上传。

例如,下面是一个具有名为 $photos 的数组属性的组件。通过向表单的文件输入添加 multiple,Livewire 将自动将新文件追加到此数组:

php
<?php // resources/views/components/⚡upload-photos.blade.php

use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;

new class extends Component {
    use WithFileUploads;

    #[Validate(['photos.*' => 'image|max:1024'])]
    public $photos = [];

    public function save()
    {
        foreach ($this->photos as $photo) {
            $photo->store(path: 'photos');
        }
    }
};
blade
<form wire:submit="save">
    <input type="file" wire:model="photos" multiple>

    @error('photos.*') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">保存照片</button>
</form>

文件验证

如我们所讨论的,使用 Livewire 验证文件上传与从标准 Laravel 控制器处理文件上传相同。

确保正确配置 S3

许多与文件相关的验证规则需要访问文件。当直接上传到 S3 时,如果 S3 文件对象不是公开可访问的,这些验证规则将失败。

有关文件验证的更多信息,请查阅 Laravel 的文件验证文档

临时预览 URL

用户选择文件后,你通常应该在他们提交表单并存储文件之前向他们显示该文件的预览。

Livewire 通过在上传的文件上使用 ->temporaryUrl() 方法使这变得微不足道。

临时 URL 仅限于图像

出于安全原因,临时预览 URL 仅支持具有图像 MIME 类型的文件。

让我们探索一个带有图像预览的文件上传示例:

php
<?php // resources/views/components/⚡upload-photo.blade.php

use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;

new class extends Component {
    use WithFileUploads;

    #[Validate('image|max:1024')]
    public $photo;

    // ...
};
blade
<form wire:submit="save">
    @if ($photo)
        <img src="{{ $photo->temporaryUrl() }}">
    @endif

    <input type="file" wire:model="photo">

    @error('photo') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">保存照片</button>
</form>

如前所述,Livewire 将临时文件存储在非公开目录中;因此,通常没有简单的方法向用户公开用于图像预览的临时公共 URL。

然而,Livewire 通过提供临时签名 URL 来解决此问题,该 URL 伪装成上传的图像,以便你的页面可以向用户显示图像预览。

此 URL 受到保护,防止显示临时目录上方目录中的文件。而且,因为它是签名的,用户不能滥用此 URL 来预览系统上的其他文件。

S3 临时签名 URL

如果你已将 Livewire 配置为使用 S3 进行临时文件存储,调用 ->temporaryUrl() 将直接生成到 S3 的临时签名 URL,以便图像预览不会从你的 Laravel 应用程序服务器加载。

测试文件上传

你可以使用 Laravel 现有的文件上传测试助手来测试文件上传。

下面是使用 Livewire 测试 UploadPhoto 组件的完整示例:

php
<?php

namespace Tests\Feature\Livewire;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use App\Livewire\UploadPhoto;
use Livewire\Livewire;
use Tests\TestCase;

class UploadPhotoTest extends TestCase
{
    public function test_can_upload_photo()
    {
        Storage::fake('avatars');

        $file = UploadedFile::fake()->image('avatar.png');

        Livewire::test(UploadPhoto::class)
            ->set('photo', $file)
            ->call('upload', 'uploaded-avatar.png');

        Storage::disk('avatars')->assertExists('uploaded-avatar.png');
    }
}

下面是使上述测试通过所需的 upload-photo 组件示例:

php
<?php // resources/views/components/⚡upload-photo.blade.php

use Livewire\WithFileUploads;
use Livewire\Component;

new class extends Component {
    use WithFileUploads;

    public $photo;

    public function upload($name)
    {
        $this->photo->storeAs('/', $name, disk: 'avatars');
    }

    // ...
};

有关测试文件上传的更多信息,请查阅 Laravel 的文件上传测试文档

直接上传到 Amazon S3

如前所述,Livewire 将所有文件上传存储在临时目录中,直到开发者永久存储该文件。

默认情况下,Livewire 使用默认的文件系统磁盘配置(通常是 local)并将文件存储在 livewire-tmp/ 目录中。

因此,即使你选择稍后将上传的文件存储在 S3 存储桶中,文件上传也始终使用你的应用程序服务器。

如果你希望绕过应用程序服务器,而是将 Livewire 的临时上传存储在 S3 存储桶中,你可以在应用程序的 config/livewire.php 配置文件中配置该行为。首先,将 livewire.temporary_file_upload.disk 设置为 s3(或另一个使用 s3 驱动程序的自定义磁盘):

php
return [
    // ...
    'temporary_file_upload' => [
        'disk' => 's3',
        // ...
    ],
];

现在,当用户上传文件时,该文件实际上永远不会存储在你的服务器上。相反,它将直接上传到你的 S3 存储桶中的 livewire-tmp/ 子目录。

发布 Livewire 的配置文件

在自定义文件上传磁盘之前,你必须首先通过运行以下命令将 Livewire 的配置文件发布到应用程序的 /config 目录:

shell
php artisan livewire:config

配置自动文件清理

Livewire 的临时上传目录会很快被文件填满;因此,配置 S3 清理超过 24 小时的文件是必不可少的。

要配置此行为,请从使用 S3 存储桶进行文件上传的环境运行以下 Artisan 命令:

shell
php artisan livewire:configure-s3-upload-cleanup

现在,任何超过 24 小时的临时文件都将被 S3 自动清理。

INFO

如果你没有使用 S3 进行文件存储,Livewire 将自动处理文件清理,无需运行上述命令。

加载指示器

尽管文件上传的 wire:model 在幕后与其他 wire:model 输入类型的工作方式不同,但显示加载指示器的接口保持不变。

你可以使用 wire:loading 显示作用于文件上传的加载指示器:

blade
<input type="file" wire:model="photo">

<div wire:loading wire:target="photo">上传中...</div>

或者更简单地使用 Livewire 的自动 data-loading 属性:

blade
<div>
    <input type="file" wire:model="photo">

    <div class="not-data-loading:hidden">上传中...</div>
</div>

现在,当文件上传时,将显示"上传中..."消息,上传完成后将隐藏。

了解更多关于加载状态 →

进度指示器

每个 Livewire 文件上传操作都会在相应的 <input> 元素上分发 JavaScript 事件,允许自定义 JavaScript 拦截事件:

事件描述
livewire-upload-start上传开始时分发
livewire-upload-finish上传成功完成时分发
livewire-upload-cancel上传提前取消时分发
livewire-upload-error上传失败时分发
livewire-upload-progress包含上传进度百分比的事件,随着上传进度更新

下面是将 Livewire 文件上传包装在 Alpine 组件中以显示上传进度条的示例:

blade
<form wire:submit="save">
    <div
        x-data="{ uploading: false, progress: 0 }"
        x-on:livewire-upload-start="uploading = true"
        x-on:livewire-upload-finish="uploading = false"
        x-on:livewire-upload-cancel="uploading = false"
        x-on:livewire-upload-error="uploading = false"
        x-on:livewire-upload-progress="progress = $event.detail.progress"
    >
        <!-- 文件输入 -->
        <input type="file" wire:model="photo">

        <!-- 进度条 -->
        <div x-show="uploading">
            <progress max="100" x-bind:value="progress"></progress>
        </div>
    </div>

    <!-- ... -->
</form>

取消上传

如果上传花费很长时间,用户可能想要取消它。你可以使用 Livewire 在 JavaScript 中的 $cancelUpload() 函数提供此功能。

这是一个使用 wire:click 处理点击事件在 Livewire 组件中创建"取消上传"按钮的示例:

blade
<form wire:submit="save">
    <!-- 文件输入 -->
    <input type="file" wire:model="photo">

    <!-- 取消上传按钮 -->
    <button type="button" wire:click="$cancelUpload('photo')">取消上传</button>

    <!-- ... -->
</form>

当按下"取消上传"时,文件上传请求将被中止,文件输入将被清除。用户现在可以尝试使用不同的文件进行另一次上传。

或者,你可以像这样从 Alpine 调用 cancelUpload(...)

blade
<button type="button" x-on:click="$wire.cancelUpload('photo')">取消上传</button>

JavaScript 上传 API

与第三方文件上传库集成通常需要比简单的 <input type="file" wire:model="..."> 元素更多的控制。

对于这些场景,Livewire 公开了专用的 JavaScript 函数。

这些函数存在于 JavaScript 组件对象上,可以使用 Livewire 方便的 $wire 对象从 Livewire 组件模板中访问:

blade
<script>
    let file = $wire.el.querySelector('input[type="file"]').files[0]

    // 上传文件...
    $wire.upload('photo', file, (uploadedFilename) => {
        // 成功回调...
    }, () => {
        // 错误回调...
    }, (event) => {
        // 进度回调...
        // event.detail.progress 包含 1 到 100 之间的数字,表示上传进度
    }, () => {
        // 取消回调...
    })

    // 上传多个文件...
    $wire.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback, cancelledCallback)

    // 从多个上传的文件中删除单个文件...
    $wire.removeUpload('photos', uploadedFilename, successCallback)

    // 取消上传...
    $wire.cancelUpload('photos')
</script>

配置

因为 Livewire 在开发者可以验证或存储之前临时存储所有文件上传,它假设所有文件上传的一些默认处理行为。

全局验证

默认情况下,Livewire 将使用以下规则验证所有临时文件上传:file|max:12288(必须是小于 12MB 的文件)。

如果你希望自定义这些规则,可以在应用程序的 config/livewire.php 文件中进行:

php
'temporary_file_upload' => [
    // ...
    'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (最大 100MB,只接受 PNG、JPEG 和 PDF)
],

全局中间件

临时文件上传端点默认分配了节流中间件。你可以通过以下配置选项自定义此端点使用的中间件:

php
'temporary_file_upload' => [
    // ...
    'middleware' => 'throttle:5,1', // 每用户每分钟只允许 5 次上传
],

临时上传目录

临时文件上传到指定磁盘的 livewire-tmp/ 目录。你可以通过以下配置选项自定义此目录:

php
'temporary_file_upload' => [
    // ...
    'directory' => 'tmp',
],

另请参阅