js代碼javascript
/** * 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部分代碼: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 類css
<?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'); } } } }