工作中,经常碰到大文件上传的需求,如上传大的用户包、CDK列表等。常规的解决方案是采用form表单+ 方式提交给php处理,如下面的代码:
html代码:
<form enctype=\"multipart/form-data\" target=\"hidden_target\"
action=\"CRobFloor.php?a=Upload\" method=\"POST\"
id=\"frmImportCDK\">
<label class=\"col-lg-3\">请选择CDK文件</label>
<input type=\"file\" name=\"sCDKFile\" require=\"true\" datatype=\"require\" msg=\"请选择文件\" id=\"tFileUpload\"/>
<button type=\"submit\" id=\"btnUpload\">上传</button>
</form>
< name=\"hidden_target\" id=\"hidden_target\" src=\"about:blank\" style=\"display:none;\"></ >
php代码:
if (is_uploaded_file($_FILES[\"sCDKFile\"][\"tmp_name\"])) {
switch ($_FILES[\"sCDKFile\"][\"error\"]) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$this->Output (\'No file sent.\');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->Output (\'Exceeded filesize limit.\');
default:
$this->Output (\'Unknown errors. Code:\' . $_FILES[\"sCDKFile\"][\"error\"]);
}
//上传文件的类型
$stype = $_FILES[\"sCDKFile\"][\"type\"];
//如果文件符合要求并且上传过程中没有错误
if ($stype != \"text/plain\" && $stype != \"application/csv\") {
$this->Output (\"请选择上传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, 等),要装下载安装插件、比较麻烦。而且ActiveX插件不具有跨浏览器的特性,只能在IE浏览器上使用。
- CS解决方案:开发单独的客户端和服务器程序,使用UDP/TCP长链接通讯上传。缺点:需要单独开发Client,发布更新困难、
html5的解决方案
相关对象和API
File - 独立文件;提供只读信息,例如名称、文件大小、mimetype 和对文件句柄的引用。
FileList - File 对象的类数组序列(考虑 <input type=\"file\" multiple> 或者从桌面拖动目录或文件)。
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;
}
<input type=\"file\" id=\"files\" name=\"file\"/>
<div id=\"progress_bar\">
<div class=\"percent\">0%</div>
</div>
//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. = 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. = function (e) {
console.debug(e.target.error.message);
//alert(\'File read cancelled\');
};
reader. start = function (e) {
document.getElementById(\'progress_bar\').className = \'loading\';
};
reader. = 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, 0, 37) == \"data:application/octet-stream; 64,\"){
$data = substr($data, 37);
}
$data = 64_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!积极拥抱新技术,这是快速提高生产力的最有效手段!
继续阅读与本文标签相同的文章
上一篇 :
为什么移动鼠标会让操作系统跑得更快?
下一篇 :
如何打造一款好软件?
-
城市数字化后,新一代内生安全系统可全方位保护
2026-05-14栏目: 教程
-
谷歌也来“唱衰”5G,5G手机只会徒增功耗?为何这么说?
2026-05-14栏目: 教程
-
量子信息和量子技术白皮书合肥宣言在中科大发布
2026-05-14栏目: 教程
-
微信悄悄更新一新功能,来看看!
2026-05-14栏目: 教程
-
打破三大运营商垄断,第四大运营商终于来了!
2026-05-14栏目: 教程
