js代码
/**
* Created by 西瓜哥 on 2017/8/18.
* 分片,急速秒传和断点续传
*/
$(function () {
var obj = $('#uploader-video');
var attr = utils.toJson(obj.attr('data-options'));
var $list = $("#videoList");
var video_container = $(".video-container");
var GUID = WebUploader.Base.guid();
var chunkSize = 10 * 1024 * 1024;
//注册功能一定要写在前头,否则不会生效
WebUploader.Uploader.register({
'before-send-file': 'beforeSendFile'
, "before-send": "beforeSend"
, "after-send-file": "afterSendFile"
}, {
beforeSendFile: function (file) {
var owner = this.owner,
server = this.options.server,
deferred = WebUploader.Deferred(),
obj = $list.find(' #' + file.id);
owner.md5File(file.source).fail(function () {
deferred.reject();
}).progress(function (percentage) {
obj.find('.note').text('读取文件进度' + parseInt(percentage * 100) + "%");
}).then(function (md5Value) {
obj.find('.note').text('文件验证完毕...');
file.wholeMd5 = md5Value;
$.ajax(server, {
dataType: 'json',
type: 'post',
data: {
status: "md5Check",
unique: md5Value
},
cache: false,
timeout: 1000
}).then(function (response, textStatus, jqXHR) {
if (response.exist) {
deferred.reject();
owner.skipFile(file);
obj.find('.note').remove();
obj.find('p.state').attr('title', '正在上传').html('100%');
UploadComlate(file, response);
file.uniqueFileName = md5Value;
} else {
deferred.resolve();
file.uniqueFileName = md5Value;
}
}, function (jqXHR, textStatus, errorThrown) {
deferred.resolve();
});
});
return deferred.promise();
}, beforeSend: function (block) {
//分片验证是否已传过,用于断点续传
var deferred = WebUploader.Deferred();
var server = this.options.server;
$.ajax({
type: "POST"
, url: server
, data: {
status: "chunkCheck"
, name: block.file.uniqueFileName
, chunkIndex: block.chunk
, ext: block.file.ext
, size: block.end - block.start
}
, cache: false
, timeout: 1000
, dataType: "json"
}).then(function (response, textStatus, jqXHR) {
if (response.exist) {
deferred.reject();
} else {
deferred.resolve();
}
}, function (jqXHR, textStatus, errorThrown) { //任何形式的验证失败,都触发重新上传
deferred.resolve();
});
return deferred.promise();
}, afterSendFile: function (file) {
//合并文件
var chunksTotal = 0;
if ((chunksTotal = Math.ceil(file.size / chunkSize)) >= 1) {
//合并请求
var deferred = WebUploader.Deferred();
var server = this.options.server;
$.ajax({
type: "POST"
, url: server
, data: {
status: "chunksMerge"
, name: file.uniqueFileName
, chunks: chunksTotal
, original_name: file.source.name
, ext: file.ext
, md5: file.md5value
}
, cache: false
, dataType: "json"
}).then(function (response, textStatus, jqXHR) {
deferred.resolve();
UploadComlate(file, response);
}, function (jqXHR, textStatus, errorThrown) {
deferred.reject();
});
return deferred.promise();
} else {
UploadComlate(file);
}
}
});
var uploader = WebUploader.create({
// 选完文件后,是否自动上传。
auto: true,
// swf文件路径
swf: '/static/js/plugins/webuploader/Uploader.swf',
runtimeOrder: 'html5,flash',
// 文件接收服务端。
server: attr.url,
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick: {
id: '#videoPicker',
multiple: false,
},
threads: 1,
resize: false,
compress: false,
duplicate: true,
chunked: true,
chunkSize: chunkSize,
formData: {guid: GUID},
fileNumLimit: 10,
// fileSingleSizeLimit:2*1024*1024*1024,
accept: {
title: 'Videos',
extensions: 'mp4,mkv,flv,avi,vob,mov,mpg',
mimeTypes: 'video/*'
}
});
uploader.on('fileQueued', function (file) {
video_container.hide();
var $li = $(
'<div id="' + file.id + '" class="item">' +
'<div class="note">正在计算文件特征...</div>' +
'<div class="pic-box"><img src="/dist/home/images/video.jpg"></div>' +
'<p class="state" ><i class="fa fa-arrow-up"></i></p>' +
'</div>'
);
$list.append($li);
});
//发送前填充数据
uploader.on('uploadBeforeSend', function (block, data) {
// block为分块数据。
// file为分块对应的file对象。
// var file = block.file;
// var fileMd5 = file.wholeMd5;
// 修改data可以控制发送哪些携带数据。
// console.info("fileName= " + file.name + " fileMd5= " + fileMd5 + " fileId= " + file.id);
// 将存在file对象中的md5数据携带发送过去。
data.md5value = block.file.wholeMd5;//md5
//唯一标识符,用作断点续传
data.uniqueFileName = block.file.uniqueFileName;
if (block.chunks > 1) {
data.isChunked = true;
} else {
data.isChunked = false;
}
});
//前一个文件未传完,不能再添加文件
uploader.on('beforeFileQueued', function () {
if (obj.hasClass('disabled')) {
utils.fail('请等待上一个文件传完!');
return false;
}
});
//开始上传做一个标记
uploader.on('startUpload', function () {
obj.addClass('disabled');
});
uploader.on('uploadProgress', function (file, percentage) {
var $li = $list.find(' #' + file.id),
$percent = $li.find('.progress .progress-bar');
// 避免重复创建
if (!$percent.length) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo($li).find('.progress-bar');
}
$li.find(".note").remove();
$li.find('p.state').attr('title', '正在上传').html(parseInt(percentage * 100) + '%');
$percent.css('width', percentage * 100 + '%');
});
uploader.on('uploadError', function () {
utils.fail('文件上传失败');
});
//删除标记和进度条
uploader.on('uploadComplete', function (file) {
$('#' + file.id).find('.progress').fadeOut();
obj.removeClass('disabled');
$list.html('');
});
uploader.on("error", function (type, handler) {
if (type === "Q_TYPE_DENIED") {
utils.fail('上传文件格式不符合要求');
} else if (type === "F_EXCEED_SIZE") {
utils.fail('上传文件超过限制');
}
});
function UploadComlate(file, response) {
utils.success('上传成功');
uploader.reset();
$list.find('.item').remove();
video_container.show();
if (response.state) {
video_container.find('span').html(file.source.name);
video_container.find('input').val(response.url);
} else {
video_container.find('span').html(file.source.name);
video_container.find('input').val( response.data.file_path);
}
}
});
php部分代码:
public function batch(Request $request)
{
if ($request->isMethod('post')) {
$action = request()->get('status');
switch ($action) {
case "md5Check":
$file = $this->checkFile();
if ($file) {
return ["exist" => 1, "data" => $file];
} else {
return ["exist" => 0];
}
break;
case "chunkCheck":
$this->upload = new Upload();
$this->upload->chunkCheck();
return $this->upload->info();
break;
case "chunksMerge":
$this->upload = new Upload();
$this->upload->type = 'batch';
$this->upload->public = false;
$this->upload->user = $this->service->setUser();
$this->upload->chunksMerge();
return $this->upload->info();
break;
default:
$this->upload = new Upload();
$this->upload->chunkUpload();
return $this->upload->info();
}
}
}
public function checkFile()
{
if (request()->isMethod('post')) {
$md5 = request()->get('unique');
return $this->service->checkFile($md5);
}
}
uploader 类
<?php namespace App\Tools\Upload; use Illuminate\Support\Facades\Event; use Intervention\Image\Facades\Image; use Storage; use App\Events\UserUploadImage; use App\Events\UserUploadAttach; use App\Events\Logger; use \App\Jobs\OSSQueue; class Upload { private $public = true; private $type; private $water; private $info; private $img; private $original_name; private $original_path; private $thumb_image; private $ext; private $attachType = 'thumb'; private $mimetype; private $user; private $size; private $image_exts = ["png", "jpg", "jpeg", "gif", "bmp"]; private $video_exts = ["flv", "swf", "mkv", "avi", "rm", "rmvb", "mpeg", "mpg", "ogg", "ogv", "mov", "wmv", "mp4", "webm", "mp3", "wav", "mid"]; private $fileField = 'file'; public function __construct($type = 'images', $water = false) { $this->type = $type; $this->water = $water; } public function __set($name, $value) { $this->$name = $value; } public function __get($name) { return $this->$name; } public function info() { return $this->info; } public function upload() { $file = request()->file($this->fileField); if ($file->isValid()) { if ($this->type == 'images') { $allow_exts = explode(',', config('system.images_extensions')); $max_size = config('system.max_images_size') * 1024; } elseif ($this->type == 'video') { $allow_exts = explode(',', config('system.video_extensions')); $max_size = config('system.max_video_size') * 1024; } else { $allow_exts = explode(',', config('system.attach_extensions')); $max_size = config('system.max_attach_size') * 1024; } $this->ext = $file->getClientOriginalExtension(); $this->size = $file->getClientSize(); //批量上传 if ($this->type == 'batch') { $flag = false; if (in_array(strtolower($this->ext), $this->image_exts)) { $this->type = 'images'; $this->attachType = 'depot'; $flag = true; } if (in_array(strtolower($this->ext), $this->video_exts)) { $this->type = 'video'; $this->attachType = 'video'; $flag = true; } if ($flag == false) { $this->info = ['state' => '未知的上传类型']; return; } } else { if (!in_array(strtolower($this->ext), $allow_exts)) { $this->info = ['state' => '不允许上传的类型']; return; } if ($this->size > $max_size) { $this->info = ['state' => '上传文件大小超过限制']; return; } } $this->original_name = $file->getClientOriginalName(); $realPath = $file->getRealPath(); $this->mimetype = $file->getClientMimeType(); if ($this->attachType == 'agreement') { $path = upload_path() . '/agreements/' . date('Y-m-d'); } else { $path = upload_path() . '/' . $this->type . '/' . date('Y-m-d'); } if (!is_dir(public_path($path))) { @mkdir(public_path($path), 0777, true); } $small_thumb = ''; $oss_file = ''; if ($this->type == 'images') { if ($this->public == false) { $filename = $file->store($path); if (!is_file(storage_path('app/' . $filename))) { $this->info = ['state' => '文件上传失败,请确保storage目录可写']; return; } } else { $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext; Storage::disk('uploads')->put($filename, file_get_contents($realPath)); if (!is_file(public_path($filename))) { $this->info = ['state' => '文件上传失败,请确保uploads目录可写']; return; } } $this->thumb_image = $this->thumb($filename); $small_thumb = '/' . setSmallImg($filename); $oss_file = '/' . setOSSImg($filename); $this->original_path = $filename; $this->setImagesInfo(); $images = Event::fire(new UserUploadImage($this->user))[0]; $link = '/images/' . $images->name; $newName = $images->name; $this->user->message = '上传了图片:' . $filename; } else if ($this->type == 'video') { if ($this->public == false) { $filename = $file->store($path); if (!is_file(storage_path('app/' . $filename))) { $this->info = ['state' => '文件上传失败,请确保storage目录可写']; return; } } else { $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext; Storage::disk('uploads')->put($filename, file_get_contents($realPath)); if (!is_file(public_path($filename))) { $this->info = ['state' => '文件上传失败,请确保uploads目录可写']; return; } } $this->original_path = $filename; $this->setAttachInfo(); $video = Event::fire(new UserUploadAttach($this->user))[0]; $link = '/video/' . $video->name; $newName = $filename; $this->user->message = '上传了视频:' . $filename; } else if ($this->type == 'attach') { if ($this->public == false) { $filename = $file->store($path); if (!is_file(storage_path('app/' . $filename))) { $this->info = ['state' => '文件上传失败,请确保storage目录可写']; return; } } else { $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext; Storage::disk('uploads')->put($filename, file_get_contents($realPath)); if (!is_file(public_path($filename))) { $this->info = ['state' => '文件上传失败,请确保uploads目录可写']; return; } } $this->original_path = $filename; $this->setAttachInfo(); $video = Event::fire(new UserUploadAttach($this->user))[0]; $link = '/video/' . $video->name; $newName = $filename; $this->user->message = '上传了附件:' . $filename; } else { $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext; Storage::disk('uploads')->put($filename, file_get_contents($realPath)); if (!is_file(public_path($filename))) { $this->info = ['state' => '文件上传失败,请确保uploads目录可写']; return; } $this->original_path = $filename; $link = '/' . $filename; $newName = ''; $this->user->message = '上传了附件:' . $filename; } Event::fire(new Logger($this->user)); // 上传原图原附件到阿里云OSS if ($this->public == false) { dispatch((new OSSQueue($filename, false, $oss_file))->onQueue('high')); } $this->info = [ 'state' => 'SUCCESS', 'originalName' => $this->original_name, 'ext' => $this->ext, 'small' => $small_thumb, 'oss_file' => $oss_file, 'type' => $this->type, 'mime' => $this->mimetype, 'size' => $this->size, 'newName' => $newName, //链接形式访问 文件名称 'link' => $link, //链接形式访问地址 'url' => '/' . $filename, //文件存放路径 ]; return; } else { $this->info = ['state' => '文件上传失败']; return; } } public function chunkCheck() { $dir_name = request()->get('name'); $chunkIndex = request()->get('chunkIndex'); $size = request()->get('size'); if (!is_dir(storage_path('chunk_temp_files/' . $dir_name))) { Storage::disk('storage')->makeDirectory('chunk_temp_files/' . $dir_name); } $chunk_file = storage_path('chunk_temp_files/' . $dir_name . '/' . $chunkIndex . '.tmp'); if (file_exists($chunk_file)) { if (filesize($chunk_file) == $size) { $this->info = ['exist' => 1]; return; } } $this->info = ['exist' => 0]; return; } public function chunkUpload() { $file = request()->file('file'); $isChunked = request()->get('isChunked'); $chunk = request()->get('chunk'); $chunks = request()->get('chunks'); $uniqueFileName = request()->get('uniqueFileName'); if ($file->isValid()) { $this->ext = $file->getClientOriginalExtension(); $this->original_name = $file->getClientOriginalName(); $this->mimetype = $file->getClientMimeType(); $this->size = $file->getClientSize(); $flag = false; $this->image_exts = explode(',', config('system.images_extensions')); $this->video_exts = explode(',', config('system.video_extensions')); if (in_array(strtolower($this->ext), $this->image_exts)) { $flag = true; } if (in_array(strtolower($this->ext), $this->video_exts)) { $flag = true; } if ($flag == false) { $this->info = ['chunked' => false, 'state' => '不允许的上传类型']; return; } $realPath = $file->getRealPath(); if ($isChunked=='true') { Storage::disk('storage')->put('chunk_temp_files/' . $uniqueFileName . '/' . $chunk . '.tmp', file_get_contents($realPath)); if ($chunks == ($chunk + 1)) { $this->info = ['chunked' => true, 'state' => 'SUCCESS', 'ext' => $this->ext, 'original' => $this->original_name]; } else { $this->info = ['chunked' => true, 'state' => 'chunked']; } } else { Storage::disk('storage')->put('chunk_temp_files/' . $uniqueFileName . '/tmp.tmp', file_get_contents($realPath)); $this->info = ['chunked' => true, 'state' => 'SUCCESS', 'ext' => $this->ext, 'original' => $this->original_name]; } return; } else { $this->info = ['chunked' => false, 'state' => '文件上传失败']; return; } } public function chunksMerge() { $store = request()->all(); $this->ext = $store['ext']; $dir_name = $store['name']; $chunks = $store['chunks']; $this->original_name = $store['original_name']; if (in_array(strtolower($this->ext), $this->image_exts)) { $this->type = 'images'; $this->attachType = 'depot'; }elseif (in_array(strtolower($this->ext), $this->video_exts)) { $this->type = 'video'; $this->attachType = 'video'; }else{ $this->type = 'attach'; $this->attachType = 'attach'; } $path = upload_path() . '/' . $this->type . '/' . date('Y-m-d'); if (!is_dir(storage_path('app/' . $path))) { Storage::makeDirectory($path); } $filename = $path . '/' .$dir_name. '.' . $this->ext; $files = Storage::disk('storage')->files('chunk_temp_files/' . $dir_name); if (count($files) == $chunks) { $arr=array(); foreach ($files as $value) { $arr[filemtime(storage_path($value))]=$value; } // 根据修改时间对文件排序 ksort($arr); $fp = fopen(storage_path('app/') . $filename, "ab"); foreach ($arr as $file) { $tempFile = storage_path($file); $handle = fopen($tempFile, "rb"); fwrite($fp, fread($handle, filesize($tempFile))); fclose($handle); unset($handle); } fclose($fp); Storage::disk('storage')->deleteDirectory('chunk_temp_files/' . $dir_name); $this->mimetype = Storage::disk('local')->mimeType($filename); $this->size = Storage::disk('local')->size($filename); $small_thumb = ''; $oss_file = ''; if ($this->type == 'images') { if (!is_dir(public_path($path))) { Storage::disk('uploads')->makeDirectory($path); } $this->thumb_image = $this->thumb($filename); $small_thumb = '/' . setSmallImg($filename); $oss_file = '/' . setOSSImg($filename); $this->original_path = $filename; $this->setImagesInfo(); $images = Event::fire(new UserUploadImage($this->user))[0]; $link = '/images/' . $images->name; $newName = $images->name; $this->user->message = '上传了图片:' . $filename; dispatch((new OSSQueue($filename, false, $oss_file))->onQueue('high')); } if ($this->type == 'video') { $this->original_path = $filename; $this->setAttachInfo(); $video = Event::fire(new UserUploadAttach($this->user))[0]; $link = '/videos/' . $video->name; $newName = $filename; $this->user->message = '上传了视频:' . $filename; dispatch((new OSSQueue($filename))->onQueue('low')); } //上传加入队列 控制台需要运行队列进程 php artisan queue:work // dispatch(new OSSQueue($filename)); Event::fire(new Logger($this->user)); $this->info = [ 'state' => 'SUCCESS', 'originalName' => $this->original_name, 'ext' => $this->ext, 'small' => $small_thumb, 'oss_file' => $oss_file, 'type' => $this->type, 'mime' => $this->mimetype, 'size' => $this->size, 'newName' => $newName, //链接形式访问 文件名称 'link' => $link, //链接形式访问地址 'url' => '/' . $filename, //文件存放路径 ]; return; } } private function setImagesInfo() { $this->user->public = $this->public; $this->user->image_type = $this->attachType; $this->user->mime = $this->mimetype; $this->user->original_name = $this->original_name; $this->user->ext = $this->ext; $this->user->thumb_image = $this->thumb_image; $this->user->original_image = $this->original_path; } private function setAttachInfo() { $this->user->public = $this->public; $this->user->attach_type = $this->attachType; $this->user->mime = $this->mimetype; $this->user->original_name = $this->original_name; $this->user->ext = $this->ext; // $this->user->thumb_image = $this->thumb_image; $this->user->original_path = $this->original_path; } private function thumb($filename) { if ($this->type == 'images') { $temp = explode('.', $filename); $ext = $temp[count($temp) - 1] ?: 'jpg'; $width = config('system.images_max_width') ?: 500; $height = config('system.images_max_height') ?: 500; $file = $this->public ? public_path($filename) : storage_path('app/' . $filename); $this->img = Image::make($file); $this->resizeByWidth(360); if (!is_dir(public_path(dirname(setSmallImg($filename))))) { @mkdir(public_path(dirname(setSmallImg($filename))), 0777, true); } $this->img->save(public_path(setSmallImg($filename))); $this->img = Image::make($file); if ($this->img->width() > $this->img->height()) { if ($this->img->width() > $width) { $this->resizeByWidth($width); } } else { if ($this->img->height() > $height) { $this->resizeByHeight($height); } } if ($this->water) { $this->water(); $this->img->save(public_path($filename), 60); } else { $this->img->save(public_path($filename), 90); } return $filename; } else { return ''; } } private function resizeByWidth($width) { $this->img = $this->img->resize($width, null, function ($constraint) { $constraint->aspectRatio(); }); } private function resizeByHeight($height) { $this->img = $this->img->resize(null, $height, function ($constraint) { $constraint->aspectRatio(); }); } private function water() { if ($this->water) { if (file_exists(public_path(config('system.images_water')))) { $this->img->insert(public_path(config('system.images_water')), 'center'); } else { $this->img->insert(resource_path('logo.png'), 'center'); } } } }