webuploader实现大文件断点续传
前端代码(基于Yii框架,逻辑可供参考)
<script>
var fileMd5; //文件MD5
var fileObj; //文件对象
var state = 'pending'; //状态
WebUploader.Uploader.register({
"before-send": "beforeSend",
"after-send-file": "afterSendFile"
}, {
beforeSend: function (block) {
//每个分片开始上传前检查分片是否存在
var deferred = WebUploader.Deferred();
$.post('<?=Url::to(['video-upload/check-chunk'])?>', {
fileMd5: fileMd5,
chunk: block.chunk
}, function (res) {
//不存在继续下一步
if (res.code == 1) {
deferred.resolve();
} else {
//已存在直接跳过
deferred.reject();
}
}, 'json');
//把fileMd5作为参数传入upload接口,配置项里无法直接配置所以放在这里
this.owner.options.formData.fileMd5 = fileMd5;
return deferred.promise();
},
afterSendFile: function () {
$.post('<?=Url::to(['video-upload/merge-chunks'])?>', {
fileMd5: fileMd5,
}, function (res) {
$('#videoupload-url').val(res.path);
$('#message').text(res.message);
}, 'json');
}
});
var uploader = new WebUploader.Uploader({
// 自动上传。
auto: false,
// swf文件路径
swf: '/webuploader/Uploader.swf',
// 文件接收服务端。
server: '<?=Url::to(['video-upload/upload'])?>',
sendAsBinary: false,
method: 'POST',
pick: '#upload',
// 只允许选择MP4文件
accept: {
extensions: 'mp4',
mimeTypes: 'video/mp4'
},
resize: false,
chunked: true, //开启分片上传
chunkSize: 2 * 1024 * 1024, //分片大小2M
threads: 1,
//单个文件的大小 50M
fileSingleSizeLimit: 50 * 1024 * 1024
});
uploader.on('fileQueued', function (file) {
//计算文件的唯一标记fileMd5,用于断点续传 如果.md5File(file)方法里只写一个file参数则计算MD5值会很慢 所以加了后面的参数:50*1024*1024,此处的大小取决于配置中的fileSingleSizeLimit
(new WebUploader.Uploader()).md5File(file, 0, 50 * 1024 * 1024).progress(function (percentage) {
}).then(function (val) {
$('#message').text("成功获取文件信息!");
fileMd5 = val;
//检查文件上传进度
$.post('<?=Url::to(['video-upload/check-file'])?>', {
fileMd5: fileMd5,
}, function (res) {
if (res.code === 200) {
$(".progress-bar").css('width', res.percent);
$(".progress-bar").text(res.percent);
if (res.percent == '0%') {
$("#btn").text('开始上传');
} else if (res.percent == '100%') {
$("#btn").text('上传完毕');
} else {
$("#btn").text('开始上传');
}
}
}, 'json');
});
uploader.stop(true);
});
uploader.on('uploadProgress', function (file, percentage) {
//动态更改上传进度
var percent = Math.round(percentage * 100) + '%';
$(".progress-bar").css('width', percent);
$(".progress-bar").text(percent);
$("#message").val('上传中...');
});
uploader.on('error', function (error) {
//错误处理
console.log(error);
$("#btn").val('开始上传');
uploader.stop(true);
if (error == 'F_EXCEED_SIZE') {
$('#message').text('文件最大限制为50M');
} else {
$('#message').text(error);
}
});
uploader.on('all', function (type) {
//实时监听上传状态
if (type === 'startUpload') {
state = 'uploading';
} else if (type === 'stopUpload') {
state = 'paused';
} else if (type === 'uploadFinished') {
state = 'done';
} else if (type === 'fileQueued') {
state = 'queued';
}
if (state === 'uploading') {
$("#btn").text('暂停上传');
} else if (state === 'paused') {
$("#btn").text('继续上传');
} else if (state === 'queued') {
$("#btn").removeAttr('disabled');
} else if (state === 'done') {
$("#btn").text('上传完毕');
$("#btn").attr('disabled', 'disabled');
}
});
uploader.on('uploadSuccess', function (file, response) {
$(".progress-bar").css('width', '100%');
$(".progress-bar").text('100%');
$("#btn").text('上传完毕');
$("#btn").attr('disabled', 'disabled');
});
//手动操作视频上传/暂停/继续
$("#btn").click(function () {
if (state === 'uploading') {
uploader.stop(true);
return false;
}
uploader.upload(fileObj);
});
</script>
后端接口代码
/**
* 分片上传
* @return array
*/
public function actionUpload()
{
if (Yii::$app->request->isPost) {
Yii::$app->response->format = Response::FORMAT_JSON;
$file = UploadedFile::getInstanceByName('file');
$chunk = Yii::$app->request->post('chunk', 0);
$fileMd5 = Yii::$app->request->post('fileMd5');
$chunks = Yii::$app->request->post('chunks', 1);
$percent = ((round(($chunk + 1) / $chunks, 2)) * 100) . '%';
//记录该文件的上传进度
Yii::$app->cache->set($fileMd5, $percent);
//把分片内容按照一定规律存储
rename($file->tempName, $this->tempFile($fileMd5, $chunk));
return [
'code' => 200,
'message' => 'success',
];
}
}
/**
* 合并所有分片
* @return array
*/
public function actionMergeChunks()
{
$fileMd5 = Yii::$app->request->post('fileMd5');
$full = $this->tempFile($fileMd5);
$fp = fopen($full, 'wb');
$i = 0;
while (file_exists($this->tempFile($fileMd5, $i))) {
$content = file_get_contents($this->tempFile($fileMd5, $i));
fwrite($fp, $content);
//删除分片
unlink($this->tempFile($fileMd5, $i));
$i++;
}
fclose($fp);
Yii::$app->cache->delete($fileMd5);
Yii::$app->response->format = Response::FORMAT_JSON;
$data = [
'code' => 200,
'message' => 'success',
'path' => '../upload/' . $fileMd5 . '.mp4',
];
return $data;
}
/**
* 分片上传前检查分片是否已存在
*/
public function actionCheckChunk()
{
if (Yii::$app->request->isPost) {
Yii::$app->response->format = Response::FORMAT_JSON;
$fileMd5 = Yii::$app->request->post('fileMd5');
$chunk = Yii::$app->request->post('chunk', 0);
if (file_exists($this->tempFile($fileMd5, $chunk))) {
return [
'code' => 0,
'message' => 'chunk_exists',
];
} else {
return [
'code' => 1,
'message' => 'chunk_not_exists',
];
}
}
}
/**
* 开始上传前检查文件上传进度
* @return array
*/
public function actionCheckFile()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$fileMd5 = Yii::$app->request->post('fileMd5');
$percent = Yii::$app->cache->get($fileMd5) ? Yii::$app->cache->get($fileMd5) : '0%';
return [
'code' => 200,
'percent' => $percent,
];
}
/**
* 获取格式化的文件名
* @param $fileMd5
* @param null $chunk
* @return bool|string
*/
private function tempFile($fileMd5, $chunk = null)
{
$tempFile = Yii::getAlias('@webroot/upload/' . $fileMd5);
if (is_null($chunk)) {
$tempFile .= '.mp4';
} else {
$tempFile .= '.chunk' . $chunk;
}
return $tempFile;
}
参考文章:http://blog.ncmem.com/wordpress/2023/11/17/webuploader%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/
欢迎入群一起讨论