From 9dbc48a80c66004d6bd94f437294e85e274441e3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 16 May 2024 15:44:10 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=BF=E9=87=8Coss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/alioss/config.php | 326 +++++++++--------- addons/alioss/info.ini | 2 +- application/extra/addons.php | 16 +- .../assets/addons/alioss}/js/spark.js | 0 public/assets/js/addons.js | 244 ++++++++++++- 5 files changed, 420 insertions(+), 168 deletions(-) rename {addons/alioss/assets => public/assets/addons/alioss}/js/spark.js (100%) mode change 100755 => 100644 diff --git a/addons/alioss/config.php b/addons/alioss/config.php index 0d958c2..4f75a00 100755 --- a/addons/alioss/config.php +++ b/addons/alioss/config.php @@ -2,232 +2,232 @@ return [ [ - 'name' => 'accessKeyId', - 'title' => 'AccessKey ID', - 'type' => 'string', + 'name' => 'accessKeyId', + 'title' => 'AccessKey ID', + 'type' => 'string', 'content' => [], - 'value' => '', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => 'LTAI5tDHguPjmAzjHaEuLrkt', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'accessKeySecret', - 'title' => 'AccessKey Secret', - 'type' => 'string', + 'name' => 'accessKeySecret', + 'title' => 'AccessKey Secret', + 'type' => 'string', 'content' => [], - 'value' => '', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '4VKvuOzJAbwsq6n67ZWfE0F7EtzgIl', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'bucket', - 'title' => 'Bucket名称', - 'type' => 'string', + 'name' => 'bucket', + 'title' => 'Bucket名称', + 'type' => 'string', 'content' => [], - 'value' => 'yourbucket', - 'rule' => 'required;bucket', - 'msg' => '', - 'tip' => '阿里云OSS的空间名', - 'ok' => '', - 'extend' => 'data-rule-bucket="[/^[0-9a-z_\-]{3,63}$/, \'请输入正确的Bucket名称\']"', + 'value' => 'zhongkexiangjiu', + 'rule' => 'required;bucket', + 'msg' => '', + 'tip' => '阿里云OSS的空间名', + 'ok' => '', + 'extend' => 'data-rule-bucket="[/^[0-9a-z_\\-]{3,63}$/, \'请输入正确的Bucket名称\']"', ], [ - 'name' => 'endpoint', - 'title' => 'Endpoint', - 'type' => 'string', + 'name' => 'endpoint', + 'title' => 'Endpoint', + 'type' => 'string', 'content' => [], - 'value' => 'oss-cn-shenzhen.aliyuncs.com', - 'rule' => 'required;endpoint', - 'msg' => '', - 'tip' => '请填写从阿里云存储获取的Endpoint', - 'ok' => '', - 'extend' => 'data-rule-endpoint="[/^(?!http(s)?:\/\/).*$/, \'不能以http(s)://开头\']"', + 'value' => 'oss-cn-beijing.aliyuncs.com', + 'rule' => 'required;endpoint', + 'msg' => '', + 'tip' => '请填写从阿里云存储获取的Endpoint', + 'ok' => '', + 'extend' => 'data-rule-endpoint="[/^(?!http(s)?:\\/\\/).*$/, \'不能以http(s)://开头\']"', ], [ - 'name' => 'cdnurl', - 'title' => 'CDN地址', - 'type' => 'string', + 'name' => 'cdnurl', + 'title' => 'CDN地址', + 'type' => 'string', 'content' => [], - 'value' => 'https://yourbucket.oss-cn-shenzhen.aliyuncs.com', - 'rule' => 'required;cdnurl', - 'msg' => '', - 'tip' => '请填写CDN地址,必须以http(s)://开头', - 'ok' => '', - 'extend' => 'data-rule-cdnurl="[/^http(s)?:\/\/.*$/, \'必需以http(s)://开头\']"', + 'value' => 'https://zhongkexiangjiu.oss-cn-beijing.aliyuncs.com', + 'rule' => 'required;cdnurl', + 'msg' => '', + 'tip' => '请填写CDN地址,必须以http(s)://开头', + 'ok' => '', + 'extend' => 'data-rule-cdnurl="[/^http(s)?:\\/\\/.*$/, \'必需以http(s)://开头\']"', ], [ - 'name' => 'uploadmode', - 'title' => '上传模式', - 'type' => 'select', + 'name' => 'uploadmode', + 'title' => '上传模式', + 'type' => 'select', 'content' => [ 'client' => '客户端直传(速度快,无备份)', 'server' => '服务器中转(占用服务器带宽,可备份)', ], - 'value' => 'server', - 'rule' => '', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => 'server', + 'rule' => '', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'serverbackup', - 'title' => '服务器中转模式备份', - 'type' => 'radio', + 'name' => 'serverbackup', + 'title' => '服务器中转模式备份', + 'type' => 'radio', 'content' => [ 1 => '备份(附件管理将产生2条记录)', 0 => '不备份', ], - 'value' => '1', - 'rule' => '', - 'msg' => '', - 'tip' => '服务器中转模式下是否备份文件', - 'ok' => '', - 'extend' => '', + 'value' => '1', + 'rule' => '', + 'msg' => '', + 'tip' => '服务器中转模式下是否备份文件', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'savekey', - 'title' => '保存文件名', - 'type' => 'string', + 'name' => 'savekey', + 'title' => '保存文件名', + 'type' => 'string', 'content' => [], - 'value' => '/uploads/{year}{mon}{day}/{filemd5}{.suffix}', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '/uploads/{year}{mon}{day}/{filemd5}{.suffix}', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'expire', - 'title' => '上传有效时长', - 'type' => 'string', + 'name' => 'expire', + 'title' => '上传有效时长', + 'type' => 'string', 'content' => [], - 'value' => '600', - 'rule' => 'required', - 'msg' => '', - 'tip' => '用户停留页面上传有效时长,单位秒', - 'ok' => '', - 'extend' => '', + 'value' => '600', + 'rule' => 'required', + 'msg' => '', + 'tip' => '用户停留页面上传有效时长,单位秒', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'maxsize', - 'title' => '最大可上传', - 'type' => 'string', + 'name' => 'maxsize', + 'title' => '最大可上传', + 'type' => 'string', 'content' => [], - 'value' => '10M', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '10M', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'mimetype', - 'title' => '可上传后缀格式', - 'type' => 'string', + 'name' => 'mimetype', + 'title' => '可上传后缀格式', + 'type' => 'string', 'content' => [], - 'value' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'multiple', - 'title' => '多文件上传', - 'type' => 'bool', + 'name' => 'multiple', + 'title' => '多文件上传', + 'type' => 'bool', 'content' => [], - 'value' => '0', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'thumbstyle', - 'title' => '缩略图样式', - 'type' => 'string', + 'name' => 'thumbstyle', + 'title' => '缩略图样式', + 'type' => 'string', 'content' => [], - 'value' => '', - 'rule' => '', - 'msg' => '', - 'tip' => '用于后台列表缩略图样式,可使用:?x-oss-process=image/resize,m_lfit,w_120,h_90', - 'ok' => '', - 'extend' => '', + 'value' => '', + 'rule' => '', + 'msg' => '', + 'tip' => '用于后台列表缩略图样式,可使用:?x-oss-process=image/resize,m_lfit,w_120,h_90', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'chunking', - 'title' => '分片上传', - 'type' => 'radio', + 'name' => 'chunking', + 'title' => '分片上传', + 'type' => 'radio', 'content' => [ 1 => '开启', 0 => '关闭', ], - 'value' => '0', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'chunksize', - 'title' => '分片大小', - 'type' => 'number', + 'name' => 'chunksize', + 'title' => '分片大小', + 'type' => 'number', 'content' => [], - 'value' => '4194304', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '4194304', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'syncdelete', - 'title' => '附件删除时是否同步删除云存储文件', - 'type' => 'bool', + 'name' => 'syncdelete', + 'title' => '附件删除时是否同步删除云存储文件', + 'type' => 'bool', 'content' => [], - 'value' => '0', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'apiupload', - 'title' => 'API接口使用云存储', - 'type' => 'bool', + 'name' => 'apiupload', + 'title' => 'API接口使用云存储', + 'type' => 'bool', 'content' => [], - 'value' => '0', - 'rule' => 'required', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => '1', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], [ - 'name' => 'noneedlogin', - 'title' => '免登录上传', - 'type' => 'checkbox', + 'name' => 'noneedlogin', + 'title' => '免登录上传', + 'type' => 'checkbox', 'content' => [ - 'api' => 'API', + 'api' => 'API', 'index' => '前台', 'admin' => '后台', ], - 'value' => '', - 'rule' => '', - 'msg' => '', - 'tip' => '', - 'ok' => '', - 'extend' => '', + 'value' => 'api,index,admin', + 'rule' => '', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', ], ]; diff --git a/addons/alioss/info.ini b/addons/alioss/info.ini index e51ec16..53f78b3 100755 --- a/addons/alioss/info.ini +++ b/addons/alioss/info.ini @@ -4,7 +4,7 @@ intro = 使用阿里云OSS云储存,支持直传、服务器中转、分片上 author = FastAdmin website = https://www.fastadmin.net version = 1.2.4 -state = 0 +state = 1 url = /addons/alioss license = regular licenseto = 62324 diff --git a/application/extra/addons.php b/application/extra/addons.php index 09ad373..988b62a 100755 --- a/application/extra/addons.php +++ b/application/extra/addons.php @@ -3,6 +3,19 @@ return [ 'autoload' => false, 'hooks' => [ + 'app_init' => [ + 'alioss', + 'shopro', + ], + 'module_init' => [ + 'alioss', + ], + 'upload_config_init' => [ + 'alioss', + ], + 'upload_delete' => [ + 'alioss', + ], 'sms_send' => [ 'alisms', ], @@ -24,9 +37,6 @@ return [ 'upgrade' => [ 'shopro', ], - 'app_init' => [ - 'shopro', - ], 'config_init' => [ 'shopro', 'summernote', diff --git a/addons/alioss/assets/js/spark.js b/public/assets/addons/alioss/js/spark.js old mode 100755 new mode 100644 similarity index 100% rename from addons/alioss/assets/js/spark.js rename to public/assets/addons/alioss/js/spark.js diff --git a/public/assets/js/addons.js b/public/assets/js/addons.js index 7485ff7..6295cea 100755 --- a/public/assets/js/addons.js +++ b/public/assets/js/addons.js @@ -1,5 +1,247 @@ define([], function () { - if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.actionname == 'index') { + if (typeof Config.upload.storage !== 'undefined' && Config.upload.storage === 'alioss') { + require(['upload'], function (Upload) { + //获取文件MD5值 + var getFileMd5 = function (file, cb) { + //如果savekey中未检测到md5,则无需获取文件md5,直接返回upload的uuid + if (!Config.upload.savekey.match(/\{(file)?md5\}/)) { + cb && cb(file.upload.uuid); + return; + } + require(['../addons/alioss/js/spark'], function (SparkMD5) { + var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, + chunkSize = 10 * 1024 * 1024, + chunks = Math.ceil(file.size / chunkSize), + currentChunk = 0, + spark = new SparkMD5.ArrayBuffer(), + fileReader = new FileReader(); + + fileReader.onload = function (e) { + spark.append(e.target.result); + currentChunk++; + if (currentChunk < chunks) { + loadNext(); + } else { + cb && cb(spark.end()); + } + }; + + fileReader.onerror = function () { + console.warn('文件读取错误'); + }; + + function loadNext() { + var start = currentChunk * chunkSize, + end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; + + fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); + } + + loadNext(); + + }); + }; + + var _onInit = Upload.events.onInit; + //初始化中完成判断 + Upload.events.onInit = function () { + _onInit.apply(this, Array.prototype.slice.apply(arguments)); + //如果上传接口不是阿里OSS,则不处理 + if (this.options.url !== Config.upload.uploadurl) { + return; + } + $.extend(this.options, { + //关闭自动处理队列功能 + autoQueue: false, + params: function (files, xhr, chunk) { + var params = Config.upload.multipart; + if (chunk) { + return $.extend({}, params, { + filesize: chunk.file.size, + filename: chunk.file.name, + chunkid: chunk.file.upload.uuid, + chunkindex: chunk.index, + chunkcount: chunk.file.upload.totalChunkCount, + chunksize: this.options.chunkSize, + chunkfilesize: chunk.dataBlock.data.size, + width: chunk.file.width || 0, + height: chunk.file.height || 0, + type: chunk.file.type, + uploadId: chunk.file.uploadId, + key: chunk.file.key, + }); + } else { + params = $.extend({}, params, files[0].params); + params.category = files[0].category || ''; + } + return params; + }, + chunkSuccess: function (chunk, file, response) { + var etag = chunk.xhr.getResponseHeader("ETag").replace(/(^")|("$)/g, ''); + file.etags = file.etags ? file.etags : []; + file.etags[chunk.index] = etag; + }, + chunksUploaded: function (file, done) { + var that = this; + Fast.api.ajax({ + url: "/addons/alioss/index/upload", + data: { + action: 'merge', + filesize: file.size, + filename: file.name, + chunkid: file.upload.uuid, + chunkcount: file.upload.totalChunkCount, + md5: file.md5, + key: file.key, + uploadId: file.uploadId, + etags: file.etags, + category: file.category || '', + aliosstoken: Config.upload.multipart.aliosstoken, + }, + }, function (data, ret) { + done(JSON.stringify(ret)); + return false; + }, function (data, ret) { + file.accepted = false; + that._errorProcessing([file], ret.msg); + return false; + }); + + }, + }); + + var _success = this.options.success; + //先移除已有的事件 + this.off("success", _success).on("success", function (file, response) { + var ret = {code: 0, msg: response}; + try { + if (response) { + ret = typeof response === 'string' ? JSON.parse(response) : response; + } + if (file.xhr.status === 200) { + if (Config.upload.uploadmode === 'client') { + ret = {code: 1, data: {url: '/' + file.key}}; + var url = ret.data.url || ''; + + Fast.api.ajax({ + url: "/addons/alioss/index/notify", + data: {name: file.name, url: url, md5: file.md5, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, category: file.category || '', aliosstoken: Config.upload.multipart.aliosstoken} + }, function () { + return false; + }, function () { + return false; + }); + } + } else { + console.error(file.xhr); + } + } catch (e) { + console.error(e); + } + _success.call(this, file, ret); + }); + + this.on("addedfile", function (file) { + var that = this; + setTimeout(function () { + if (file.status === 'error') { + return; + } + getFileMd5(file, function (md5) { + var chunk = that.options.chunking && file.size > that.options.chunkSize ? 1 : 0; + var params = $(that.element).data("params") || {}; + var category = typeof params.category !== 'undefined' ? params.category : ($(that.element).data("category") || ''); + category = typeof category === 'function' ? category.call(that, file) : category; + Fast.api.ajax({ + url: "/addons/alioss/index/params", + data: {method: 'POST', category: category, md5: md5, name: file.name, type: file.type, size: file.size, chunk: chunk, chunksize: that.options.chunkSize, aliosstoken: Config.upload.multipart.aliosstoken}, + }, function (data) { + file.md5 = md5; + file.id = data.id; + file.key = data.key; + file.date = data.date; + file.uploadId = data.uploadId; + file.policy = data.policy; + file.signature = data.signature; + file.partsAuthorization = data.partsAuthorization; + file.params = data; + file.category = category; + + if (file.status != 'error') { + //开始上传 + that.enqueueFile(file); + } else { + that.removeFile(file); + } + return false; + }, function () { + that.removeFile(file); + }); + }); + }, 0); + }); + + if (Config.upload.uploadmode === 'client') { + var _method = this.options.method; + var _url = this.options.url; + this.options.method = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + if (!chunk) { + return "POST"; + } else { + return "PUT"; + } + } + return _method; + }; + this.options.url = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + var index = chunk.dataBlock.chunkIndex; + // debugger; + this.options.headers = {"Authorization": "OSS " + files[0]['id'] + ":" + files[0]['partsAuthorization'][index], "x-oss-date": files[0]['date']}; + if (!chunk) { + return Config.upload.uploadurl + "/" + files[0].key + "?uploadId=" + files[0].uploadId; + } else { + return Config.upload.uploadurl + "/" + files[0].key + "?partNumber=" + (index + 1) + "&uploadId=" + files[0].uploadId; + } + } + return _url; + }; + this.on("sending", function (file, xhr, formData) { + var that = this; + if (file.upload.chunked) { + var _send = xhr.send; + xhr.send = function () { + var chunk = null; + file.upload.chunks.forEach(function (item) { + if (item.status == 'uploading') { + chunk = item; + } + }); + if (chunk) { + _send.call(xhr, chunk.dataBlock.data); + } + }; + } + }); + } + }; + }); +} + +if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.actionname == 'index') { require.config({ paths: { 'vue3': "../addons/shopro/libs/vue",