基于 Laravel + Vue 组件实现文件异步上传


我们在上一篇教程中已经演示了如何通过 Request 请求实例获取各种文本输入数据,但是还有一种输入数据我们没有涉及到,那就是文件上传。我们可以通过 Request 请求实例提供的 file 方法获取用户上传文件,并将其保存到指定目录从而完成文件上传,接下来,我们将从前端到后端实现一个完整的用户上传文件功能,包括视图、路由、控制器部分代码。

定义文件上传路由

首先我们在 routes/web.php 中定义上传文件涉及到的路由:

我们定义了一个 GET 路由 /form,用于渲染用户上传表单页面,然后定义了一个 POST 路由 /form/file_upload,用于实现文件上传逻辑。

接下来去控制器中初始化这两个方法。

初始化控制器方法

打开 app/Http/Controllers/RequestController.php,初始化路由定义中指定的控制器方法,首先是 formPage 方法用于渲染表单视图,我们约定视图路径是 request.form(可以先创建一个空的视图文件 resources/views/request/form.blade.php):

然后是 fileUpload 方法,用于处理 POST 请求实现文件上传,假定前端文件输入框对应 name 属性是 picture,如果请求内容中包含该字段,则将对应文件实例打印出来(文件上传保存实现代码后面再完善):

编写前端表单视图

接下来,就可以到前端编写视图文件了,我们将通过单独的 Vue 组件实现前端文件异步上传操作,所以编写视图文件 resources/views/request/form.blade.php 代码如下:

我们会在表单控件中使用 Bootstrap 样式,所以引入了 css/app.css 文件,同时为 HTML 元素设置相应的 class 属性,将文件上传控件拆分成一个独立的 Vue 组件,并通过 <fileupload-component></fileupload-component> 引入。此外,需要注意的是我们在页面顶部添加了如下这行代码:

这是为了后续通过 axios 发送 POST 请求的时候(axios 是一个功能强大的基于 Promise 的 JavaScript HTTP 客户端,推荐使用它来替代传统的 ajaxXMLHttpRequest API 发送 HTTP 请求),方便在请求头中全局设置 CSRF Token,在 axios 请求头添加 CSRF Token 的逻辑位于 resources/js/bootstrap.js 文件:

意思是从当前页面 meta 元标签中获取 [name="csrf-token"] 的值并将其设置到 axios 的请求头字段 X-CSRF-TOKEN 中,每次发送 POST 请求时会自动带上它,这样经过 CSRF 保护中间件时校验该请求头字段通过则认为是安全请求放行(具体原理不了解的话可以看 CSRF 防护教程)。

编写文件上传 Vue 组件

完成视图模板文件的编写之后,接下来就可以编写文件上传 Vue 组件了。在 resources/js/components 目录下新增 FileUploadComponent.vue,编写代码如下:

这算是一个比较完整的 Vue 组件代码了,既有 HTML 模板代码,又有 CSS 和 JavaScript 代码,代码逻辑很简单,就是监听到文件上传控件有变动时调用 uploadFile 方法,通过 axios 发送包含文件信息的 POST 请求到 /form/file_upload 路由,由于我们发送的是上传文件请求,所以必须将内容类型设置为 multipart/form-data,如果后端处理成功则打印响应信息,否则打印失败信息。

然后在 resources/js/app.js 文件中将这个组件全局注册到 Vue 实例:

注:如果是在 Laravel 5.8+ 中,需要这样注册:Vue.component('fileupload-component', require('./components/FileUploadComponent.vue').default);,否则在使用的时候会报错。

这样在 form.blade.php 视图中就可以正常引入该组件了。

测试文件上传功能

至此,我们完成了前端视图和 Vue 组件的编写,运行 npm run dev 重新编译前端资源,访问 http://blog.test/form 就可以测试文件上传了,先打开 F12 监听上传请求,然后上传一张图片,上传成功后,就可以看到后端打印的文件信息了:

完善后端文件上传代码

通过打印信息可以看出,$request->file() 方法获取的是一个 Illuminate\Http\UploadedFile 对象实例,该类继承自 PHP SPL 库中提供与文件交互方法的 SplFileInfo 类,我们通过该类提供的方法就可以完成上传文件保存操作:

关于 UploadedFile 提供的更多方法,可以去源码中查看。我们使用了 Storage::disk('public') 磁盘将上传文件保存到本地,关于该磁盘的自定义配置信息可以去 config/filesystems.php 文件中查看,我们将其保存到此磁盘的原因是图片一般都是提供对外访问的,如果上传的是其他格式的私密文件,不想被外部访问,需要将其保存到 local 磁盘,你还可以将其保存到云存储服务中,关于完整的文件系统我们放到后面去讲,现在你只需了解保存到哪里,以及如何配置自定义路径。

如果要让上传到 storage/app/public 目录的文件可以被外部访问,还要执行以下命令:

该命令会在项目根目录下的 public 中创建一个软链 storage 指向 storage/app/public,这样,后者下面的文件才可以真正通过 Web 路径访问。

优化前端图片上传组件代码

接下来,回到 resources/js/components/FileUploadComponent.vue 组件,对前端文件上传代码进行调整和优化。文件上传成功后,将返回路径更新到一个隐藏的字段,以便后续跟随表单上传,并且提供图片预览功能,以便拥有更好的用户体验:

再次测试文件上传功能

至此,基于 Laravel + Vue 组件的文件异步上传功能就全部完成了。运行 npm run dev 重新编译前端资源,再次访问表单页面,重新上传一张新的图片:

上传成功后,就能通过图片 Web 路径预览刚刚上传的图片了。

如果你想对上传图片进行裁剪、压缩、生成缩略图等操作,可以试试 Intervention Image 这个扩展包,功能非常强大,基本能够满足你的所有图片处理需求。


<< 上一篇: 通过 Request 对象实例获取用户请求数据

>> 下一篇: 在 Laravel 控制器中进行表单请求字段验证