在php laravel项目中简单实现大文件分片上传 绕过上传大小限制

目的

nginx和php都有大文件上传限制,当我们的项目需要上传超过2M的大文件时,就会被拦截。当然可以修改这个配置,以扩大限制,但只是治标不治本,换一个环境还要重新配置。

今天在自己的laravel项目中实现一下大文件分片上传,基本原理就是把大文件切成若干片,每片都是一个小文件,再上传到服务器。由于我做的小项目并没有太高要求,本文只是在已有项目的基础上,新建几个文件的简单实现,并没有积极考虑效率问题。如果对项目效率要求比较高,推荐直接使用开源的laravel拓展包:AetherUpload-Laravel

请保证你已经有一个可以正常运行的laravel项目

问题情景

假设你已经写了一个控制器方法upload_my_file(Request $request);,当你上传一个100KB的文件时,你成功执行完了所有请求。但是,当你上传一个10MB的文件时,你发现被系统拦截了,原因是文件太大!那么你就可以通过下面的方案解决!

换句话说,如果你已经写好了可以上传小文件的完整功能,那么请继续看下面的解决方案!

大文件解决方案(4步)

  • A. 在laravel项目中新建两个文件

1. 项目根下执行php artisan make:controller UploadController产生控制器,编辑它(app/Http/Controllers/UploadController)

    该控制器不需要路由,在你正常情况下提交form表单的那个控制器中调用下面的upload方法即可。

file('block');
        $block_id=intval( $request->input('block_id') );  //0~tot-1
        $block_tot=intval( $request->input('block_tot') );

        $block->move(storage_path('app/'.$temp_save_dir),$block_id); //以块号为名保存当前块
        if($block_id == $block_tot-1){  //整个文件上传完成
            for($i=0;$i<$block_tot;$i++){
                if(!Storage::exists($save_dir)){  //保存文件夹
                    Storage::makeDirectory($save_dir);
                }
                $content=Storage::get($temp_save_dir.'/'.$i);
                file_put_contents(storage_path('app/'.$save_dir.'/'.$save_name),$content,$i?FILE_APPEND:FILE_TEXT);//追加:覆盖
            }
            Storage::deleteDirectory($temp_save_dir); //删除临时文件
            return true;  //标记上传完成
        }
        return false;
    }
}

2. 新建文件:项目根目录/public/js/uploadBig.js

function uploadBig(obj) {
    var args={
        url:obj.url,        //必须,相当于form的action
        _token:obj._token,  //必须,laravel token:'{{csrf_token()}}'
        files:obj.files,    //必须,上传的文件列表
        data:obj.data,     //可选,除files外的其他数据
        blockSize:1024*(obj.blockSize!==undefined?obj.blockSize:900),  //可选,每块的大小,默认900KB
        before:   obj.before,    //可选,上传前执行函数
        uploading:obj.uploading, //可选,上传中执行函数
        success:  obj.success,   //可选,上传成功执行函数
        error:    obj.error,     //可选,上传出错执行函数
    };

    function dfs_ajax(index=0,start=0) {
        var formData = new FormData();
        formData.append('filename',args.files[index].name);     //文件原始名
        formData.append('block_id',Math.round(start/args.blockSize));  //块号
        formData.append('block_tot',Math.ceil(args.files[index].size/args.blockSize));//块数
        formData.append('block',args.files[index].slice(start,start+args.blockSize)); //文件块
        if(args.data!==undefined && start+args.blockSize>=args.files[index].size)//要上传最后一块了
        {
            for(let key of Object.keys(args.data))
                formData.append(key,args.data[key]); //除文件外的附加数据
        }
        $.ajax({
            headers: {'X-CSRF-TOKEN': args._token},
            url: args.url ,
            type: 'post',
            data: formData,
            processData: false,
            contentType: false,
            success:function(ret){
                if(index===args.files.length-1 && start+args.blockSize>=args.files[index].size)//最后一个上传完毕
                {
                    //上传成功...回调函数[文件总数,控制器返回值]
                    if(args.success!==undefined)
                        args.success(args.files.length,ret);
                }
                else
                {
                    //上传中...回调函数,参数[文件总数,当前第几个,当前文件已上传大小KB]
                    if(args.uploading!==undefined)
                        args.uploading(args.files.length,index+1,(start+args.blockSize)/1024,args.files[index].size/1024);

                    if(start+args.blockSize>=args.files[index].size)//跳到下一个文件
                        dfs_ajax(index+1,0);//递归
                    else
                        dfs_ajax(index,start+args.blockSize);//递归
                }

            },
            error:function(xhr,status,err){
                if(args.error!==undefined)
                    args.error(xhr,status,err);
            }
        });
    }

    //上传开始前回调函数,参数[文件数量,合计大小KB]
    if(args.before!==undefined){
        var total_size=0;
        for(var file_temp of args.files)total_size+=file_temp.size;
        args.before(args.files.length,total_size);
    }

    dfs_ajax(); //递归按顺序开始执行ajax
}
  • B. 稍加修改你的html和控制器函数

3. 仿照如下修改你的前端代码。do_upload中3个参数url,_token,files是必需的,其余的可选。


@csrf

4. 修改你的控制器函数,函数的开头加入下面三行代码

$uc=new UploadController;
$isUploaded=$uc->upload($request,'保存路径','文件名');
if(!$isUploaded)return 0;

注:'保存路径'填相对于storage/app/的文件夹名字,'文件名'不要含有中文或/

如果提示函数upload不存在,就在控制器文件顶端加一行:use App\Http\Controllers\UploadController;

总结

基本原理就是将大文件切成一个个的小文件,所有的小文件上传完成再继续执行你的剩余代码。把这个过程封装到另一个控制器中,而在需要上传大文件时,只需调用上面这三行代码即可。

上面的代码完全可以根据自身需要修改。

你可能感兴趣的:(在php laravel项目中简单实现大文件分片上传 绕过上传大小限制)