Large File Upload based on Laravel | 基于Laravel的大文件HTML5分块上传

Author Avatar
DataLeoZ 8月 29, 2019
  • 在其它设备中阅读本文章

This solution use AetherUpload-Laravel as core module

When we try to upload a large file to server, the processing time will be much longer, no need to mention problems if the network terminated or cancel by users during uploading. So use HTML slice upload will be a better choice. This blog I will try to build a large file upload solution based on Php Laravel and AetherUpload-Laravel.
大文件上传时,往往需要的处理时间会很长,同时可能会出现用户中途取消,网络断开等问题。使用HTML5的切片上传技术会有效改善大文件上传时的用户体验,基于Php Laravel后端框架和AetherUpload-Laravel这个组件,本文尝试去建立一个覆盖前后端的大文件上传方案。

我们可以利用HTML5的分块上传技术来轻松解决这个困扰,通过将大文件分割成小块逐个上传再拼合,来降低服务器内存的占用,突破服务器及后端语言配置中的上传大小限制,可上传任意大小的文件,同时也简化了操作,提供了直观的进度显示。
– AetherUpload-Laravel Github Introduction

Env

  • Php Laravel 5.8
  • AetherUpload-Laravel 2.0
  • UI Framwork: Bootstrap, jasny-bootstrap, Creative-Tim Argon

Solution

1. AetherUpload-Laravel Installation

The detail can refer AetherUpload-Laravel github README.
Here I just mention a error when I install it. After I install it follow the instruction, I try to check the function via http://YourHost/aetherupload. But, the uploading show error said can’t process upload. The problem I found is that I still have to build the upload path manually, then rebuild the groups even its the first default group.

chmod -R 755 storage/app/aetherupload
XXX$ php artisan aetherupload:groups

2. Front-end

The css setting will only be reflected if include UI Framworks Bootstrap, jasny-bootstrap, Creative-Tim Argon. JQuery is also needed in the module.

<form ...Your Route config set here...>
<Input ...Other Input...>
<Input ...Other Input...>
<Input ...Other Input...>

<!-- If this part is in edit page I will show the uploaded video, else will show the upload component
The video object I store in database will be like: Video(id, path, screenshot, introduciton, content...) -->

@if($path === '')
<div class="col-xl-6 form-group{{ $errors->has('path') ? ' has-danger' : '' }}" id="aetherupload-wrapper">
    <!--组件最外部需要一个名为aetherupload-wrapper的id,用以包装组件-->
    <label class="form-control-label">上传视频</label>

    <div class="controls">
        <div class="fileinput fileinput-new input-group" data-provides="fileinput"
            style="box-shadow: 0 1px 3px rgba(50, 50, 93, .15), 0 1px 0 rgba(0, 0, 0, .02);">
            <div class="form-control form-control-alternative" data-trigger="fileinput">
                <span class="fileinput-filename"></span>
            </div>
            <span class="input-group-append">

                <span class="input-group-text btn-file" style="border: 0px;">
                    <span class="fileinput-new">选择视频</span>
                    <span class="fileinput-exists">更换视频</span>
                    <input type="file" id="aetherupload-resource"
                        onchange="console.log('更换视频');aetherupload(this).setGroup('file').setSavedPathField('#aetherupload-savedpath').setPreprocessRoute('/aetherupload/preprocess').setUploadingRoute('/aetherupload/uploading').setLaxMode(false).success(someCallback).upload()"/>
                </span>
            </span>
        </div>
        <!--需要一个名为aetherupload-resource的id,用以标识上传的文件,setGroup(...)设置分组名,setSavedPathField(...)设置资源存储路径的保存节点,setPreprocessRoute(...)设置预处理路由,setUploadingRoute(...)设置上传分块路由,setLaxMode(...)设置宽松模式,success(...)可用于声名上传成功后的回调方法名。默认为选择文件后触发上传,也可根据需求手动更改为特定事件触发,如点击提交表单时-->
        <div class="progress " style="height: 6px;margin-bottom: 2px;margin-top: 10px;width: 200px;">
            <div class="progress-bar bg-primary" id="aetherupload-progressbar"
                style="    background-color:#419DF9!important;"></div>
            <!--需要一个名为aetherupload-progressbar的id,用以标识进度条-->
        </div>

        <span style="font-size:12px;color:#aaa;" id="aetherupload-output"></span>
        <!--需要一个名为aetherupload-output的id,用以标识提示信息-->
        <input type="hidden" name="path" id="aetherupload-savedpath" value="{{ $path }}">
        <!--需要一个自定义名称的id,以及一个自定义名称的name值, 用以标识资源储存路径自动填充位置,默认id为aetherupload-savedpath,可根据setSavedPathField(...)设置为其它任意值-->
    </div>
    <div id="result"></div>
