Laravel 11 文件上传与存储

几乎每个 Web 应用都会涉及文件上传,如用户头像、文章配图、附件等。Laravel 提供了强大的文件上传处理能力,结合统一的文件系统 API,让你可以轻松将文件存储到本地磁盘、云存储(如阿里云 OSS、Amazon S3)等。 本章将详细介绍如何在 Laravel 中处理文件上传并进行存储。

🎯 文件系统配置

Laravel 的文件系统配置位于 config/filesystems.php,默认提供了 localpublic 两个磁盘:

'disks' => [
    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],
    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],
],
  • local 磁盘:文件存储在 storage/app 目录,默认不公开访问。
  • public 磁盘:文件存储在 storage/app/public,通过 php artisan storage:link 创建软链接后,可在 public/storage 访问。
💡 提示: 如需使用云存储(如 Amazon S3),需要安装对应的驱动包(league/flysystem-aws-s3-v3)并配置密钥。

📝 创建上传表单

文件上传表单必须设置 enctype="multipart/form-data",并包含 CSRF 令牌:

<form method="POST" action="{{ route('avatar.upload') }}" enctype="multipart/form-data">
    @csrf
    <div class="form-group">
        <label for="avatar">选择头像:</label>
        <input type="file" class="form-control @error('avatar') is-invalid @enderror" id="avatar" name="avatar">
        @error('avatar')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>
    <button type="submit" class="btn btn-primary">上传</button>
</form>

📦 控制器处理上传

在控制器中获取上传文件,验证后存储:

use Illuminate\Http\Request;

public function uploadAvatar(Request $request)
{
    $request->validate([
        'avatar' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', // 最大2MB
    ]);

    if ($request->hasFile('avatar')) {
        $file = $request->file('avatar');
        // 存储文件,返回保存路径
        $path = $file->store('avatars', 'public');

        // 也可以自定义文件名
        // $path = $file->storeAs('avatars', 'avatar-'.time().'.'.$file->getClientOriginalExtension(), 'public');

        // 将路径保存到数据库(例如用户表)
        auth()->user()->update(['avatar' => $path]);

        return back()->with('success', '头像上传成功!');
    }

    return back()->with('error', '未选择文件');
}
💡 关键方法:
  • $request->hasFile('avatar') — 判断文件是否存在。
  • $request->file('avatar') — 获取 UploadedFile 对象。
  • store($path, $disk) — 将文件存储到指定磁盘的路径下,自动生成唯一文件名。
  • storeAs($path, $name, $disk) — 存储并指定文件名。
  • getClientOriginalName() — 获取原始文件名。
  • getClientOriginalExtension() — 获取原始扩展名。
  • getSize() — 获取文件大小(字节)。

🔧 文件验证规则

Laravel 提供了丰富的文件验证规则:

  • file — 必须是成功上传的文件。
  • image — 必须是图片(jpg, png, bmp, gif, svg)。
  • mimes:foo,bar — 限制 MIME 类型,如 mimes:jpeg,png
  • mimetypes:text/plain,image/png — 更精确的 MIME 类型限制。
  • max:2048 — 最大文件大小(KB)。
  • dimensions — 限制图片尺寸,如 dimensions:min_width=100,min_height=200

示例:

$request->validate([
    'avatar' => 'required|image|dimensions:min_width=100,min_height=100|max:2048',
]);

📁 存储到不同磁盘

使用 Storage 门面可以更灵活地操作文件:

use Illuminate\Support\Facades\Storage;

// 存储到默认磁盘(local)
$path = Storage::putFile('avatars', $request->file('avatar'));

// 存储到 public 磁盘
$path = Storage::disk('public')->putFile('avatars', $request->file('avatar'));

// 存储并指定文件名
$path = Storage::disk('public')->putFileAs('avatars', $request->file('avatar'), 'avatar.jpg');

🖼️ 展示已上传文件

对于公开磁盘的文件,可以通过 asset() 辅助函数生成 URL:

<img src="{{ asset('storage/' . $user->avatar) }}" alt="头像">

如果文件存储在私有磁盘,可以通过控制器返回文件:

public function showAvatar($userId)
{
    $user = User::findOrFail($userId);
    $path = storage_path('app/' . $user->avatar);
    return response()->file($path);
}
⚠️ 注意: 使用 public 磁盘前必须执行 php artisan storage:link 创建软链接,否则无法通过 /storage 访问文件。

🎨 实战示例:用户头像上传与展示

1. 路由定义

Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::post('/profile/avatar', [ProfileController::class, 'uploadAvatar'])->name('profile.avatar');

2. 控制器方法

public function edit()
{
    return view('profile.edit', ['user' => auth()->user()]);
}

public function uploadAvatar(Request $request)
{
    $request->validate([
        'avatar' => 'required|image|max:2048',
    ]);

    $user = auth()->user();
    $path = $request->file('avatar')->store('avatars', 'public');

    // 删除旧头像(如果有)
    if ($user->avatar) {
        Storage::disk('public')->delete($user->avatar);
    }

    $user->update(['avatar' => $path]);

    return redirect()->route('profile.edit')->with('success', '头像更新成功!');
}

3. 视图 profile/edit.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
    <h1>个人资料</h1>

    @if (session('success'))
        <div class="alert alert-success">{{ session('success') }}</div>
    @endif

    <div class="row">
    <div class="col-md-4">
    <div class="card">
    <div class="card-body text-center">
    @if ($user->avatar)
        <img src="{{ asset('storage/' . $user->avatar) }}" class="img-thumbnail mb-3" width="150">
    @else
        <div class="mb-3" style="width:150px;height:150px;background:#eee;display:flex;align-items:center;justify-content:center">
        暂无头像
        </div>
    @endif
    <form method="POST" action="{{ route('profile.avatar') }}" enctype="multipart/form-data">
    @csrf
    <div class="mb-3">
    <input type="file" class="form-control @error('avatar') is-invalid @enderror" name="avatar">
    @error('avatar')
    <div class="invalid-feedback">{{ $message }}</div>
    @enderror
    </div>
    <button type="submit" class="btn btn-primary">更新头像</button>
    </form>
    </div>
    </div>
    </div>
    <div class="col-md-8">
    <!-- 其他资料表单 -->
    </div>
    </div>
    </div>
@endsection

⚠️ 常见问题

需要调整 PHP 配置:upload_max_filesizepost_max_sizememory_limit。对于超大文件,考虑使用分片上传或异步队列处理。

使用 store() 方法会自动生成唯一文件名(基于时间+随机字符串)。也可以手动生成:$filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();

安装对应的驱动包(如 league/flysystem-aws-s3-v3),配置 config/filesystems.php 中的 disks,然后使用 Storage::disk('oss')->putFile(...) 即可。

📝 小结

文件上传与存储是 Laravel 开发中的常见需求。通过 Request 对象获取上传文件,配合验证规则和存储方法,可以安全高效地处理用户文件。 掌握 Storage 门面的使用,能够让你轻松应对本地、云存储等多种场景。下一章我们将学习如何通过邮件通知用户,构建更完善的交互体验。