nginx和php都有大文件上传限制,当我们的项目需要上传超过2M的大文件时,就会被拦截。当然可以修改这个配置,以扩大限制,但只是治标不治本,换一个环境还要重新配置。
今天在自己的laravel项目中实现一下大文件分片上传,基本原理就是把大文件切成若干片,每片都是一个小文件,再上传到服务器。由于我做的小项目并没有太高要求,本文只是在已有项目的基础上,新建几个文件的简单实现,并没有积极考虑效率问题。如果对项目效率要求比较高,推荐直接使用开源的laravel拓展包:AetherUpload-Laravel
请保证你已经有一个可以正常运行的laravel项目
假设你已经写了一个控制器方法upload_my_file(Request $request);,当你上传一个100KB的文件时,你成功执行完了所有请求。但是,当你上传一个10MB的文件时,你发现被系统拦截了,原因是文件太大!那么你就可以通过下面的方案解决!
换句话说,如果你已经写好了可以上传小文件的完整功能,那么请继续看下面的解决方案!
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
}
3. 仿照如下修改你的前端代码。do_upload中3个参数url,_token,files是必需的,其余的可选。
4. 修改你的控制器函数,函数的开头加入下面三行代码
$uc=new UploadController;
$isUploaded=$uc->upload($request,'保存路径','文件名');
if(!$isUploaded)return 0;
注:'保存路径'填相对于storage/app/的文件夹名字,'文件名'不要含有中文或/
如果提示函数upload不存在,就在控制器文件顶端加一行:use App\Http\Controllers\UploadController;
基本原理就是将大文件切成一个个的小文件,所有的小文件上传完成再继续执行你的剩余代码。把这个过程封装到另一个控制器中,而在需要上传大文件时,只需调用上面这三行代码即可。
上面的代码完全可以根据自身需要修改。