sun-kang

教程源自:Laravel学院

这一节 咱来说说上传文件的功能实现,我们会把上传的文件保存到项目本地,不仅上传 还有删除和预览功能。

 


1 配置

我们先从配置开始做起,先修改我们自己创建的 blog.php

<?php
return [
    \'title\' => "Larger K\'s Blog",
    \'posts_per_page\' => 10,
    \'uploads\' => [
        \'storage\' => \'local\',       // 储存处
        \'webpath\' => \'/uploads\'     // 储存路径
    ],
];

然后编辑 config/filesystems.php

    \'disks\' => [

        \'local\' => [
            \'driver\' => \'local\',
//            \'root\'   => storage_path(\'app\'),
            \'root\'  => public_path(\'uploads\'),
        ],

 


2 创建文件服务类

我们需要创建一个Manager来封装需要用到的功能。

2.1 引入dflydev

difydev包是主要用于分别MIME类型的,我们需要根据不同的文件类型进行不同的操作,所以需要使用到它,如果你记不住它的名字可以到Packagist 中搜索 MIME

然后我们使用 composer 引入它:

composer require "dflydev/apache-mime-types"

2.2 创建UploadsManager

我们在 app/Service 目录下创建:

<?php
namespace App\Services;

use Carbon\Carbon;
use Dflydev\ApacheMimeTypes\PhpRepository;
use Illuminate\Support\Facades\Storage;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;

class UploadsManager {
    protected $disk;
    protected $mimeDelect;

