HTML5 大文件分片上传

当我们上传一个超大的文件到服务器,不可能一次性上传过去。一方面是后台有限制,一方面网络不稳定的话无法做到断点上传。所以,我们可以将文件分割成多段上传,前端和后台都记录当前上传的段索引,最后合并下文件即可。这里主要用到了浏览器 file 对象和 FormData 对象的特性。

html代码:

1
<input type="file" onchange="handleUpload()">

js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const LENGTH = 10*1024;  // 每次上传10kb
var file,
chunks = [],
index = 1, // 当前上传块
total; // 总块数

function handleUpload() {
file = document.querySelector('[type=file]').files[0];
total = Math.ceil(file.size / LENGTH);

for(var i = 0; i < total; i++) {
chunks[i] = file.slice(i*LENGTH, (i+1)*LENGTH);
}

upload(chunks, index, total, polling);
}

function upload(chunks, index, total, callback) {
var xhr = new XMLHttpRequest(),
chunk = chunks[index - 1],
formData = new FormData();

// 表单对象
formData.append('file', chunk);
formData.append('index', index);
formData.append('total', total);
formData.append('filename', file.name);

xhr.open('POST', './upload.php');
xhr.send(formData);

xhr.onload = function() {
var res = JSON.parse(xhr.response);
if(typeof callback == 'function') {
callback.call(this, res, chunks, index, total);
}
}
}

function polling(res, chunks, index, total)
{
if(res.isFinish) {
alert('上传成功')
}else {
console.log(res.progress);
upload(chunks, index + 1, total, polling);
}
}

php代码:

文件上传类 FileUpload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php

class FileUpload
{
private $index;
private $total;
private $filename;
private $filePath = './document/';
private $file;
private $tmpFile; // 临时文件

function __construct($tmpFile, $index, $total, $filename)
{
$this->index = $index;
$this->total = $total;
$this->filename = $filename;
$this->tmpFile = $tmpFile;

$this->move_file();
}

/**
* 创建文件夹
* @return bool
*/
public function touch_dir()
{
if(!file_exists($this->filePath)) {
return mkdir($this->filePath);
}
}

/**
* 移动文件
*/
public function move_file()
{
$this->touch_dir();
$this->file = $this->filePath . $this->filename . '_' . $this->index;
move_uploaded_file($this->tmpFile, $this->file);
}

/**
* 合并文件
*/
public function merge_file()
{
if($this->index == $this->total) {
$mergeFile = $this->filePath . $this->filename;
for($i = 1; $i <= $this->total; $i++) {
$chunk = file_get_contents($mergeFile.'_'.$i);
file_put_contents($mergeFile, $chunk, FILE_APPEND);
}
$this->del_file();
}
}

/**
* 删除文件
*/
public function del_file()
{
for($i = 1; $i <= $this->total; $i++) {
$delFile = $this->filePath . $this->filename. '_' . $i;
@unlink($delFile);
}
}

/**
* 结果返回
* @return stdClass
*/
public function getReturn()
{
$result = new stdClass();
if($this->index == $this->total) {
$result->isFinish = TRUE;
$this->merge_file();
}else {
$result->progess = ($this->index / $this->total);
}

return $result;
}
}

控制器:

1
2
3
4
5
6
7
8
<?php

require './FileUpload.php';

$fileUpload = new FileUpload($_FILES['file']['tmp_name'], $_POST['index'], $_POST['total'], $_POST['filename']);
$result = $fileUpload->getReturn();

echo json_encode($result);