由於圖片太大,服務器上傳七牛雲耗費服務器的帶寬和內存,所以由用戶支付這部分的資源是最適合不過的了。 鑑於此,將七牛雲上傳統一改成前端發起上傳。前端webuploader 和 七牛雲配合上傳。
前端代碼如下:
<link rel="stylesheet" href="__STATIC__/admin/Widget/webuploader/0.1.5/webuploader.css"/>
<link rel="stylesheet" type="text/css" href="__STATIC__/admin/Widget/webuploader/0.1.5/image-upload/style.css">
<link href="__STATIC__/admin/Widget/webuploader/extra/bootstrap.min.css" rel="stylesheet">
<link href="__STATIC__/admin/Widget/webuploader/extra/simplebootadmin.css" rel="stylesheet">
<style>
.state-complete .progress {
display: none;
}
</style>
</head>
<body class="body_none">
<div class="wrap " style="padding:5px;">
<ul class="nav nav-tabs">
<li class="active"><a href="#wrapper" data-toggle="tab">上傳文件</a></li>
</ul>
<div class="tabbable">
<div class="tab-content ">
<div class="tab-pane active" id="wrapper">
<div id="container">
<!--頭部,相冊選擇和格式選擇-->
<div id="uploader">
<div class="queueList">
<div id="dndArea" class="placeholder">
<div id="filePicker"></div>
<p>或將文件拖到這裏,單次最多可選1個文件</p>
</div>
</div>
<div class="statusBar" style="display:none;">
<div class="progress" style="height: 0px;border: 1px solid #fff;">
<span class="text">0%</span>
<span class="percentage"></span>
</div>
<div class="info" ></div>
<div class="btns">
<div id="filePicker2"></div>
<div class="uploadBtn">開始上傳</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--<script src="__ROOT__/static/js/admin.js"></script>-->
<script src="__STATIC__/home/js/jquery.min.js"></script>
<script type="text/javascript" src="__STATIC__/admin/Widget/webuploader/0.1.5/webuploader.min.js"></script>
<script type="text/javascript">
/**
* 七牛雲配置
* @type {{tokenUrl: string, domain: string, mockToken: boolean, host: string, hash: boolean}}
*/
var options = {
host : "http://upload.qiniu.com",
tokenUrl : "{$server}",
domain : "xxxxxx", //qiniuyun kongjian yuming
mockToken : false,
hash : false
}
function get_selected_files() {
var files = [];
var idPre = 'id' + new Date().getTime();
if (jQuery("#wrapper").is(":hidden")) {
var file = new Object();
file.id = idPre + '1';
file.filepath = jQuery("#info").val();
file.preview_url = file.filepath;
file.url = file.filepath;
file.name = "";//jQuery(".filelist li .title").eq(i).html();
files.push(file);
} else {
var number = jQuery(".filelist li").size();
for (var i = 0; i < number; i++) {
var file = new Object();
var $file = jQuery(".filelist li").eq(i);
file.id = idPre + i;
file.filepath = $file.data("filepath");
file.preview_url = $file.data("preview_url");//httpUrl+file.filepath;
file.url = $file.data("url");
file.name = $file.data("name");
if (file.url == undefined) {
continue;
} else {
files.push(file);
}
}
}
return files;
}
var multi = false;//是否允許同時選多個文件
var maxFiles =1;//允許上傳多少文件
var fileErrorMsg = {};
(function ($) {
// 當domReady的時候開始初始化
$(function () {
var $wrap = $('#uploader'),
// 圖片容器
$queue = $('<ul class="filelist"></ul>').appendTo($wrap.find('.queueList')),
// 狀態欄,包括進度和控制按鈕
$statusBar = $wrap.find('.statusBar'),
// 文件總體選擇信息。
$info = $statusBar.find('.info'),
// 上傳按鈕
$upload = $wrap.find('.uploadBtn').hide(),
// 沒選擇文件之前的內容。
$placeHolder = $wrap.find('.placeholder'),
$progress = $statusBar.find('.progress').hide(),
// 添加的文件數量
fileCount = 0,
// 添加的文件總大小
fileSize = 0,
// 優化retina, 在retina下這個值是2
ratio = window.devicePixelRatio || 1,
// 縮略圖大小
thumbnailWidth = 110 * ratio,
thumbnailHeight = 110 * ratio,
// 可能有pedding, ready, uploading, confirm, done.
state = 'pedding',
// 所有文件的進度信息,key爲file id
percentages = {},
// 判斷瀏覽器是否支持圖片的base64
isSupportBase64 = (function () {
var data = new Image();
var support = true;
data.onload = data.onerror = function () {
if (this.width != 1 || this.height != 1) {
support = false;
}
}
data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
return support;
})(),
// 檢測是否已經安裝flash,檢測flash的版本
flashVersion = (function () {
var version;
try {
version = navigator.plugins['Shockwave Flash'];
version = version.description;
} catch (ex) {
try {
version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch (ex2) {
version = '0.0';
}
}
version = version.match(/\d+/g);
return parseFloat(version[0] + '.' + version[1], 10);
})(),
supportTransition = (function () {
var s = document.createElement('p').style,
r = 'transition' in s ||
'WebkitTransition' in s ||
'MozTransition' in s ||
'msTransition' in s ||
'OTransition' in s;
s = null;
return r;
})(),
// WebUploader實例
uploader;
if (!WebUploader.Uploader.support('flash') && WebUploader.browser.ie) {
// flash 安裝了但是版本過低。
if (flashVersion) {
(function (container) {
window['expressinstallcallback'] = function (state) {
switch (state) {
case 'Download.Cancelled':
alert('您取消了更新!')
break;
case 'Download.Failed':
alert('安裝失敗')
break;
default:
alert('安裝已成功,請刷新!');
break;
}
delete window['expressinstallcallback'];
};
var swf = './expressInstall.swf';
// insert flash object
var html = '<object type="application/' +
'x-shockwave-flash" data="' + swf + '" ';
if (WebUploader.browser.ie) {
html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
}
html += 'width="100%" height="100%" style="outline:0">' +
'<param name="movie" value="' + swf + '" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowscriptaccess" value="always" />' +
'</object>';
container.html(html);
})($wrap);
// 壓根就沒有安轉。
} else {
$wrap.html('<a href="http://www.adobe.com/go/getflashplayer" target="_blank" border="0"><img alt="get flash player" src="http://www.adobe.com/macromedia/style_guide/images/160x41_Get_Flash_Player.jpg" /></a>');
}
return;
} else if (!WebUploader.Uploader.support()) {
alert('Web Uploader 不支持您的瀏覽器!');
return;
}
// 實例化
uploader = WebUploader.create({
// 選完文件後,是否自動上傳。
auto: true,
// swf文件路徑
swf: '__STATIC__/admin/Widget/webuploader/0.1.5/Uploader.swf',
// 文件接收服務端。
server: options.host,
// 選擇文件的按鈕。可選。
// 內部根據當前運行是創建,可能是input元素,也可能是flash.
pick: {
id : '#filePicker',
label : '點擊選擇文件',
},
duplicate :true,
chunked: false,
compress: false,
resize: false,
// 只允許選擇圖片文件。
accept: {
title: 'File',
extensions: '{$ext}',
mimeTypes: '*'
}
});
uploader.on("uploadStart", function(file){
if(!options.mockToken) {
GetToken(options.tokenUrl, file);
} else {
uploader.options.formData = {
token : options.mockTokenValue
}
token = options.mockTokenValue;
}
});
// 拖拽時不接受 js, txt 文件。
uploader.on('dndAccept', function (items) {
var denied = false,
len = items.length,
i = 0,
// 修改js類型
unAllowed = 'text/plain;application/javascript ';
for (; i < len; i++) {
// 如果在列表裏面
if (~unAllowed.indexOf(items[i].type)) {
denied = true;
break;
}
}
return !denied;
});
if (maxFiles > 1) {
// 添加“添加文件”的按鈕,
uploader.addButton({
id: '#filePicker2',
label: '繼續添加'
});
}
uploader.on('ready', function () {
window.uploader = uploader;
});
// 當有文件添加進來時執行,負責view的創建
function addFile(file) {
var $li = $('<li id="' + file.id + '">' +
'<p class="title">' + file.name + '</p>' +
'<p class="imgWrap"></p>' +
'<p class="progress"><span></span></p>' +
'</li>'),
$btns = $('<div class="file-panel">' +
'<span class="cancel">刪除</span>' +
'<span class="rotateRight">向右旋轉</span>' +
'<span class="rotateLeft">向左旋轉</span></div>').appendTo($li),
$prgress = $li.find('p.progress span'),
$wrap = $li.find('p.imgWrap'),
$info = $('<p class="error"></p>'),
showError = function (code) {
switch (code) {
case 'exceed_size':
text = '文件大小超出';
break;
case 'interrupt':
text = '上傳暫停';
break;
default:
text = '上傳失敗,請重試';
break;
}
$info.text(text).appendTo($li);
};
if (file.getStatus() === 'invalid') {
showError(file.statusText);
} else {
// @todo lazyload
$wrap.text('預覽中');
uploader.makeThumb(file, function (error, src) {
var img;
if (error) {
$wrap.text('不能預覽');
return;
}
if (isSupportBase64) {
img = $('<img src="' + src + '">');
$wrap.empty().append(img);
} else {
$.ajax('../../server/preview.php', {
method: 'POST',
data: src,
dataType: 'json'
}).done(function (response) {
if (response.result) {
img = $('<img src="' + response.result + '">');
$wrap.empty().append(img);
} else {
$wrap.text("預覽出錯");
}
});
}
}, thumbnailWidth, thumbnailHeight);
percentages[file.id] = [file.size, 0];
file.rotation = 0;
}
file.on('statuschange', function (cur, prev) {
if (prev === 'progress') {
$prgress.hide().width(0);
} else if (prev === 'queued') {
$li.off('mouseenter mouseleave');
$btns.remove();
}
// 成功
if (cur === 'error' || cur === 'invalid') {
showError(file.statusText);
percentages[file.id][1] = 1;
} else if (cur === 'interrupt') {
showError('interrupt');
} else if (cur === 'queued') {
percentages[file.id][1] = 0;
} else if (cur === 'progress') {
$info.remove();
$prgress.css('display', 'block');
} else if (cur === 'complete') {
$li.append('<span class="success"></span>');
}
$li.removeClass('state-' + prev).addClass('state-' + cur);
});
$li.on('mouseenter', function () {
$btns.stop().animate({height: 30});
});
$li.on('mouseleave', function () {
$btns.stop().animate({height: 0});
});
$btns.on('click', 'span', function () {
var index = $(this).index(),
deg;
switch (index) {
case 0:
uploader.removeFile(file);
return;
case 1:
file.rotation += 90;
break;
case 2:
file.rotation -= 90;
break;
}
if (supportTransition) {
deg = 'rotate(' + file.rotation + 'deg)';
$wrap.css({
'-webkit-transform': deg,
'-mos-transform': deg,
'-o-transform': deg,
'transform': deg
});
} else {
$wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');
}
});
$li.appendTo($queue);
}
// 負責view的銷燬
function removeFile(file) {
var $li = $('#' + file.id);
delete percentages[file.id];
updateTotalProgress();
$li.off().find('.file-panel').off().end().remove();
}
function updateTotalProgress() {
var loaded = 0,
total = 0,
spans = $progress.children(),
percent;
$.each(percentages, function (k, v) {
total += v[0];
loaded += v[0] * v[1];
});
percent = total ? loaded / total : 0;
spans.eq(0).text(Math.round(percent * 100) + '%');
spans.eq(1).css('width', Math.round(percent * 100) + '%');
updateStatus();
}
function updateStatus() {
var text = '', stats;
if (state === 'ready') {
text = '選中' + fileCount + '個文件,共' +
WebUploader.formatSize(fileSize) + '。';
} else if (state === 'confirm') {
stats = uploader.getStats();
if (stats.uploadFailNum) {
text = '已成功上傳' + stats.successNum + '個文件,' +
stats.uploadFailNum + '個文件上傳失敗,<a class="retry" href="#">重新上傳</a>失敗文件或<a class="ignore" href="#">忽略</a>'
}
} else {
stats = uploader.getStats();
text = '共' + fileCount + '個文件(' +
WebUploader.formatSize(fileSize) +
'),已上傳' + stats.successNum + '個';
if (stats.uploadFailNum) {
text += ',失敗' + stats.uploadFailNum + '個';
}
if (stats.progressNum > 0) {
text += '<br><font color="red">由於圖片過大,上傳期間請不要關閉對話框</font>';
}
}
$info.html(text);
}
function setState(val) {
var file, stats;
if (val === state) {
return;
}
$upload.removeClass('state-' + state);
$upload.addClass('state-' + val);
state = val;
switch (state) {
case 'pedding':
$placeHolder.removeClass('element-invisible');
$queue.hide();
$statusBar.addClass('element-invisible');
uploader.refresh();
break;
case 'ready':
$placeHolder.addClass('element-invisible');
$('#filePicker2').removeClass('element-invisible');
$queue.show();
$statusBar.removeClass('element-invisible');
uploader.refresh();
break;
case 'uploading':
$('#filePicker2').addClass('element-invisible');
$progress.show();
$upload.text('暫停上傳');
break;
case 'paused':
$progress.show();
$upload.text('繼續上傳');
break;
case 'confirm':
$progress.hide();
$('#filePicker2').removeClass('element-invisible');
$upload.text('開始上傳');
stats = uploader.getStats();
if (stats.successNum && !stats.uploadFailNum) {
setState('finish');
return;
}
break;
case 'finish':
stats = uploader.getStats();
if (stats.successNum) {
//alert( '上傳成功' );
} else {
// 沒有成功的圖片,重設
state = 'done';
location.reload();
}
break;
}
updateStatus();
}
uploader.onUploadProgress = function (file, percentage) {
var $li = $('#' + file.id),
$percent = $li.find('.progress span');
$percent.css('width', percentage * 100 + '%');
percentages[file.id][1] = percentage;
updateTotalProgress();
};
uploader.onFileQueued = function (file) {
fileCount++;
fileSize += file.size;
if (fileCount === 1) {
$placeHolder.addClass('element-invisible');
$statusBar.show();
}
addFile(file);
setState('ready');
updateTotalProgress();
};
uploader.onFileDequeued = function (file) {
fileCount--;
fileSize -= file.size;
if (!fileCount) {
setState('pedding');
}
removeFile(file);
updateTotalProgress();
};
uploader.on('all', function (type, file, msg) {
var stats;
switch (type) {
case 'uploadFinished':
setState('confirm');
break;
case 'startUpload':
setState('uploading');
break;
case 'stopUpload':
setState('paused');
break;
}
});
uploader.on("uploadSuccess", function(file, res){
UploadComplete(file,res);
// if(parseInt(file.size) <= parseInt(uploader.options.chunkSize)) {
//
// console.log(res);
// } else {
// //console.log(file);return false;
// MakeFile(m.get(file.name), file, options.hash);
// }
});
function UploadComplete(file,res) {
//ctx = new Array();
uploader.options.chunked = true;
// $("#" + file.id + " .percentage").text("上傳完畢");
// $(".itemStop").hide();
// $(".itemUpload").hide();
// $(".itemDel").hide();
// $("#" + file.id + " .url").text(options.domain + res.key);
// $("#url").attr("href",options.domain + res.key).text(options.domain + res.key);
var $file = jQuery("#" + file.id);
//$file.data("filepath", msg.data.filepath);
//$file.data("name", msg.data.name);
$file.data("url", options.domain + res.key); //原圖
$file.data("preview_url", options.domain + res.key); //縮略圖
//手動觸發一個持續化處理圖片
$.ajax({
async:true,
type: 'post',
data : {
'key' :res.key,
},
url: '{:url("appindex/Files/touchPfopImg")}',
success: function (res) {
}
});
}
uploader.on("uploadAccept", function (object, ret) {
if (ret.code == 0) {
fileErrorMsg[object.file.id] = ret.msg;
return false;
}
return true;
});
uploader.on('uploadError', function (file, reason) {
if (reason == 'server') {
$('#' + file.id).find('p.error').text(fileErrorMsg[file.id]);
}
});
uploader.onError = function (code) {
switch (code) {
case "Q_TYPE_DENIED":
code = "文件類型錯誤!";
break;
case "Q_EXCEED_NUM_LIMIT":
code = "最多隻能上傳" + maxFiles + '個文件';
break;
case "F_DUPLICATE":
code = "文件重複添加!";
break;
}
alert(code);
};
$upload.on('click', function () {
if ($(this).hasClass('disabled')) {
return false;
}
if (state === 'ready') {
uploader.upload();
} else if (state === 'paused') {
uploader.upload();
} else if (state === 'uploading') {
uploader.stop();
}
});
$info.on('click', '.retry', function () {
uploader.retry();
});
$info.on('click', '.ignore', function () {
alert('todo');
});
$upload.addClass('state-' + state);
updateTotalProgress();
function GetToken(tokenUrl, file) {
$.ajax({
async:false,
type: 'post',
data : {
'extension' :file.ext,
'size' : file.size
},
url: tokenUrl,
success: function (res) {
//console.log(res);
if(res.code == 1){
token = res.data.token;
//console.log(token);
if(options.hash) {
uploader.options.formData = {
token : token,
}
} else {
uploader.options.formData = {
token : token,
key: res.data.key
}
}
}else {
alert(res.msg);
setState('finish');
}
}
});
}
function MakeFile(ctx, file, hash) {
//console.log(111);return false;
var b = ctx.join(",");
if(hash){
$.ajax({
type: 'POST',
url: options.host + '/mkfile/' + file.size,
data: b,
contentType: "text/plain",
contentLength: b.length,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Authorization", 'UpToken ' + token);
},
success: function(res){
UploadComplete(file, res);
}
});
} else {
$.ajax({
type: 'POST',
url: options.host + '/mkfile/' + file.size + '/key/' + URLSafeBase64Encode(file.name),
data: b,
contentType: "text/plain",
contentLength: b.length,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Authorization", 'UpToken ' + token);
},
success: function(res){
UploadComplete(file, res);
}
});
}
}
function utf8_encode(argString) {
if (argString === null || typeof argString === 'undefined') {
return '';
}
var string = (argString + ''); // .replace(/\r\n/g, '\n').replace(/\r/g, '\n');
var utftext = '',
start, end, stringl = 0;
start = end = 0;
stringl = string.length;
for (var n = 0; n < stringl; n++) {
var c1 = string.charCodeAt(n);
var enc = null;
if (c1 < 128) {
end++;
} else if (c1 > 127 && c1 < 2048) {
enc = String.fromCharCode(
(c1 >> 6) | 192, (c1 & 63) | 128
);
} else if (c1 & 0xF800 ^ 0xD800 > 0) {
enc = String.fromCharCode(
(c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
);
} else { // surrogate pairs
if (c1 & 0xFC00 ^ 0xD800 > 0) {
throw new RangeError('Unmatched trail surrogate at ' + n);
}
var c2 = string.charCodeAt(++n);
if (c2 & 0xFC00 ^ 0xDC00 > 0) {
throw new RangeError('Unmatched lead surrogate at ' + (n - 1));
}
c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
enc = String.fromCharCode(
(c1 >> 18) | 240, ((c1 >> 12) & 63) | 128, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
);
}
if (enc !== null) {
if (end > start) {
utftext += string.slice(start, end);
}
utftext += enc;
start = end = n + 1;
}
}
if (end > start) {
utftext += string.slice(start, stringl);
}
return utftext;
}
function URLSafeBase64Encode(data) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = '',
tmp_arr = [];
if (!data) {
return data;
}
data = utf8_encode(data + '');
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc.replace(/\//g, '_').replace(/\+/g, '-');
}
});
})(jQuery);
</script>
</body>
</html>
後臺生成Token代碼如下:
這個方法爲Apicloud 端 七牛雲模塊和PC端 提供Token 。 Apicloud端可直接進行持久化處理。
/**
* 爲前端生成七牛雲上傳圖片參數
* Function createTokenForFront
* @author mselect <[email protected]>
* @DateTime 2019/5/22
*/
public function createTokenForFront(){
$extension = $this->request->post("extension", '', 'htmlspecialchars');
$wid = $this->request->post('wid', 0, 'intval');
$hei = $this->request->post('hei', 0, 'intval');
$size = $this->request->post('size', 0, 'intval');
if(empty($extension)){
return json(['code' => -1, 'msg' => '圖片後綴錯誤']);
}
$limitSize = 50 * 1024 * 1024; //單位B
if($limitSize < $size){
return json(['code' => 0, 'msg' => '上傳文件最大50M']);
}
$extension = strtolower($extension);
if(!in_array($extension, ['jpg', 'png', 'jpeg'])){
return json(['code' => -1, 'msg' => '圖片格式錯誤']);
}
require_once APP_PATH . '/../vendor/qiniu/autoload.php';
$auth = new Auth($this->accessKey, $this->secretKey);
$namep = substr(md5( microtime()),0,20) ;
$name = $namep . "." . $extension;
//生成縮略圖
$thumbName = $namep . "_thumb" . "." . $extension;
$entry = $this->bucket .":". $thumbName;
$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry);
$pipeline = 'uploadimg';
$array = [
'persistentOps'=>'imageView2/2/w/200/h/200/q/70/ignore-error/1/|saveas/'.$encodedEntryURI, //處理方式 200*200 縮略圖, 並保存縮略圖
'persistentNotifyUrl'=> url('appindex/Files/uploadImgNotify', '', true, true),
'persistentPipeline' => $pipeline,
];
//生成上傳Token
$token = $auth->uploadToken($this->bucket, null, 3600, $array );
$arr2 = [
'x:persistentOps'=>'imageView2/2/w/200/h/200/q/70/ignore-error/1/|saveas/'.$encodedEntryURI, //處理方式 200*200 縮略圖, 並保存縮略圖
'x:persistentNotifyUrl'=> url('appindex/Files/uploadImgNotify', '', true, true),
'x:persistentPipeline' =>$pipeline,
];
return json(['code' => 1 , 'msg' => '成功', 'data' => ['token' => $token, 'key'=> $name, 'params' => $arr2, 'url' => $this->httpPre . $this->domain . "/" . $name ]]);
}