Laravel文件上传
头像、文章封面、产品图片和附件上传,是 Laravel 项目中很常见的功能。一个完整的文件上传流程不只是把文件保存到服务器,还要验证文件是否合法、限制大小、生成安全文件名、保存访问路径,并在文件更新或数据删除时清理旧文件。
Laravel 的文件系统基于统一的 Storage 接口,可以在尽量不改业务代码的情况下切换本地磁盘、SFTP 或对象存储。下面以文章封面上传为例,介绍文件验证、保存和访问的完整流程。
一、创建文件上传表单
上传文件的 HTML 表单必须使用 multipart/form-data 编码:
<form
action="{{ route('posts.store') }}"
method="POST"
enctype="multipart/form-data"
>
@csrf
<label for="title">文章标题</label>
<input
id="title"
type="text"
name="title"
value="{{ old('title') }}"
>
<label for="cover">文章封面</label>
<input
id="cover"
type="file"
name="cover"
accept="image/jpeg,image/png,image/webp"
>
<button type="submit">提交</button>
</form>
如果漏掉 enctype="multipart/form-data",浏览器不会按文件上传方式提交数据,Laravel 也无法正常取得文件。
accept 可以帮助用户筛选文件,但它只是浏览器端提示,不能替代服务端验证。
二、使用Form Request验证图片
创建上传请求类:
php artisan make:request StorePostRequest
在请求类中编写验证规则:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\File;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'cover' => [
'required',
File::image()
->types(['jpg', 'jpeg', 'png', 'webp'])
->max('2mb'),
],
];
}
public function messages(): array
{
return [
'cover.required' => '请选择封面图片',
'cover.image' => '上传文件必须是图片',
];
}
}
这里将文件限制为 JPG、JPEG、PNG 和 WebP,最大容量为 2MB。具体上限应根据网站用途调整,不建议允许用户无限制上传大图。
也可以使用字符串规则:
'cover' => [
'required',
'image',
'mimes:jpg,jpeg,png,webp',
'max:2048',
],
字符串规则中的 max:2048 对上传文件表示 2048KB。
三、image和mimes规则有什么区别
image 用于判断文件是否属于 Laravel 支持的图片类型。mimes 用于限制允许上传的文件扩展类型,但验证并不是只相信文件名后缀,Laravel 会读取文件内容并判断 MIME 类型。
为了让限制更明确,图片上传可以同时指定图片规则和允许类型。对于 SVG 文件要更加谨慎,因为 SVG 本质上是 XML,处理不当可能产生脚本执行等安全风险。没有明确需求时,不建议开放普通用户上传 SVG。
四、使用Storage保存上传文件
在控制器中注入已经配置好的请求类:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
class PostController extends Controller
{
public function store(StorePostRequest $request)
{
$path = $request->file('cover')->store(
'post-covers',
'public'
);
$post = Post::create([
'title' => $request->validated('title'),
'cover' => $path,
]);
return redirect()
->route('posts.show', $post)
->with('success', '文章创建成功');
}
}
store() 会生成相对安全的唯一文件名,并返回保存路径,例如:
post-covers/H8rB3nQ7h5VxP2sL9kWm.jpg
数据库中建议只保存相对路径,不要写死完整域名。这样更换域名、CDN 或存储服务时会更容易处理。
五、public磁盘保存在哪里
Laravel 默认文件系统配置位于:
config/filesystems.php
使用 public 磁盘保存的文件通常位于:
storage/app/public
为了让浏览器能够访问这些文件,需要在项目根目录执行:
php artisan storage:link
该命令通常会创建以下软链接:
public/storage -> storage/app/public
执行完成后,文件可以通过 /storage/ 路径访问。
部署到新服务器时,如果图片上传成功但浏览器访问返回 404,应优先检查软链接是否创建、Web 服务器用户是否有读取权限,以及站点根目录是否正确指向 Laravel 的 public 目录。
六、生成文件访问链接
可以使用 Storage::url() 生成访问地址:
use Illuminate\Support\Facades\Storage;
$url = Storage::disk('public')->url($post->cover);
在 Blade 模板中显示图片:
<img
src="{{ Storage::disk('public')->url($post->cover) }}"
alt="{{ $post->title }}封面"
>
如果文件路径是:
post-covers/example.jpg
本地 public 磁盘通常会生成:
/storage/post-covers/example.jpg
不要直接把服务器的物理路径输出到页面,例如 /var/www/project/storage/...。物理路径只用于服务器内部读写,不能作为浏览器访问地址。
七、检查文件是否上传成功
在保存前,可以判断请求中是否包含有效文件:
if ($request->hasFile('cover')) {
$file = $request->file('cover');
if ($file->isValid()) {
$path = $file->store('post-covers', 'public');
}
}
已经通过 Form Request 的 required 和文件规则验证后,一般可以直接保存。hasFile() 更适合处理选填文件或更新资料时的可选上传。
八、自定义文件名时要注意什么
如果业务必须保留可识别文件名,可以使用 storeAs():
use Illuminate\Support\Str;
$file = $request->file('cover');
$fileName = Str::uuid() . '.' . $file->extension();
$path = $file->storeAs(
'post-covers',
$fileName,
'public'
);
不要直接使用用户上传的原始文件名作为最终文件名。原始名称可能包含空格、中文、特殊字符、重复名称或不安全路径信息。
使用 UUID、随机字符串或业务 ID 生成文件名,会更适合长期管理。
九、更新图片时删除旧文件
编辑文章封面时,如果直接保存新文件而不删除旧文件,服务器中会不断积累无用图片。
示例:
use Illuminate\Support\Facades\Storage;
public function update(
UpdatePostRequest $request,
Post $post
) {
$data = $request->safe()->only(['title']);
if ($request->hasFile('cover')) {
$newPath = $request->file('cover')->store(
'post-covers',
'public'
);
if ($post->cover) {
Storage::disk('public')->delete($post->cover);
}
$data['cover'] = $newPath;
}
$post->update($data);
return redirect()
->route('posts.show', $post)
->with('success', '文章更新成功');
}
为了降低数据丢失风险,应先确认新文件保存成功,再删除旧文件。
十、删除数据时同步删除文件
删除文章时,也可以清理关联文件:
public function destroy(Post $post)
{
if ($post->cover) {
Storage::disk('public')->delete($post->cover);
}
$post->delete();
return redirect()
->route('posts.index')
->with('success', '文章已删除');
}
如果项目使用软删除,需要先确定文件是否还可能恢复。常见做法是软删除数据时暂时保留文件,永久删除记录时再清理文件。
十一、私有文件应该怎么保存
用户合同、内部附件和付费资源不应放在公开访问的 public 磁盘中。此类文件应保存到私有磁盘,并通过控制器完成权限验证后再下载。
保存私有文件:
$path = $request->file('document')->store(
'documents',
'local'
);
下载时先检查权限:
use Illuminate\Support\Facades\Storage;
public function download(Document $document)
{
$this->authorize('view', $document);
return Storage::disk('local')->download(
$document->path,
$document->original_name
);
}
不要仅凭一个可猜测的文件 URL 保护敏感文件。访问控制应在服务端完成。
广告合作
QQ群号:4114653
- 本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。
- 本站原创内容未经允许不得转载,转载请注明出处“站长百科”和原文地址。