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 = $(
'
'
'
'
'
);
$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 = $('
'
' +'
}
$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 类
namespace App\Tools\Upload;useIlluminate\Support\Facades\Event;useIntervention\Image\Facades\Image;useStorage;useApp\Events\UserUploadImage;useApp\Events\UserUploadAttach;useApp\Events\Logger;use\App\Jobs\OSSQueue;classUpload
{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 functioninfo()
{return $this->info;
}public functionupload()
{$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 functionchunkCheck()
{$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 functionchunkUpload()
{$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 functionchunksMerge()
{$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 functionsetImagesInfo()
{$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 functionsetAttachInfo()
{$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 functionwater()
{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');
}
}
}
}