</div>
@else
    <div class="col-xl-6 form-group">
        <video width="500" height="277" controls>
        <source src="http://127.0.0.1:8000/admin/play/file_201908_4fa4366cd697d28f654254ba599eaeb9.mov" type="video/mp4">
      </video>
    </div>
@endif


<input ...Other Input...>
<input ...Other Input...>
<input ...Other Input...>

</form>

<!-- JS Part Code -->
<script src="{{ URL::asset('vendor/aetherupload/js/spark-md5.min.js') }}"></script>
<!--(可选)需要引入spark-md5.min.js,用以支持秒传功能-->
<script src="{{ URL::asset('vendor/aetherupload/js/aetherupload.js') }}"></script>
<!--需要引入aetherupload.js-->
<script>
    // success(someCallback)中声名的回调方法需在此定义,参数someCallback可为任意名称,此方法将会在上传完成后被调用
    // 可使用this对象获得resourceName,resourceSize,resourceTempBaseName,resourceExt,groupSubdir,group,savedPath等属性的值
    someCallback = function () {
        // Example Code
        $('#result').append(
            '<p>执行回调 - 文件已上传,原名:<span >' + this.resourceName + '</span> | 大小:<span >' + parseFloat(this
                .resourceSize / (1000 * 1000)).toFixed(2) + 'MB' + '</span> | 储存名:<span >' + this.savedPath
            .substr(this.savedPath.lastIndexOf('_') + 1) + '</span></p>'
        );
        // Allow reupload video 允许再次上传新视频覆盖原有的
        $('#aetherupload-resource').attr('disabled', false);
        // Update Video info 像后台更新本次上传的视频信息
        var video_path = $('#aetherupload-savedpath').val();
        var id = $('#video_id').val();
        var path = "/admin/videos/" + id + '/path';

        $.ajax({
            type: "put",
            url: path,
            data: {'path' : video_path},
            success: function (data) {
                // location.reload();
                // toastr.success('Success');
            }
        });
    }

</script>

3. Back-end

The update new video function

    public function updatePath(Request $request, $id){
        $video = Video::find($id);

        <!-- Delete the old video first -->
        if($video->path !== ''){
            \AetherUpload\Util::deleteResource($video->path);
        }

        $video->path = $request->path;
        if($video->save()){
            return redirect()->back()->withInput()->with('message', '更新视频成功');
        }else{
            return redirect()->back()->withInput()->withErrors('更新视频失败');
        };

    }

Other methods may use in other parts:

  • 获得已上传资源的访问链接
    1. (手动)通过请求路由”域名(分布式启用时应当为储存服务器的域名)/aetherupload/display/“+file1 访问file1
    2. (自动)通过全局帮助方法 aetherupload_display_link(file1) 访问file1
    3. (自动)通过工具类方法 \AetherUpload\Util::getDisplayLink(file1) 访问file1
  • 获得已上传资源的下载链接
    1. (手动)通过请求路由”域名(分布式启用时应当为储存服务器的域名)/aetherupload/download/“+file1+”/newname” 下载file1
    2. (自动)通过全局帮助方法 aetherupload_download_link(file1,newname) 下载file1
    3. (自动)通过工具类方法 \AetherUpload\Util::getDownloadLink(file1,newname) 下载file1
  • 删除资源
\AetherUpload\Util::deleteResource($savedPath); //删除对应的资源文件 \AetherUpload\Util::deleteRedisSavedPath($savedPath); //删除对应的redis秒传记录。

This blog is under a CC BY-NC-SA 3.0 Unported License
本文使用 CC BY-NC-SA 3.0 中国 协议许可
署名-非商业性使用-相同方式共享(BY-NC-SA):使用者可以对本创作进行共享、演绎,但使用时须进行署名(同时标明是否(对原始作品)作了修改),且不得运用于商业目的,采用本创作的内容必须同样采用本协议进行授权。详情请参考协议细则。

本文链接:https://dataleoz.com/laravel-large-file-upload/