    /**
     * UploadsServices constructor.
     * @param $mimeDelect
     */
    public function __construct(PhpRepository $mimeDelect)
    {
        $this->mimeDelect = $mimeDelect;
        $this->disk = Storage::disk(config(\'blag.uploads.storage\'));
    }

    /**
     * 返回目录详情
     *
     * @param $folder
     * @return array [
     *      \'folder\',           文件夹的路径
     *      \'folderName\',       文件夹名称
     *      \'breadcrumbs\',      文件夹被分割后的数组
     *      \'subfolders\',       此文件夹下的所有子文件夹
     *      \'files\'             此文件夹下的所有文件详情
     * ]
     */
    public function folderInfo($folder)
    {
        // 处理$folder
        $folder = $this->cleanFolder($folder);
        // 获得路径数组
        $breadcrumbs = $this->breadcrumbs($folder);
        // 取出最后一段
        $slice = array_slice($breadcrumbs, -1);
        // 获得当前数组指针下的value
        $folderName = current($slice);
        // 获取0到倒数第二个的片段
        $breadcrumbs = array_slice($breadcrumbs, 0, -1);
        // 获得子目录
        $subfolders = [];
        foreach (array_unique($this->disk->directories($folder)) as $subfolder) {
            $subfolders["/$subfolder"] = basename($subfolder);
        }
        // 获得文件
        $files = [];
        foreach ($this->disk->files($folder) as $path) {
            $files[] = $this->fileDetails($path);
        }

        return compact(
            \'folder\',
            \'folderName\',
            \'breadcrumbs\',
            \'subfolders\',
            \'files\'
        );
    }

    /**
     * 去除路径中首尾的..和/
     *
     * @param string $folder
     * @return string
     * 例子:传入 ../public/images/home/01/ 返回 "/public/images/home/01"
     */
    protected function cleanFolder($folder)
    {
        return \'/\' . trim(str_replace(\'..\', \'\', $folder), \'/\');
    }

    /**
     * 返回路径数组
     *
     * @param $folder
     * @return array when $folder is empty[
     *      \'/\' => \'root\'
     * ] when folder is not empty[
     *      "/" => "root"
     *      "/public" => "public"
     *      "/images" => "images"
     *      "/home" => "home"
     * ]
     */
    protected function breadcrumbs($folder)
    {
        // 去除首尾的 /
        $folder = trim($folder, \'/\');
        $crumbs = [\'/\' => \'root\'];
        if (empty($folder)){
            return $crumbs;
        }
        // 分割路径
        $folders = explode(\'/\', $folder);
        // build
        $build = \'\';
        foreach ($folders as $folder) {
            $build = \'/\'.$folder;
            $crumbs[$build] = $folder;
        }
        return $crumbs;
    }

    /**
     * 返回文件的详情信息
     *
     * @param string $path
     * @return array
     */
    protected function fileDetails($path)
    {
        $path = \'/\' . trim($path, \'/\');
        return [
            \'name\' => basename($path),
            \'fullpath\' => $path,
            \'webPath\' => $this->fileWebpath($path),
            \'mimeType\' => $this->fileMimeType($path),
            \'size\' => $this->fileSize($path),
            \'modified\' => $this->fileModified($path),
        ];
    }

    /**
     * 返回文件的Web路径
     *
     * @param $path
     * @return string
     */
    protected function fileWebpath($path)
    {
        $path = rtrim(config(\'blog.uploads.webpath\'), \'/\') . \'/\' . ltrim($path, \'/\');
        return url($path);
    }

    /**
     * 返回文件的MIME类型
     *
     * @param $path
     * @return mixed|null|string
     */
    protected function fileMimeType($path)
    {
        return $this->mimeDelect->findType(
            pathinfo(
                $path, PATHINFO_EXTENSION
            )
        );
    }

    /**
     * 返回文件的大小
     *
     * @param $path
     * @return mixed
     */
    protected function fileSize($path)
    {
        return $this->disk->size($path);
    }

    /**
     * 返回最后一次被修改的时间
     *
     * @param $path
     * @return static
     */
    protected function fileModified($path)
    {
        return Carbon::createFromTimestamp(
            $this->disk->lastModified($path)
        );
    }
}

注释写的很尽力了,敲一遍你就懂了,上面的代码主要是folderInfo这个方法 它返回我们需要用到的一些数据

2.3 创建帮助函数

其实把Manager创建好后就可以展示视图了,但是方便我们使用先来创建两个帮助函数,在app目录下创建一个helper.php

<?php
/**
 * 返回可读性更好的文件大小
 *
 * @param $bytes
 * @param int $decimals
 * @return string
 */
function human_filesize($bytes, $decimals = 2){
    $size = [\'B\', \'kB\', \'MB\', \'GB\', \'TB\', \'PB\'];
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) .@$size[$factor];
}

/**
 * 判断mime type是否是图片类型
 *
 * @param $mime_type
 * @return bool
 */
function is_image($mime_type){
    return starts_with($mime_type, \'image/\');
}

 现在有个问题,我们如何在不import的情况下使用到帮助函数呢 它并不是一个类 没有命名空间,解决这个方法就要修改下composer.json文件让它自动加载文件:

   "autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App\\": "app/"
        },
        "files": [
            "app/helper.php"
        ]

最后执行以下命令:

composer dumpauto

 


3 展示文件视图

3.1 首先编辑UploadController的index方法:

class UploadController extends Controller
{
    protected $manager;

    /**
     * UploadController constructor.
     * @param UploadsManager $manager
     */
    public function __construct(UploadsManager $manager)
    {
        $this->manager = $manager;
    }

    public function index(Request $request)
    {
        // 取到目录的详情
        $data = $this->manager->folderInfo($request->get(\'folder\'));
        return view(\'admin.upload.index\', $data);
    }
}

创建一个 /admin/upload/index.blade.php

@extends(\'admin.layout\')

@section(\'content\')
<div class="container-fluid">

    {{-- 顶部工具栏 --}}
    <div class="row page-title-row">
        <div class="col-md-6">
            <h3 class="pull-left">Uploads </h3>
            <div class="pull-left">
                <ul class="breadcrumb">
                    @foreach ($breadcrumbs as $path => $disp)
                        <li><a href="/admin/upload?folder={{ $path }}">{{ $disp }}</a></li>
                    @endforeach
                    <li class="active">{{ $folderName }}</li>
                </ul>
            </div>
        </div>
        <div class="col-md-6 text-right">
            <button type="button" class="btn btn-success btn-md" data-toggle="modal" data-target="#modal-folder-create">
                <i class="fa fa-plus-circle"></i> New Folder
            </button>
            <button type="button" class="btn btn-primary btn-md" data-toggle="modal" data-target="#modal-file-upload">
                <i class="fa fa-upload"></i> Upload
            </button>
        </div>
    </div>

    <div class="row">
        <div class="col-sm-12">

            @include(\'admin.partials.error\')
            @include(\'admin.partials.success\')

            <table id="uploads-table" class="table table-striped table-bordered">
                <thead>
                <tr>
                    <th>Name</th>
                    <th>Type</th>
                    <th>Date</th>
                    <th>Size</th>
                    <th data-sortable="false">Actions</th>
                </tr>
                </thead>
                <tbody>

                {{-- 子目录 --}}
                @foreach ($subfolders as $path => $name)
                    <tr>
                        <td>
                            <a href="/admin/upload?folder={{ $path }}">
                                <i class="fa fa-folder fa-lg fa-fw"></i>
                                {{ $name }}
                            </a>
                        </td>
                        <td>Folder</td>
                        <td>-</td>
                        <td>-</td>
                        <td>
                            <button type="button" class="btn btn-xs btn-danger" onclick="delete_folder(\'{{ $name }}\')">
                                <i class="fa fa-times-circle fa-lg"></i>
                                Delete
                            </button>
                        </td>
                    </tr>
                @endforeach

                {{-- 所有文件 --}}
                @foreach ($files as $file)
                    <tr>
                        <td>
                            <a href="{{ $file[\'webPath\'] }}">
                                @if (is_image($file[\'mimeType\']))
                                    <i class="fa fa-file-image-o fa-lg fa-fw"></i>
                                @else
                                    <i class="fa fa-file-o fa-lg fa-fw"></i>
                                @endif
                                {{ $file[\'name\'] }}
                            </a>
                        </td>
                        <td>{{ $file[\'mimeType\'] or \'Unknown\' }}</td>
                        <td>{{ $file[\'modified\']->format(\'j-M-y g:ia\') }}</td>
                        <td>{{ human_filesize($file[\'size\']) }}</td>
                        <td>
                            <button type="button" class="btn btn-xs btn-danger" onclick="delete_file(\'{{ $file[\'name\'] }}\')">
                                <i class="fa fa-times-circle fa-lg"></i>
                                Delete
                            </button>
                            @if (is_image($file[\'mimeType\']))
                                <button type="button" class="btn btn-xs btn-success" onclick="preview_image(\'{{ $file[\'webPath\'] }}\')">
                                    <i class="fa fa-eye fa-lg"></i>
                                    Preview
                                </button>
                            @endif
                        </td>
                    </tr>
                @endforeach

                </tbody>
            </table>

        </div>
    </div>
</div>

@include(\'admin.upload._modal\')
@endsection

@section(\'scripts\')
    <script>
        $(function(){
            $("#uploads-table").DataTable();
        })
    </script>
@endsection

经过上面的代码,我们可以看到展示效果,你可以自己在upload目录下创建几个文件夹和文件试试。

下面我们继续写我们 include的 _modal

{{--创建目录--}}
<div class="modal fade" id="modal-folder-create">
    <div class="modal-dialog">
        <div class="modal-content">
            <form action="/admin/upload/folder" method="post" class="form-horizontal">
                <input type="hidden" name="_token" value="{{ csrf_token() }}">
                {{--隐式传递文件夹--}}
                <input type="hidden" name="folder" value="{{ $folder }}">

                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal">
                        x
                    </button>
                    <h4 class="modal-title">Create New Folder</h4>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label for="new_folder_name" class="col-sm-3 control-label">Folder Name</label>
                        <div class="col-sm-8">
                            <input type="text" id="new_folder_name" class="form-control" name="new_folder">
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                    <button type="submit" class="btn btn-primary">Create Folder</button>
                </div>
            </form>
        </div>
    </div>
</div>

{{-- 删除目录 --}}
<div class="modal fade" id="modal-folder-delete">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                    ×
                </button>
                <h4 class="modal-title">Please Confirm</h4>
            </div>
            <div class="modal-body">
                <p class="lead">
                    <i class="fa fa-question-circle fa-lg"></i>
                    Are you sure you want to delete the
                    <kbd><span id="delete-folder-name1">folder</span></kbd>
                    folder?
                </p>
            </div>
            <div class="modal-footer">
                <form method="POST" action="/admin/upload/folder">
                    <input type="hidden" name="_token" value="{{ csrf_token() }}">
                    <input type="hidden" name="_method" value="DELETE">
                    <input type="hidden" name="folder" value="{{ $folder }}">
                    <input type="hidden" name="del_folder" id="delete-folder-name2">
                    <button type="button" class="btn btn-default" data-dismiss="modal">
                        Cancel
                    </button>
                    <button type="submit" class="btn btn-danger">
                        Delete Folder
                    </button>
                </form>
            </div>
        </div>
    </div>
</div>

{{--删除文件--}}
<div class="modal fade" id="modal-file-delete">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <div class="modal-title">
                    <button type="button" class="close" data-dismiss="modal">
                        x
                    </button>
                    <h4 class="modal-title">Please Confirm</h4>
                </div>
            </div>
            <div class="modal-body">
                <p class="lead">
                    <i class="fa fa-question-circle fa-lg"></i>
                    Are you sure your want to delete the <kbd><span id="delete-file-name1"></span></kbd>
                    file?
                </p>
            </div>
            <div class="modal-footer">
                <form action="/admin/upload/file" method="post">
                    <input type="hidden" name="_token" value="{{ csrf_token() }}">
                    <input type="hidden" name="_method" value="DELETE">
                    <input type="hidden" name="folder" value="{{ $folder }}">
                    <input type="hidden" name="del_file" id="delete-file-name2">
                    <button type="button" class="btn btn-default" data-dismiss="modal">
                        Cancel
                    </button>
                    <button type="submit" class="btn btn-danger">
                        Delete File
                    </button>
                </form>
            </div>
        </div>
    </div>
</div>

{{--上传文件--}}
<div class="modal fade" id="modal-file-upload">
    <div class="modal-dialog">
        <div class="modal-content">
            <form action="/admin/upload/file" method="post" class="form-horizontal" enctype="multipart/form-data">
                <input type="hidden" name="_token" value="{{ csrf_token() }}">
                <input type="hidden" name="folder" value="{{ $folder }}">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal">
                        x
                    </button>
                    <h4 class="modal-title">Upload New Folder</h4>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label for="file" class="control-label col-sm-3">File</label>
                        <div class="col-sm-8">
                            <input type="file" id="file" name="file">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="file_name" class="control-label col-sm-3">Optional Filename</label>
                        <div class="col-sm-4">
                            <input type="text" id="file_name" name="file_name" class="form-control">
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button class="btn btn-default" data-dismiss="modal" type="button">
                        Cancel
                    </button>
                    <button class="btn btn-primary" type="submit">
                        Upload File
                    </button>
                </div>
            </form>
        </div>
    </div>
</div>

{{--浏览图片--}}
<div class="modal fade" id="modal-image-view">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                    x
                </button>
                <h4 class="modal-title">Image Preview</h4>
            </div>
            <div class="modal-body">
                <img src="" id="preview-image" class="img-responsive">
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" type="button" data-dismiss="modal">Cancel</button>
            </div>
        </div>
    </div>
</div>

紧接着在 admin/upload/index.blade.php 尾部写js:

@section(\'scripts\')
    <script>
        // 删除文件
        function delete_file(name){
            $("#delete-file-name1").html(name);
            $("#modal-file-delete").modal("show");
        }

        // 删除文件夹
        function delete_folder(name){
            $("#delete-folder-name1").html(name);
            $("#delete-folder-name2").val(name);
            $("#modal-folder-delete").modal("show");
        }

        // 上传图片
        function preview_image(path) {
            $("#preview-image").attr("src", path);
            $("#modal-image-view").modal("show");
        }
        $(function(){
            $("#uploads-table").DataTable();
        })
    </script>
@endsection

至此,咱就可以浏览到效果了。

 


4 实现功能

4.1 准备

首先添加路由:

Route::group([\'namespace\' => \'Admin\', \'middleware\' => \'auth\', \'prefix\' => \'admin\'], function(){
    Route::resource(\'post\', \'PostController\');
    Route::resource(\'tag\', \'TagController\', [\'except\' => \'show\']);

    Route::get(\'upload\', \'UploadController@index\');
    // 上传文件
    Route::post(\'upload/file\', \'UploadController@uploadFile\');
    // 删除文件
    Route::delete(\'upload/file\', \'UploadController@deleteFile\');
    // 创建文件夹
    Route::post(\'upload/folder\', \'UploadController@createFolder\');
    // 删除文件夹
    Route::delete(\'upload/folder\', \'UploadController@deleteFolder\');
});

创建需要的Request来进行表单认证:

class UploadFileRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            // 文件必须存在
            \'file\' => \'required\',
            // 路径必须存在 以保证上传文件的路径
            \'folder\' => \'required\'
        ];
    }
}
<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class UploadNewFolderRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            // 要创建目录所需要的目录必须存在
            \'folder\' => \'required\',
            // 新创建的目录
            \'new_folder\' => \'required\'
        ];
    }
}

4.2 创建新的目录

UploadControllercreateFolder方法:

    public function createFolder(Requests\UploadNewFolderRequest $request)
    {
        // 取到我们要创建的目录
        $new_folder = $request->get(\'new_folder\');
        // 拼接上当前的路径
        $folder = $request->get(\'folder\') . \'/\' . $new_folder;
        // 创建新的目录
        $result = $this->manager->createDirectory($folder);

        if ($result === true){
            return redirect()->back()->withSuccess("Folder $new_folder created.");
        }
        $error = $result ?  : "An error occurred creating directory";
        return redirect()->back()->withErrors([$error]);
    }

uploadManager下完善刚刚用到的方法createDirectory

    /**
     * 创建一个目录 成功时返回true错误时返回错误信息或false。
     *
     * @param $folder
     * @return string|bool
     */
    public function createDirectory($folder)
    {
        $folder = $this->cleanFolder($folder);
        // 判断是否存在
        if ($this->disk->exists($folder)){
            return "Folder $folder already exists";
        }
        return $this->disk->makeDirectory($folder);
    }

4.3 删除目录

    public function deleteFolder(Request $request)
    {
        // 获取要删除的目录
        $del_folder = $request->get(\'del_folder\');
        // 拼接完整路径
        $folder = $request->get(\'folder\') . \'/\' . $del_folder;
        $result = $this->manager->deleteDirectory($folder);

        if ($result === true){
            return redirect()->back()->withSuccess("Folder $del_folder deleted.");
        }
        return redirect()->back()->withErrors($result);
    }

uploadManager下完善刚刚用到的方法deleteDirectory

    /**
     * 删除一个目录 成功时返回true错误时返回错误信息或false。
     *
     * @param $folder
     * @return string|bool
     */
    public function deleteDirectory($folder)
    {
        $folder = $this->cleanFolder($folder);
        // 获取目录下的所有子目录和文件
        $filesFolders = array_merge(
            $this->disk->directories($folder),
            $this->disk->files($folder)
        );
        // 判断数组是否是空的
        if (!empty($filesFolders)){
            // 如果不是空的 则不能删除
            return "Directory must be empty to delete it.";
        }
        return $this->disk->deleteDirectory($folder);
    }

4.4 删除文件

    public function deleteFile(Request $request)
    {
        $del_file = $request->get(\'del_file\');
        $path = $request->get(\'folder\').\'/\'.$del_file;

        $result = $this->manager->deleteFile($path);

        if ($result === true) {
            return redirect()
                ->back()
                ->withSuccess("File \'$del_file\' deleted.");
        }

        $error = $result ? : "An error occurred deleting file.";
        return redirect()
            ->back()
            ->withErrors([$error]);
    }

uploadManager下完善刚刚用到的方法deleteDirectory

    /**
     * 删除一个文件 成功时返回true错误时返回错误信息或false。
     * 
     * @param $path
     * @return string|bool
     */
    public function deleteFile($path)
    {
        $path = $this->cleanFolder($path);

        // 判断文件是否存在
        if (! $this->disk->exists($path)) {
            return "File does not exist.";
        }

        return $this->disk->delete($path);
    }

4.5 上传文件

    public function uploadFile(Requests\UploadFileRequest $request)
    {
        // 获取到文件信息
        $file = $_FILES[\'file\'];
        // 获取文件名
        $fileName = $request->get(\'file_name\');
        $fileName = $fileName ?: $file[\'name\'];
        // 拼接路径
        $path = str_finish($request->get(\'folder\'), \'/\') . $fileName;
        // 获取内容
        $content = File::get($file[\'tmp_name\']);

        $result = $this->manager->saveFile($path, $content);

        if ($result === true) {
            return redirect()
                ->back()
                ->withSuccess("File \'$fileName\' uploaded.");
        }

        $error = $result ? : "An error occurred uploading file.";
        return redirect()
            ->back()
            ->withErrors([$error]);
    }

uploadManager下完善刚刚用到的方法deleteDirectory

    /**
     * 保存文件 成功时返回true错误时返回错误信息或false。
     *
     * @param $path
     * @param $content
     * @return string
     */
    public function saveFile($path, $content)
    {
        $path = $this->cleanFolder($path);

        if ($this->disk->exists($path)) {
            return "File already exists.";
        }

        return $this->disk->put($path, $content);
    }

注意:如果重命名文件名的话要自行加上后缀哦

分类:

技术点:

相关文章: