大文件上传实例
问题
服务端为什么不能直接传大文件?跟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();