用php如何解决大文件分片上传问题


Posted in PHP onJuly 07, 2021

如果上传的文件只有小于10M的话, 就没必要考虑这样的做法, 直接在 php.ini中更改一下 upload_max_filesize = 10m post_max_size = 10m 这样就可以了, 下面我们来说一说php上传超大的文件

前提

首先, 上传超大的文件, 前端要和后端相互配合文件上传要使用 ajax 的方法, 而不是 form 的 submit的方式

思想

前端把file文件对象按一定的大小 分割成一定大小的文件(如按 2M 或 5M来分割), 对分割后的文件, 一个个的上传到后端去, 后端接收到分片文件后,把它们先放到一个临时的目录下, 在收到前端完成的数据请求的时候, 把临时目录中的文件组装起来成一个新的文件, 保存后, 把临时目录下的文件删除掉就可以了

代码

html

<div class="a">
          上传<input id="myfile" type="file" name="myfile"/>
   </div>

这里要说明一下,没有使用 submit 上传, 使用 ajax上传

javascript

<script>
	$(function(){
		let myfile = document.getElementById("myfile");
		myfile.onchange = function(){
			let file = myfile.files[0];		//这里可以得到上传的文件对象
			let length = 1024 * 1024 * 5;   //这里是每一个分片的大小
			let total_number = Math.ceil(file.size/length) //使用进一法, 来确定分片的个数
			let start = 0;			//分片的初始位置
			let end = length;		//分片的结束位置
			let parr = [];         //这里为promise.all方法准备一个数组;
			for(let i = 1;i<=total_number;i++){
				//这里开始分片, 并且把每一个分片上传到服务器
				let bolb = file.slice(start,end);  //得到一个分片
				start = end;				//调整下一个分片的起始位置
				end = start+length;			//调整下一个分片的结束位置
				if(end > file.size){
					end=file.size;		//这里对最后的一个分片结束位置进行调整
				}
				let formdata = new FormData();  //创建一个FormData对象, 准备传送数据
				formdata.append("file",blob);   //据分片数据放入 formdata
				formdata.append("tempfilename",i+"_"+file.name)  //同时为这个分片设置一个名称, 其中的 i 可以帮助后端进行排序处理
				
				//formdata组装好之后, 调用 pro() 函数, 返回一个promise对象, 并把它放入 parr 数组中, 方便后面的 promise.all方法使用
				parr.push(pro(formadata));
			}
			//以上for 循环结束之后,  parr数组中就全部是  分片上传的 promise的对象了, 此时我们使用promise.all 方法, 等待所有上传都成功执行后, 再向服务器发送一个请求, 也就是上传完成, 让服务器组装分片的请求
			Promise.all(parr).then(res=>{
				if(res.length == parr.length){   //如果返回成功的数组长度 和 parr的数组长度相等,说明分片全部上传成功
					//此时对上传接口再次发送请求, 同时把 上传的文件名带上, 方便后台查找要组装的分片文件名, 因为是请求同一个上传接口所以, 我们还要传一个 flag=1  表示这是一个数据组装的请求
 $.ajax({
                    type:"post",
                    url:"http://fastadmin.test/index/upload/getupload",
                    data:{flag:1,filename:file.name},  //这里 flag=1表示上传完成,请求组装, filename:表示要组成哪一组文件分片
                    success:function(res){
                        if(res.length == parr.length){
                            console.log(111);
                        }
                    },
                    fail: function () {
                        reject()
                    }
                })
				}
			})
		}
	})
	//这个函数用来上传分片文件, 返回的是一个 promise 对象, 方便后面使用  promise.all还判断所有分片是否是上传成功的
	//这里要说明一下, $.post() 是不可以上传文件的, 只能用$.ajax() 并且要把 contentType:false和processData:false 带上
    function pro(formData){
        return new Promise((resolve,reject)=>{
            $.ajax({
                type:"post",
                url:"http://fastadmin.test/index/upload/getupload",  //后台上传文件的地址
                data:formData,
                contentType: false,    //这个不能少, ajax上传文件是不能少的
                processData: false,	   //这个不能少, ajax上传文件必传 false
                success:function(res){
                    resolve(res)
                },
                fail: function () {
                    reject()
                }
            })
        })
    }		
</script>

以上就是 前端的 js 核心部分, 注释基本就可以看懂了

php

使用的tp5的框架

