php结合html5 实现大文件上传的方法
|
工作中,经常碰到大文件上传的需求,如上传大的用户包、CDK列表等。常规的解决方案是采用form表单+iframe方式提交给php处理,如下面的代码: html代码: php代码: if (is_uploaded_file($_FILES["sCDKFile"]["tmp_name"])) { switch ($_FILES["sCDKFile"]["error"]) { case UPLOAD_ERR_OK: break; case UPLOAD_ERR_NO_FILE: $this->OutputScript('No file sent.'); case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: $this->OutputScript('Exceeded filesize limit.'); default: $this->OutputScript('Unknown errors. Code:' . $_FILES["sCDKFile"]["error"]); } //上传文件的类型 $stype = $_FILES["sCDKFile"]["type"]; //如果文件符合要求并且上传过程中没有错误 if ($stype != "text/plain" && $stype != "application/csv") { $this->OutputScript("请选择上传txt,csv格式的文件,不支持格式{$stype}"); } 上面方案存在一个致命的问题,没发处理大文件的上传。主要受到来自下面几个方面的限制: PHP的脚本时长的限制 长时间上传,网络中断的问题 PHP脚本执行时内存大小等的限制 apache服务器链接数限制 与之相关的php配置项如下: ;;;;;;;;;;;;;;;; ; File Uploads ; ;;;;;;;;;;;;;;;; file_uploads = On upload_max_filesize = 8m //允许上传文件大小的最大值 max_file_uploads = 20 //单请求最多允许上传文件数量 post_max_size = 8M //post数据大小限制 ;;;;;;;;;;;;;;;;;; ; Resource Limits ; ;;;;;;;;;;;;;;;;;;; max_execution_time = 30 ;每个PHP页面运行的最大时间值(秒),默认30秒 max_input_time = 60 ;每个PHP页面接收数据所需的最大时间,默认60秒 memory_limit = 128m ;每个PHP页面所吃掉的最大内存,默认8M 从上面的配置看到,如果调大upload_max_filesize = 128m,设上传网速为 500KB,则30s只够上传15M以内大小的文件,时间长了脚本将中断执行,如果继续调整max_execution_time等设置,将 如果中间网络中断,上传文件还是失败。 并且上述方案还有下面几个问题: 不支持显示上传进度 不支持断点续传、如果网络中断后需要整体重传 我们希望能够找到一个在web上能够解决上面两个问题的方案。 HTML5之前的解决方案一般有三种方法: RIA技术(Flex Silverlight等),该方案需要浏览器支持Flash,最常用的是使用SWFupload实现。 插件技术(ActiveX,applet等),要装下载安装插件、比较麻烦。而且ActiveX插件不具有跨浏览器的特性,只能在IE浏览器上使用。 CS解决方案:开发单独的客户端和服务器程序,使用UDP/TCP长链接通讯上传。缺点:需要单独开发Client,发布更新困难、 html5的解决方案相关对象和API File - 独立文件;提供只读信息,例如名称、文件大小、mimetype 和对文件句柄的引用。 FileList - File 对象的类数组序列(考虑 或者从桌面拖动目录或文件)。 Blob - 可将文件分割为字节范围。 FileReader:文件读写对象,包括四个异步读取文件的选项: FileReader.readAsBinaryString(Blob|File) - result 属性将包含二进制字符串形式的 file/blob 数据。每个字节均由一个 [0..255] 范围内的整数表示。 FileReader.readAsText(Blob|File,opt_encoding) - result 属性将包含文本字符串形式的 file/blob 数据。该字符串在默认情况下采用“UTF-8”编码。使用可选编码参数可指定其他格式。 FileReader.readAsDataURL(Blob|File) - result 属性将包含编码为数据网址的 file/blob 数据。 FileReader.readAsArrayBuffer(Blob|File) - result 属性将包含 ArrayBuffer 对象形式的 file/blob 数据。 HTML5文件处理API能够支持文件拖拽、上传进度显示、支持文件分块读取等特性。有了文件分块读取的特性,就可以实现将文件分块上传,然后在服务器段合并文件。如下面的demo: #progress_bar { margin: 10px 0; padding: 3px; border: 1px solid #000; font-size: 14px; clear: both; opacity: 0; -moz-transition: opacity 1s linear; -o-transition: opacity 1s linear; -webkit-transition: opacity 1s linear; } #progress_bar.loading { opacity: 1.0; } #progress_bar .percent { background-color: #99ccff; height: auto; width: 0; } //todo流程: //0. 读取文件长度 //1. 初始化显示信息 //1. 读取数据 //2. 发送给后台 //3. 接收返回值、更新收到的数据 var uploadData = function (data,size,beg,end,callback) { $.post("testUpload.php", { "size": size, "data": data, "beg": beg, "end": end }).done(function (result) { if (result.indexOf("Success") != -1) { callback(); } else { console.log("Error:n" + data); alert("文件块上传失败,请重新上传文件!"); } }); }; var handleFileSelect = function (evt) { var reader; var progress = document.querySelector('.percent'); progress.style.width = '0%'; progress.textContent = '0%'; var files = document.getElementById('files').files; if (!files.length) { alert('请选择文件'); return; } var file = files[0]; var length = 1024 * 1024; //1M var hadRead = 0; var start = 0; var stop = 0; readBob = function (start,stop) { console.log("Read:[" + start + ':' + stop + ']'); if (file.webkitSlice) { var blob = file.webkitSlice(start,stop); } else if (file.mozSlice) { var blob = file.mozSlice(start,stop); } else { var blob = file.slice(start,stop); } reader.readAsDataURL(blob); } reader = new FileReader(); reader.onerror = function (evt) { console.debug(evt.target.error.message); switch (evt.target.error.code) { case evt.target.error.NOT_FOUND_ERR: alert('File Not Found!'); break; case evt.target.error.NOT_READABLE_ERR: alert('File is not readable'); break; case evt.target.error.ABORT_ERR: console.debug("errorHandler ABORT_ERROR"); break; // noop default: alert('An error occurred reading this file.'); } ; }; reader.onabort = function (e) { console.debug(e.target.error.message); //alert('File read cancelled'); }; reader.onloadstart = function (e) { document.getElementById('progress_bar').className = 'loading'; }; reader.onload = function (e) { if (reader.readyState == FileReader.DONE) { // DONE == 2 var callback = function () { hadRead = stop; progress.style.width = Math.round(hadRead / file.size * 100) + '%'; progress.textContent = Math.round(hadRead / file.size * 100) + '%'; if (hadRead >= file.size) { return; } stop = (hadRead + length) > file.size ? (file.size) : (hadRead + length); start = hadRead; readBob(start,stop); } uploadData(reader.result,file.size,start,stop,callback); //setTimeout(callback,1000); } } stop = (hadRead + length) > file.size ? (file.size) : (hadRead + length); readBob(start,stop); } // Check for the various File API support. if (window.File && window.FileReader && window.FileList && window.Blob) { document.getElementById('files').addEventListener('change',handleFileSelect,false); } else { alert('您的浏览器不支持HTML5 API,请使用最新版本的chrome或者firefox浏览器'); } //php //TODO: //1. 合并同一个文件、 //2. 判断是否文件结束 //3. 多用户并发情况下,不能互相干扰 session_start(); $data = $_POST["data"]; if(substr($data,37) == "data:application/octet-stream;base64,"){ $data = substr($data,37); } $data = base64_decode($data); $size = $_POST["size"]; $end = $_POST["end"]; $beg = $_POST["beg"]; if($beg == 0){ $filename = tempnam("/tmp","FOO"); $_SESSION["filename"] = $filename; }else{ $filename = $_SESSION["filename"]; } // Let's make sure the file exists and is writable first. if (!$handle = fopen($filename,'a')) { echo "Cannot open file ($filename)"; exit; } // Write $somecontent to our opened file. if (fwrite($handle,$data) === FALSE) { echo "Cannot write to file ($filename)"; exit; } fclose($handle); if($size == $end){ unset($_SESSION["filename"]); chmod($filename,0755); $newName = "Date".date("YmdHis",time()).".jpg"; rename($filename,$newName); echo $newName; } echo "Success"; exit(0); One more thing大文件上传在后端还要考虑如何支持接入服务器集群部署,把数据传递给后端的文件处理服务器。 结束语:感谢html5!积极拥抱新技术,这是快速提高生产力的最有效手段! 参考文献: http://www.html5rocks.com/zh/tutorials/file/dndfiles/ (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
