大文件上传实例
问题
服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关
upload_max_filesize = 2M //PHP最大能接受的文件大小 post_max_size = 8M //PHP能收到的最大POST值' memory_limit = 128M //内存上限 max_execution_time = 30 //最大执行时间
当然不能简单粗暴的把上面几个值调大,否则服务器内存资源吃光是迟早的问题。
解决思路
好在HTML5开放了新的FILE API,也可以直接操作二进制对象,我们可以直接在浏览器端实现文件切割
JS思路
1.获取文件的FILE对象
2.把文件的FILE对象进行切割,并且附加到FormData对象中
3.把FormData对象通过AJAX发送到服务器
5.重复3、4步骤,直到文件发送完。
PHP思路
1.把上传文件块按照 分割的Index 命名 方便后期合并
3.所有的文件块上传完成后,进行文件合并
4.删除文件不需要的文件块
5.返回上传后的文件信息(文件访问URL、文件名称等)
DEMO 代码
index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>大文件上传实例</title> <script type="text/javascript">
const BYTES_PER_CHUNK = 1024 * 1024; // 每个文件切片大小定为1MB . var slices; var totalSlices; //发送请求 function sendRequest() { var blob = document.getElementById('file').files[0]; var start = 0; var end; var index = 0; // 计算文件切片总数 slices = Math.ceil(blob.size / BYTES_PER_CHUNK); totalSlices = slices; //分块上传 while(start < blob.size) { end = start + BYTES_PER_CHUNK; if(end > blob.size) { end = blob.size; } uploadFile(blob, index, start, end); start = end; index++; } } //上传文件 function uploadFile(blob, index, start, end) { var xhr; var fd; var chunk; xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.responseText) { console.log(xhr.responseText); // alert(xhr.responseText); } slices--; // 如果所有文件切片都成功发送,发送文件合并请求。 if(slices == 0) { mergeFile(blob); console.log('文件上传完毕'); // alert('文件上传完毕'); } } }; chunk =blob.slice(start,end);//切割文件 //构造form数据 fd = new FormData(); fd.append("file", chunk); fd.append("fileName", blob.name); fd.append("chunkNum", index); xhr.open("POST", "upload.php?act=upload", true); //设置二进制文边界件头 xhr.setRequestHeader("X_Requested_With", location.href.split("/")[3].replace(/[^a-z]+/g, '$')); xhr.send(fd); } //合并文件块 function mergeFile(blob) { var xhr; var fd; xhr = new XMLHttpRequest(); fd = new FormData(); fd.append("fileName", blob.name); fd.append("totalChunkNum", totalSlices); xhr.open("POST", "upload.php?act=merge", true); xhr.setRequestHeader("X_Requested_With", location.href.split("/")[3].replace(/[^a-z]+/g, '$')); xhr.send(fd); }
</script> </head> <body> <input type="file" id="file"/> <button onclick="sendRequest()">上传</button> </body> </html>
upload.php
<?php class Upload{ const ERROR_CODE = 500; //错误码 const SUCCESS_CODE = 200; //成功码 private $filepath = './uploads/'; //上传目录 private $chunkNum; //第几个文件块 private $totalChunkNum; //文件块总数 private $fileName; //文件名 public function __construct() { } public function run(){ $act = trim( isset($_REQUEST['act'])?$_REQUEST['act']:'' ); if ($act == 'upload') { $rdata = $this->uploadChunk($_POST['chunkNum'],$_POST['fileName']); }else if($act == 'merge'){ $rdata = $this->mergeFile($_POST['totalChunkNum'],$_POST['fileName']); }else{ $this->sendResponse(self::ERROR_CODE,'你(bie)好(xia)可(qing)爱(qiu)'); } } /** * 上传块文件 * @param $chunkNum * @param $fileName */ private function uploadChunk($chunkNum,$fileName){ $target = $this->filepath . iconv("utf-8","gbk",$fileName) . '-' . $chunkNum; $move_result = move_uploaded_file($_FILES['file']['tmp_name'], $target); if(!$move_result){ $this->sendResponse(self::ERROR_CODE, '上传块文件失败'); } // Might execute too quickly. //sleep(1); $this->sendResponse(self::SUCCESS_CODE,'上传块文件成功'); } /** * 合并块文件 * @param $totalChunkNum * @param $fileName */ private function mergeFile($totalChunkNum,$fileName){ //文件合并 $target = $this->filepath . iconv("utf-8","gbk",$fileName); $dst = fopen($target, 'wb'); for($i = 0; $i < $totalChunkNum; $i++) { $slice = $target . '-' . $i; $src = fopen($slice, 'rb'); stream_copy_to_stream($src, $dst); fclose($src); unlink($slice); } fclose($dst); $this->sendResponse(self::SUCCESS_CODE,'合并块文件成功',[ 'url' => 'www.***.com/uploads/abc.zip' ]); } private function sendResponse($code,$msg,$data = []){ header('Content-Type: application/json; charset=utf-8', true); echo json_encode([ 'code' => $code, 'msg' => $msg, 'data' => $data, ]);exit; } } $obj = new Upload(); $obj->run();