基于Laravel开发博客应用系列 —— 实现文件上传管理功能


本节我们将在后台为博客应用实现文件上传管理(包括文件上传、预览及删除、目录创建及删除)功能,并且使用本地文件系统保存上传的文件。

1、配置本地文件系统

让我们从配置开始吧,我们在 public 目录下创建一个 uploads 目录用来存放上传的文件,这样所有上传文件都可以通过浏览器直接访问。

首先我们在博客项目目录下使用如下命令在 public 目录下创建 uploads 子目录:

为Laravel博客创建文件保存目录

很简单。接下来我们来编辑 config/blog.php

  1. <?php
  2. return [
  3. 'title' => 'My Blog',
  4. 'posts_per_page' => 5,
  5. 'uploads' => [
  6. 'storage' => 'local',
  7. 'webpath' => '/uploads',
  8. ],
  9. ];

我们在 uploads 配置项中使用 storage 定义使用的文件系统,使用 webpath 定义 web 访问根目录。

最后,编辑 config/filesystems.php 如下:

  1. //将如下区块代码
  2. 'disks' => [
  3. 'local' => [
  4. 'driver' => 'local',
  5. 'root' => storage_path('app'),
  6. ],
  7.  
  8. // 修改成这样
  9. 'disks' => [
  10. 'local' => [
  11. 'driver' => 'local',
  12. 'root' => public_path('uploads'),
  13. ],

我们将本地存储的根目录修改为前面创建的 public/uploads 目录。

2、创建帮助函数文件

在 Laravel 5.1 项目中有时我们会需要一些不依赖于类的辅助函数,通常我们会将这些辅助函数定义在一个单独文件如  helpers.php 中。我们在 app 目录下创建这个名为 helpers.php 的文件,并编辑其内容如下:

  1. <?php
  2.  
  3. /**
  4. * 返回可读性更好的文件尺寸
  5. */
  6. function human_filesize($bytes, $decimals = 2)
  7. {
  8. $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
  9. $factor = floor((strlen($bytes) - 1) / 3);
  10.  
  11. return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) .@$size[$factor];
  12. }
  13.  
  14. /**
  15. * 判断文件的MIME类型是否为图片
  16. */
  17. function is_image($mimeType)
  18. {
  19. return starts_with($mimeType, 'image/');
  20. }

其中 human_filesize() 函数返回一个易读的文件尺寸,is_image() 函数在文件类型为图片的时候返回 true

要让应用能够正确找到 helpers.php 文件,还要修改项目根目录下的 composer.json

  1. {
  2. ...
  3. "autoload": {
  4. "classmap": [
  5. "database"
  6. ],
  7. "psr-4": {
  8. "App\\": "app/"
  9. },
  10. "files": [
  11. "app/helpers.php"
  12. ]
  13. },
  14. ...
  15. }

autoload 配置项的 files 数组中指定要被加载的文件/文件夹。修改完成后记得运行 composer dumpauto 确保修改生效:

composer dumpauto

现在 helpers.php 中的所有函数都会载入到自动加载器中,你可以在博客应用的代码中任意使用其中的函数。

3、创建文件上传管理服务

现在基本配置已经完成了,让我们创建一个服务类来管理上传文件。

检测文件 MIME 类型

我们想要基于不同类型的上传文件进行不同的操作,这可以通过检测上传文件 MIME 类型轻松实现。

PHP 有一个内置函数 mime_content_type() 用于检测文件的MIME类型,但是该函数已经废弃了,我们使用另一个解决方案。

在 Packagist 中搜索 “mime” 会查询到一个名为 dflydev 的包,我们在博客项目中使用 Composer 安装该依赖包:

  1. composer require "dflydev/apache-mime-types"

我们将使用该依赖包提供的方法来检测文件的 MIME 类型。

创建UploadsManager类

app/Services 目录下创建 UploadsManager.php,编辑其内容如下:

  1. <?php
  2.  
  3. namespace App\Services;
  4.  
  5. use Carbon\Carbon;
  6. use Dflydev\ApacheMimeTypes\PhpRepository;
  7. use Illuminate\Support\Facades\Storage;
  8.  
  9. class UploadsManager
  10. {
  11. protected $disk;
  12. protected $mimeDetect;
  13.  
  14. public function __construct(PhpRepository $mimeDetect)
  15. {
  16. $this->disk = Storage::disk(config('blog.uploads.storage'));
  17. $this->mimeDetect = $mimeDetect;
  18. }
  19.  
  20. /**
  21. * Return files and directories within a folder
  22. *
  23. * @param string $folder
  24. * @return array of [
  25. * 'folder' => 'path to current folder',
  26. * 'folderName' => 'name of just current folder',
  27. * 'breadCrumbs' => breadcrumb array of [ $path => $foldername ]
  28. * 'folders' => array of [ $path => $foldername] of each subfolder
  29. * 'files' => array of file details on each file in folder
  30. * ]
  31. */
  32. public function folderInfo($folder)
  33. {
  34. $folder = $this->cleanFolder($folder);
  35.  
  36. $breadcrumbs = $this->breadcrumbs($folder);
  37. $slice = array_slice($breadcrumbs, -1);
  38. $folderName = current($slice);
  39. $breadcrumbs = array_slice($breadcrumbs, 0, -1);
  40.  
  41. $subfolders = [];
  42. foreach (array_unique($this->disk->directories($folder)) as $subfolder) {
  43. $subfolders["/$subfolder"] = basename($subfolder);
  44. }
  45.  
  46. $files = [];
  47. foreach ($this->disk->files($folder) as $path) {
  48. $files[] = $this->fileDetails($path);
  49. }
  50.  
  51. return compact(
  52. 'folder',
  53. 'folderName',
  54. 'breadcrumbs',
  55. 'subfolders',
  56. 'files'
  57. );
  58. }
  59.  
  60. /**
  61. * Sanitize the folder name
  62. */
  63. protected function cleanFolder($folder)
  64. {
  65. return '/' . trim(str_replace('..', '', $folder), '/');
  66. }
  67.  
  68. /**
  69. * 返回当前目录路径
  70. */
  71. protected function breadcrumbs($folder)
  72. {
  73. $folder = trim($folder, '/');
  74. $crumbs = ['/' => 'root'];
  75.  
  76. if (empty($folder)) {
  77. return $crumbs;
  78. }
  79.  
  80. $folders = explode('/', $folder);
  81. $build = '';
  82. foreach ($folders as $folder) {
  83. $build .= '/'.$folder;
  84. $crumbs[$build] = $folder;
  85. }
  86.  
  87. return $crumbs;
  88. }
  89.  
  90. /**
  91. * 返回文件详细信息数组
  92. */
  93. protected function fileDetails($path)
  94. {
  95. $path = '/' . ltrim($path, '/');
  96.  
  97. return [
  98. 'name' => basename($path),
  99. 'fullPath' => $path,
  100. 'webPath' => $this->fileWebpath($path),
  101. 'mimeType' => $this->fileMimeType($path),
  102. 'size' => $this->fileSize($path),
  103. 'modified' => $this->fileModified($path),
  104. ];
  105. }
  106.  
  107. /**
  108. * 返回文件完整的web路径
  109. */
  110. public function fileWebpath($path)
  111. {
  112. $path = rtrim(config('blog.uploads.webpath'), '/') . '/' .ltrim($path, '/');
  113. return url($path);
  114. }
  115.  
  116. /**
  117. * 返回文件MIME类型
  118. */
  119. public function fileMimeType($path)
  120. {
  121. return $this->mimeDetect->findType(
  122. pathinfo($path, PATHINFO_EXTENSION)
  123. );
  124. }
  125.  
  126. /**
  127. * 返回文件大小
  128. */
  129. public function fileSize($path)
  130. {
  131. return $this->disk->size($path);
  132. }
  133.  
  134. /**
  135. * 返回最后修改时间
  136. */
  137. public function fileModified($path)
  138. {
  139. return Carbon::createFromTimestamp(
  140. $this->disk->lastModified($path)
  141. );
  142. }
  143. }

4、实现文件上传管理列表

现在 UploadsManager 服务类已经创建,接下来我们来实现控制器的 index 方法。

创建 index 方法

编辑 app/Http/Controllers/Admin 目录下的 UploadController.php 文件内容如下:

  1. <?php
  2.  
  3. namespace App\Http\Controllers\Admin;
  4.  
  5. use App\Http\Controllers\Controller;
  6. use App\Services\UploadsManager;
  7. use Illuminate\Http\Request;
  8.  
  9. class UploadController extends Controller
  10. {
  11. protected $manager;
  12.  
  13. public function __construct(UploadsManager $manager)
  14. {
  15. $this->manager = $manager;
  16. }
  17.  
  18. /**
  19. * Show page of files / subfolders
  20. */
  21. public function index(Request $request)
  22. {
  23. $folder = $request->get('folder');
  24. $data = $this->manager->folderInfo($folder);
  25.  
  26. return view('admin.upload.index', $data);
  27. }
  28. }

构造方法中注入了 UploadsManager 依赖,在 index() 方法中只需传入 folderInfo() 返回的数据到要渲染的视图并返回即可。

你可能已经注意到 $folder 从请求中获取,是的,我们只需要通过请求参数即可实现文件夹修改。

创建 index 视图

resources/views/admin 目录下新建 upload 目录,并在该目录下创建 index.blade.php 文件,编辑该文件内容如下:

  1. @extends('admin.layout')
  2.  
  3. @section('content')
  4. <div class="container-fluid">
  5.  
  6. {{-- 顶部工具栏 --}}
  7. <div class="row page-title-row">
  8. <div class="col-md-6">
  9. <h3 class="pull-left">Uploads </h3>
  10. <div class="pull-left">
  11. <ul class="breadcrumb">
  12. @foreach ($breadcrumbs as $path => $disp)
  13. <li><a href="/admin/upload?folder={{ $path }}">{{ $disp }}</a></li>
  14. @endforeach
  15. <li class="active">{{ $folderName }}</li>
  16. </ul>
  17. </div>
  18. </div>
  19. <div class="col-md-6 text-right">
  20. <button type="button" class="btn btn-success btn-md" data-toggle="modal" data-target="#modal-folder-create">
  21. <i class="fa fa-plus-circle"></i> New Folder
  22. </button>
  23. <button type="button" class="btn btn-primary btn-md" data-toggle="modal" data-target="#modal-file-upload">
  24. <i class="fa fa-upload"></i> Upload
  25. </button>
  26. </div>
  27. </div>
  28.  
  29. <div class="row">
  30. <div class="col-sm-12">
  31.  
  32. @include('admin.partials.errors')
  33. @include('admin.partials.success')
  34.  
  35. <table id="uploads-table" class="table table-striped table-bordered">
  36. <thead>
  37. <tr>
  38. <th>Name</th>
  39. <th>Type</th>
  40. <th>Date</th>
  41. <th>Size</th>
  42. <th data-sortable="false">Actions</th>
  43. </tr>
  44. </thead>
  45. <tbody>
  46.  
  47. {{-- 子目录 --}}
  48. @foreach ($subfolders as $path => $name)
  49. <tr>
  50. <td>
  51. <a href="/admin/upload?folder={{ $path }}">
  52. <i class="fa fa-folder fa-lg fa-fw"></i>
  53. {{ $name }}
  54. </a>
  55. </td>
  56. <td>Folder</td>
  57. <td>-</td>
  58. <td>-</td>
  59. <td>
  60. <button type="button" class="btn btn-xs btn-danger" onclick="delete_folder('{{ $name }}')">
  61. <i class="fa fa-times-circle fa-lg"></i>
  62. Delete
  63. </button>
  64. </td>
  65. </tr>
  66. @endforeach
  67.  
  68. {{-- 所有文件 --}}
  69. @foreach ($files as $file)
  70. <tr>
  71. <td>
  72. <a href="{{ $file['webPath'] }}">
  73. @if (is_image($file['mimeType']))
  74. <i class="fa fa-file-image-o fa-lg fa-fw"></i>
  75. @else
  76. <i class="fa fa-file-o fa-lg fa-fw"></i>
  77. @endif
  78. {{ $file['name'] }}
  79. </a>
  80. </td>
  81. <td>{{ $file['mimeType'] or 'Unknown' }}</td>
  82. <td>{{ $file['modified']->format('j-M-y g:ia') }}</td>
  83. <td>{{ human_filesize($file['size']) }}</td>
  84. <td>
  85. <button type="button" class="btn btn-xs btn-danger" onclick="delete_file('{{ $file['name'] }}')">
  86. <i class="fa fa-times-circle fa-lg"></i>
  87. Delete
  88. </button>
  89. @if (is_image($file['mimeType']))
  90. <button type="button" class="btn btn-xs btn-success" onclick="preview_image('{{ $file['webPath'] }}')">
  91. <i class="fa fa-eye fa-lg"></i>
  92. Preview
  93. </button>
  94. @endif
  95. </td>
  96. </tr>
  97. @endforeach
  98.  
  99. </tbody>
  100. </table>
  101.  
  102. </div>
  103. </div>
  104. </div>
  105.  
  106. @include('admin.upload._modals')
  107.  
  108. @stop
  109.  
  110. @section('scripts')
  111. <script>
  112.  
  113. // 确认文件删除
  114. function delete_file(name) {
  115. $("#delete-file-name1").html(name);
  116. $("#delete-file-name2").val(name);
  117. $("#modal-file-delete").modal("show");
  118. }
  119.  
  120. // 确认目录删除
  121. function delete_folder(name) {
  122. $("#delete-folder-name1").html(name);
  123. $("#delete-folder-name2").val(name);
  124. $("#modal-folder-delete").modal("show");
  125. }
  126.  
  127. // 预览图片
  128. function preview_image(path) {
  129. $("#preview-image").attr("src", path);
  130. $("#modal-image-view").modal("show");
  131. }
  132.  
  133. // 初始化数据
  134. $(function() {
  135. $("#uploads-table").DataTable();
  136. });
  137. </script>
  138. @stop

尽管这个模板文件很长,但是理解起来并没有什么困难,所有文件上传和下载管理都将在这里进行。

有没有注意到我们在最后包含了 admin.upload._modals?是的,我们将模态对话框放到了一个单独的视图模板中。现在,我们在  resources/views/admin/upload 目录下创建一个空的 _modals.blade.php 文件。

上传管理界面

打开浏览器,进入博客应用后台管理页面,点击顶部导航条的“上传”(Uploads)链接,将会跳转到如下页面:

Laravel博客后台文件上传列表

既漂亮又清爽,有木有?接下来让我们来实现所有的模态对话框及其背后的业务逻辑。

5、完成文件上传管理功能

对于完整的文件上传管理器而言剩下的工作已经不多了,现在是时候完成所有功能了。

添加路由

我们需要为上传管理器定义所有需要的路由,编辑 app/Http/routes.php 添加如下路由:

  1. // 在这一行下面
  2. get('admin/upload', 'UploadController@index');
  3.  
  4. // 添加如下路由
  5. post('admin/upload/file', 'UploadController@uploadFile');
  6. delete('admin/upload/file', 'UploadController@deleteFile');
  7. post('admin/upload/folder', 'UploadController@createFolder');
  8. delete('admin/upload/folder', 'UploadController@deleteFolder');

定义所有模态对话框

编辑我们之前创建的 _modals.blade.php 文件内容如下:

  1. {{-- 创建目录 --}}
  2. <div class="modal fade" id="modal-folder-create">
  3. <div class="modal-dialog">
  4. <div class="modal-content">
  5. <form method="POST" action="/admin/upload/folder" class="form-horizontal">
  6. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  7. <input type="hidden" name="folder" value="{{ $folder }}">
  8. <div class="modal-header">
  9. <button type="button" class="close" data-dismiss="modal">
  10. ×
  11. </button>
  12. <h4 class="modal-title">Create New Folder</h4>
  13. </div>
  14. <div class="modal-body">
  15. <div class="form-group">
  16. <label for="new_folder_name" class="col-sm-3 control-label">
  17. Folder Name
  18. </label>
  19. <div class="col-sm-8">
  20. <input type="text" id="new_folder_name" name="new_folder" class="form-control">
  21. </div>
  22. </div>
  23. </div>
  24. <div class="modal-footer">
  25. <button type="button" class="btn btn-default" data-dismiss="modal">
  26. Cancel
  27. </button>
  28. <button type="submit" class="btn btn-primary">
  29. Create Folder
  30. </button>
  31. </div>
  32. </form>
  33. </div>
  34. </div>
  35. </div>
  36.  
  37. {{-- 删除文件 --}}
  38. <div class="modal fade" id="modal-file-delete">
  39. <div class="modal-dialog">
  40. <div class="modal-content">
  41. <div class="modal-header">
  42. <button type="button" class="close" data-dismiss="modal">
  43. ×
  44. </button>
  45. <h4 class="modal-title">Please Confirm</h4>
  46. </div>
  47. <div class="modal-body">
  48. <p class="lead">
  49. <i class="fa fa-question-circle fa-lg"></i>
  50. Are you sure you want to delete the
  51. <kbd><span id="delete-file-name1">file</span></kbd>
  52. file?
  53. </p>
  54. </div>
  55. <div class="modal-footer">
  56. <form method="POST" action="/admin/upload/file">
  57. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  58. <input type="hidden" name="_method" value="DELETE">
  59. <input type="hidden" name="folder" value="{{ $folder }}">
  60. <input type="hidden" name="del_file" id="delete-file-name2">
  61. <button type="button" class="btn btn-default" data-dismiss="modal">
  62. Cancel
  63. </button>
  64. <button type="submit" class="btn btn-danger">
  65. Delete File
  66. </button>
  67. </form>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72.  
  73. {{-- 删除目录 --}}
  74. <div class="modal fade" id="modal-folder-delete">
  75. <div class="modal-dialog">
  76. <div class="modal-content">
  77. <div class="modal-header">
  78. <button type="button" class="close" data-dismiss="modal">
  79. ×
  80. </button>
  81. <h4 class="modal-title">Please Confirm</h4>
  82. </div>
  83. <div class="modal-body">
  84. <p class="lead">
  85. <i class="fa fa-question-circle fa-lg"></i>
  86. Are you sure you want to delete the
  87. <kbd><span id="delete-folder-name1">folder</span></kbd>
  88. folder?
  89. </p>
  90. </div>
  91. <div class="modal-footer">
  92. <form method="POST" action="/admin/upload/folder">
  93. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  94. <input type="hidden" name="_method" value="DELETE">
  95. <input type="hidden" name="folder" value="{{ $folder }}">
  96. <input type="hidden" name="del_folder" id="delete-folder-name2">
  97. <button type="button" class="btn btn-default" data-dismiss="modal">
  98. Cancel
  99. </button>
  100. <button type="submit" class="btn btn-danger">
  101. Delete Folder
  102. </button>
  103. </form>
  104. </div>
  105. </div>
  106. </div>
  107. </div>
  108.  
  109. {{-- 上传文件 --}}
  110. <div class="modal fade" id="modal-file-upload">
  111. <div class="modal-dialog">
  112. <div class="modal-content">
  113. <form method="POST" action="/admin/upload/file" class="form-horizontal" enctype="multipart/form-data">
  114. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  115. <input type="hidden" name="folder" value="{{ $folder }}">
  116. <div class="modal-header">
  117. <button type="button" class="close" data-dismiss="modal">
  118. ×
  119. </button>
  120. <h4 class="modal-title">Upload New File</h4>
  121. </div>
  122. <div class="modal-body">
  123. <div class="form-group">
  124. <label for="file" class="col-sm-3 control-label">
  125. File
  126. </label>
  127. <div class="col-sm-8">
  128. <input type="file" id="file" name="file">
  129. </div>
  130. </div>
  131. <div class="form-group">
  132. <label for="file_name" class="col-sm-3 control-label">
  133. Optional Filename
  134. </label>
  135. <div class="col-sm-4">
  136. <input type="text" id="file_name" name="file_name" class="form-control">
  137. </div>
  138. </div>
  139. </div>
  140. <div class="modal-footer">
  141. <button type="button" class="btn btn-default" data-dismiss="modal">
  142. Cancel
  143. </button>
  144. <button type="submit" class="btn btn-primary">
  145. Upload File
  146. </button>
  147. </div>
  148. </form>
  149. </div>
  150. </div>
  151. </div>
  152.  
  153. {{-- 浏览图片 --}}
  154. <div class="modal fade" id="modal-image-view">
  155. <div class="modal-dialog">
  156. <div class="modal-content">
  157. <div class="modal-header">
  158. <button type="button" class="close" data-dismiss="modal">
  159. ×
  160. </button>
  161. <h4 class="modal-title">Image Preview</h4>
  162. </div>
  163. <div class="modal-body">
  164. <img id="preview-image" src="x" class="img-responsive">
  165. </div>
  166. <div class="modal-footer">
  167. <button type="button" class="btn btn-default" data-dismiss="modal">
  168. Cancel
  169. </button>
  170. </div>
  171. </div>
  172. </div>
  173. </div>

在该文件中总共有5个不同的模态弹出框,分别对应上面定义的5个路由。

添加表单请求验证类

使用 Artisan 命令创建 UploadFileRequest,并编辑其内容如下:

  1. <?php
  2.  
  3. namespace App\Http\Requests;
  4.  
  5. use App\Http\Requests\Request;
  6.  
  7. class UploadFileRequest extends Request
  8. {
  9. /**
  10. * Determine if the user is authorized to make this request.
  11. *
  12. * @return bool
  13. */
  14. public function authorize()
  15. {
  16. return true;
  17. }
  18.  
  19. /**
  20. * Get the validation rules that apply to the request.
  21. *
  22. * @return array
  23. */
  24. public function rules()
  25. {
  26. return [
  27. 'file' => 'required',
  28. 'folder' => 'required',
  29. ];
  30. }
  31. }

使用 Artisan 命令创建 UploadNewFolderRequest,并编辑其内容如下:

  1. <?php
  2.  
  3. namespace App\Http\Requests;
  4.  
  5. use App\Http\Requests\Request;
  6.  
  7. class UploadNewFolderRequest extends Request
  8. {
  9. /**
  10. * Determine if the user is authorized to make this request.
  11. *
  12. * @return bool
  13. */
  14. public function authorize()
  15. {
  16. return true;
  17. }
  18.  
  19. /**
  20. * Get the validation rules that apply to the request.
  21. *
  22. * @return array
  23. */
  24. public function rules()
  25. {
  26. return [
  27. 'folder' => 'required',
  28. 'new_folder' => 'required',
  29. ];
  30. }
  31. }

同样,这些请求类用于验证表单输入。

完成 UploadController 所有方法

编辑 UploadController.php 文件内容如下:

  1. <?php
  2.  
  3. // 添加如下三个use语句到UploadController控制器顶部
  4. use App\Http\Requests\UploadFileRequest;
  5. use App\Http\Requests\UploadNewFolderRequest;
  6. use Illuminate\Support\Facades\File;
  7.  
  8. // 添加如下四个方法到UploadController控制器类
  9. /**
  10. * 创建新目录
  11. */
  12. public function createFolder(UploadNewFolderRequest $request)
  13. {
  14. $new_folder = $request->get('new_folder');
  15. $folder = $request->get('folder').'/'.$new_folder;
  16.  
  17. $result = $this->manager->createDirectory($folder);
  18.  
  19. if ($result === true) {
  20. return redirect()
  21. ->back()
  22. ->withSuccess("Folder '$new_folder' created.");
  23. }
  24.  
  25. $error = $result ? : "An error occurred creating directory.";
  26. return redirect()
  27. ->back()
  28. ->withErrors([$error]);
  29. }
  30.  
  31. /**
  32. * 删除文件
  33. */
  34. public function deleteFile(Request $request)
  35. {
  36. $del_file = $request->get('del_file');
  37. $path = $request->get('folder').'/'.$del_file;
  38.  
  39. $result = $this->manager->deleteFile($path);
  40.  
  41. if ($result === true) {
  42. return redirect()
  43. ->back()
  44. ->withSuccess("File '$del_file' deleted.");
  45. }
  46.  
  47. $error = $result ? : "An error occurred deleting file.";
  48. return redirect()
  49. ->back()
  50. ->withErrors([$error]);
  51. }
  52.  
  53. /**
  54. * 删除目录
  55. */
  56. public function deleteFolder(Request $request)
  57. {
  58. $del_folder = $request->get('del_folder');
  59. $folder = $request->get('folder').'/'.$del_folder;
  60.  
  61. $result = $this->manager->deleteDirectory($folder);
  62.  
  63. if ($result === true) {
  64. return redirect()
  65. ->back()
  66. ->withSuccess("Folder '$del_folder' deleted.");
  67. }
  68.  
  69. $error = $result ? : "An error occurred deleting directory.";
  70. return redirect()
  71. ->back()
  72. ->withErrors([$error]);
  73. }
  74.  
  75. /**
  76. * 上传文件
  77. */
  78. public function uploadFile(UploadFileRequest $request)
  79. {
  80. $file = $_FILES['file'];
  81. $fileName = $request->get('file_name');
  82. $fileName = $fileName ?: $file['name'];
  83. $path = str_finish($request->get('folder'), '/') . $fileName;
  84. $content = File::get($file['tmp_name']);
  85.  
  86. $result = $this->manager->saveFile($path, $content);
  87.  
  88. if ($result === true) {
  89. return redirect()
  90. ->back()
  91. ->withSuccess("File '$fileName' uploaded.");
  92. }
  93.  
  94. $error = $result ? : "An error occurred uploading file.";
  95. return redirect()
  96. ->back()
  97. ->withErrors([$error]);
  98. }

完成 UploadsManager 服务类

最后编辑 app/Services/UploadsManager.php 内容如下:

  1. <?php
  2. // 在该类中新增以下4个方法
  3. /**
  4. * 创建新目录
  5. */
  6. public function createDirectory($folder)
  7. {
  8. $folder = $this->cleanFolder($folder);
  9.  
  10. if ($this->disk->exists($folder)) {
  11. return "Folder '$folder' already exists.";
  12. }
  13.  
  14. return $this->disk->makeDirectory($folder);
  15. }
  16.  
  17. /**
  18. * 删除目录
  19. */
  20. public function deleteDirectory($folder)
  21. {
  22. $folder = $this->cleanFolder($folder);
  23.  
  24. $filesFolders = array_merge(
  25. $this->disk->directories($folder),
  26. $this->disk->files($folder)
  27. );
  28. if (! empty($filesFolders)) {
  29. return "Directory must be empty to delete it.";
  30. }
  31.  
  32. return $this->disk->deleteDirectory($folder);
  33. }
  34.  
  35. /**
  36. * 删除文件
  37. */
  38. public function deleteFile($path)
  39. {
  40. $path = $this->cleanFolder($path);
  41.  
  42. if (! $this->disk->exists($path)) {
  43. return "File does not exist.";
  44. }
  45.  
  46. return $this->disk->delete($path);
  47. }
  48.  
  49. /**
  50. * 保存文件
  51. */
  52. public function saveFile($path, $content)
  53. {
  54. $path = $this->cleanFolder($path);
  55.  
  56. if ($this->disk->exists($path)) {
  57. return "File already exists.";
  58. }
  59.  
  60. return $this->disk->put($path, $content);
  61. }

至此,已经完成了文件上传管理的所有工作,可以去浏览器完成上传文件、创建目录、删除文件等操作了。

6、测试文件上传和删除功能

在浏览器中访问 http://blog.app/admin/upload,点击“New Folder”创建新目录,在弹出的模态对话框中填写表单:

Laravel博客后台文件管理新建目录

点击“Create Folder”提交表单,创建目录成功:

Laravel博客后台文件管理新建目录成功

点击进入新创建的子目录 laravelacademy,在该目录下点击“Upload”按钮上传文件:

Laravel博客后台文件管理上传图片

点击“Upload File”上传文件,上传成功后跳转到文件列表:

Laravel博客后台文件管理上传图片成功

但是去 public/uploads/laravelacademy 目录下查看,上传的文件 Laravel学院 并没有扩展名,而且上面列表里 Type 类型值为 Unkown,预览按钮没显示出来也说明了有问题,正确的文件名应该包含扩展名,看来是上传图片时填写的文件名称有问题,应该这样填写为 LaravelAcademy.jpg,这样重新上传后文件列表显示如下:

Laravel博客后台文件管理上传图片成功

这样数据都对了,预览按钮也显示出来了,点击“Preview”按钮,页面显示如下:

Laravel博客后台文件管理预览图片

最后我们将无效的Laravel学院文件删除,点击其对应的“Delete”按钮,页面弹出确认删除对话框:

Laravel博客后台文件管理删除图片

点击“Delete File”,确认删除。