public function getUpload(){
	$tempdir = APP_PATH."../public/tempdir"; //这里分片的文件指定了一个临时目录, 后面会用到
	$flag = input("flag",0);//接收参数flag 如果没有这个参数就默认为0, 如果flag=1,表示要组装分片
	if($flag == 0){
		//这里是上传分片
		$file = request()->file("file");  //接收到这个分片
		$tempfilename = input("tempfilename");  //接收到这个分片的名称, (注意,这个名称中含有排序信息)
		if(!file_exists($tempdir)){
			mkdir($tempdir,0755,true);		//如果临时目录不存在,则创建一个临时目录
		}
		$fileinfo = $file->move($tempdir,$tmpfilename);
		if($fileinfo){  // 这里把分片的文件保存在了临时目录中, 返回的结果有点简单, 可以根据自已的需求返回相应的数据
			return josn(['error'=>0])
		}else{
			return json(['error'=>1])
		}
	}else if($flag == 1){
		//如果flag 为 1 表示, 分片已上传完成了
		$filename = input("filename");
		//通过文件名的字符串匹配, 找上所有的分片, 返回一个文件路径的数组
		$fileArr = glob($tempdir."/*".$filename);
		// 这里的 * 是一个通配符, 它可以了所以的文件名中 包含的 $filename 的文都找到
		//说明一下, $fileArr中的数组的顺序不是我们想要的 , 所以我们新建一个数组来 整理一下顺序
		$newfileArr = [];
		foreach($fileArr as $f){
		//在js前端我们把文件的名称 前加了 序号+"_", 所以我们可以取到文件名之后, 通过 下划线来分开并把序中写在 key 中
			$filebasename = basename($f); //$f是一个个的 路径, 这里使用 basename 得到文件名
			$filebasenamesplit = explode("_",$filebasename); //通过 下划线分割文件名, 
			$newfileArr[$filebasenamesplit[0]] = $f;    //构造了一个新的数组, 其中 数组的key 就是 顺序号, 数组的值就是 分片文件的路径
		}
		//分片的序号和路径都准备好了, 就可以组装了
		$num = count($newfileArr);  //得到的所有分片的个数, 为后面使用for 循环做准务
		//开始使用for 循环来组装
		$newfilename = "huangjunhui".$filename; //这里为组装后的文件起一个名字, 可随意
		for($i = 1;$i<=$num;$i++){
			file_put_contents($newfilename,file_get_contents($newfileArr[$i]),FILE_APPEND);
			//这里以追加的方式, 把分片文件都写入到了一个文件中, 
		}
		......
		//删除临时文件中的分片文件, 这里可以使用 try catch来判断是否有错误
		foreach($newfileArr as $fi){
			unlink($fi);
		}
		//最后给前端返回 保存的文件名就可以
	}
}

上面的方法,我本地测试上传了一个 650M的文件,只用的 20秒的时间, 没有在服务器上测试过,大家可以按照这个方法试一下。

PHP 相关文章推荐
笑谈配置,使用Smarty技术
Jan 04 PHP
php中filter函数验证、过滤用户输入的数据
Jan 13 PHP
yii实现创建验证码实例解析
Jul 31 PHP
PHP遍历文件夹与文件类及处理类用法实例
Sep 23 PHP
使用PHP接受文件并获得其后缀名的方法
Aug 05 PHP
jQuery向下滚动即时加载内容实现的瀑布流效果
Jan 07 PHP
PHP模板引擎Smarty内建函数section,sectionelse用法详解
Apr 11 PHP
yii2带搜索功能的下拉框实例详解
May 12 PHP
php使用get_class_methods()函数获取分类的方法
Jul 20 PHP
PHP Ajax JavaScript Json获取天气信息实现代码
Aug 17 PHP
PHP Socket网络操作类定义与用法示例
Aug 30 PHP
ThinkPHP5.1的权限控制怎么写?分享一个AUTH权限控制
Mar 09 PHP
php 文件上传至OSS及删除远程阿里云OSS文件
Jul 04 #PHP
PHP实现两种排课方式
Linux系统下安装PHP7.3版本
详细分析PHP7与PHP5区别
Jun 26 #PHP
laravel添加角色和模糊搜索功能的实现代码
一文搞懂php的垃圾回收机制
PHP中strval()函数实例用法
Jun 07 #PHP
You might like
老生常谈文本文件和二进制文件的区别
2017/02/27 PHP
Laravel学习教程之model validation的使用示例
2017/10/23 PHP
PHP实现普通hash分布式算法简单示例
2018/08/06 PHP
可实现多表单提交的javascript函数
2007/08/01 Javascript
JS 文件传参及处理技巧分析
2010/05/13 Javascript
动态调用CSS文件的JS代码
2010/07/29 Javascript
js下获得客户端操作系统的函数代码(1:vista,2:windows7,3:2000,4:xp,5:2003,6:2008)
2011/10/31 Javascript
JavaScript实现统计文本框Textarea字数增强用户体验
2012/12/21 Javascript
javascript轻松实现当鼠标移开时已弹出子菜单自动消失
2013/12/29 Javascript
jquery实现弹出层效果实例
2015/05/19 Javascript
AngularJS仿苹果滑屏删除控件
2016/01/18 Javascript
一道常被人轻视的web前端常见面试题(JS)
2016/02/15 Javascript
辨析JavaScript中的Undefined类型与null类型
2016/05/26 Javascript
JS原型与原型链的深入理解
2017/02/15 Javascript
js字符串与Unicode编码互相转换
2017/05/17 Javascript
bootstrap table实现双击可编辑、添加、删除行功能
2017/09/27 Javascript
深入理解Angular4订阅(Subscribe)与取消
2017/11/22 Javascript
微信小程序自定义组件之可清除的input组件
2018/07/17 Javascript
layui导出所有数据的例子
2019/09/10 Javascript
微信小程序实现点击按钮后修改颜色
2019/12/05 Javascript
微信小程序开发(三):返回上一级页面并刷新操作示例【页面栈】
2020/06/01 Javascript
[01:11:35]Liquid vs LGD 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
python计算日期之间的放假日期
2018/06/05 Python
Python 使用 attrs 和 cattrs 实现面向对象编程的实践
2019/06/12 Python
python交易记录链的实现过程详解
2019/07/03 Python
CSS3 实现弹幕的示例代码
2017/08/07 HTML / CSS
HTML5 创建canvas元素示例代码
2014/06/04 HTML / CSS
Superdry瑞典官网:英国日本街头风品牌
2017/05/17 全球购物
Waterford英国官方网站:世界上最受欢迎的优质水晶品牌
2019/08/17 全球购物
StubHub中国:购买和出售全球活动门票
2020/01/01 全球购物
自我鉴定三原则
2014/01/13 职场文书
竞聘报告优秀范文
2014/11/06 职场文书
材料员岗位职责
2015/02/10 职场文书
2015年工商局个人工作总结
2015/07/23 职场文书
党员干部学法用法心得体会
2016/01/21 职场文书
python区块链实现简版工作量证明
2022/05/25 Python