From 1a8bc76cc382b0d543553b90ba9b7f6c64f3f35e Mon Sep 17 00:00:00 2001 From: heshupeng <hsp@bronet.cn> Date: Mon, 21 Dec 2020 16:59:04 +0800 Subject: [PATCH] 七牛云上传 --- addons/qiniu/.addonrc | 1 + addons/qiniu/Qiniu.php | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/bootstrap.js | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/config.php | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/controller/Index.php | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/info.ini | 10 ++++++++++ addons/qiniu/library/Qiniu/Auth.php | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Cdn/CdnManager.php | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Config.php | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Etag.php | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Http/Client.php | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Http/Error.php | 35 +++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Http/Request.php | 18 ++++++++++++++++++ addons/qiniu/library/Qiniu/Http/Response.php | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Processing/ImageUrlBuilder.php | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Processing/Operation.php | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Processing/PersistentFop.php | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Region.php | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Rtc/AppClient.php | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Sms/Sms.php | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Storage/ArgusManager.php | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Storage/BucketManager.php | 1107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Storage/FormUploader.php | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Storage/ResumeUploader.php | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Storage/UploadManager.php | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/Zone.php | 47 +++++++++++++++++++++++++++++++++++++++++++++++ addons/qiniu/library/Qiniu/functions.php | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ application/extra/addons.php | 11 +++++++++-- public/assets/js/addons.js | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 29 files changed, 4892 insertions(+), 2 deletions(-) create mode 100644 addons/qiniu/.addonrc create mode 100644 addons/qiniu/Qiniu.php create mode 100644 addons/qiniu/bootstrap.js create mode 100644 addons/qiniu/config.php create mode 100644 addons/qiniu/controller/Index.php create mode 100644 addons/qiniu/info.ini create mode 100644 addons/qiniu/library/Qiniu/Auth.php create mode 100644 addons/qiniu/library/Qiniu/Cdn/CdnManager.php create mode 100644 addons/qiniu/library/Qiniu/Config.php create mode 100644 addons/qiniu/library/Qiniu/Etag.php create mode 100644 addons/qiniu/library/Qiniu/Http/Client.php create mode 100644 addons/qiniu/library/Qiniu/Http/Error.php create mode 100644 addons/qiniu/library/Qiniu/Http/Request.php create mode 100644 addons/qiniu/library/Qiniu/Http/Response.php create mode 100644 addons/qiniu/library/Qiniu/Processing/ImageUrlBuilder.php create mode 100644 addons/qiniu/library/Qiniu/Processing/Operation.php create mode 100644 addons/qiniu/library/Qiniu/Processing/PersistentFop.php create mode 100644 addons/qiniu/library/Qiniu/Region.php create mode 100644 addons/qiniu/library/Qiniu/Rtc/AppClient.php create mode 100644 addons/qiniu/library/Qiniu/Sms/Sms.php create mode 100644 addons/qiniu/library/Qiniu/Storage/ArgusManager.php create mode 100644 addons/qiniu/library/Qiniu/Storage/BucketManager.php create mode 100644 addons/qiniu/library/Qiniu/Storage/FormUploader.php create mode 100644 addons/qiniu/library/Qiniu/Storage/ResumeUploader.php create mode 100644 addons/qiniu/library/Qiniu/Storage/UploadManager.php create mode 100644 addons/qiniu/library/Qiniu/Zone.php create mode 100644 addons/qiniu/library/Qiniu/functions.php diff --git a/addons/qiniu/.addonrc b/addons/qiniu/.addonrc new file mode 100644 index 0000000..edb5dea --- /dev/null +++ b/addons/qiniu/.addonrc @@ -0,0 +1 @@ +{"license":"regular","licenseto":"10789","licensekey":"KN4BPhkrOJUbAtGV ZPZatGA602DE5oGjtAebRQ=="} \ No newline at end of file diff --git a/addons/qiniu/Qiniu.php b/addons/qiniu/Qiniu.php new file mode 100644 index 0000000..751b6f5 --- /dev/null +++ b/addons/qiniu/Qiniu.php @@ -0,0 +1,97 @@ +<?php + +namespace addons\qiniu; + +use fast\Http; +use Qiniu\Auth; +use think\Addons; +use think\Loader; + +/** + * 七牛云储存插件 + */ +class Qiniu extends Addons +{ + + /** + * 插件安装方法 + * @return bool + */ + public function install() + { + return true; + } + + /** + * 插件卸载方法 + * @return bool + */ + public function uninstall() + { + return true; + } + + /** + * 上传初始化时 + */ + public function uploadConfigInit(&$upload) + { + $config = $this->getConfig(); + + $policy = array( + 'saveKey' => ltrim($config['savekey'], '/'), + ); + + $config['savekey'] = str_replace(['$(year)', '$(mon)', '$(day)', '$(etag)', '$(ext)'], ['{year}', '{mon}', '{day}', '{filemd5}', '{.suffix}'], $config['savekey']); + $auth = new Auth($config['accessKey'], $config['secretKey']); + $multipart['qiniutoken'] = $auth->uploadToken($config['bucket'], null, $config['expire'], $policy); + $upload = array_merge($upload, [ + 'cdnurl' => $config['cdnurl'], + 'uploadurl' => $config['uploadmode'] == 'client' ? $config['uploadurl'] : addon_url('qiniu/index/upload', [], false, true), + 'uploadmode' => $config['uploadmode'], + 'bucket' => $config['bucket'], + 'maxsize' => $config['maxsize'], + 'mimetype' => $config['mimetype'], + 'savekey' => $config['savekey'], + 'chunking' => (bool)($config['chunking'] ?? $upload['chunking']), + 'chunksize' => (int)($config['chunksize'] ?? $upload['chunksize']), + 'multipart' => $multipart, + 'storage' => $this->getName(), + 'multiple' => $config['multiple'] ? true : false, + ]); + } + + /** + * 附件删除后 + */ + public function uploadDelete($attachment) + { + $config = $this->getConfig(); + if ($attachment['storage'] == 'qiniu' && isset($config['syncdelete']) && $config['syncdelete']) { + $auth = new Auth($config['accessKey'], $config['secretKey']); + $entry = $config['bucket'] . ':' . ltrim($attachment->url, '/'); + $encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry); + $url = 'http://rs.qiniu.com/delete/' . $encodedEntryURI; + $headers = $auth->authorization($url); + //删除云储存文件 + $ret = Http::sendRequest($url, [], 'POST', [CURLOPT_HTTPHEADER => ['Authorization: ' . $headers['Authorization']]]); + //如果是服务端中转,还需要删除本地文件 + //if ($config['uploadmode'] == 'server') { + // $filePath = ROOT_PATH . 'public' . str_replace('/', DS, $attachment->url); + // if ($filePath) { + // @unlink($filePath); + // } + //} + } + return true; + } + + public function appInit() + { + if (!class_exists('\Qiniu\Config')) { + Loader::addNamespace('Qiniu', ADDON_PATH . 'qiniu' . DS . 'library' . DS . 'Qiniu' . DS); + require_once ADDON_PATH . 'qiniu' . DS . 'library' . DS . 'Qiniu' . DS . 'functions.php'; + } + } + +} diff --git a/addons/qiniu/bootstrap.js b/addons/qiniu/bootstrap.js new file mode 100644 index 0000000..39b14ee --- /dev/null +++ b/addons/qiniu/bootstrap.js @@ -0,0 +1,135 @@ +//修改上传的接口调用 +require(['upload'], function (Upload) { + + //初始化中完成判断 + Upload.events.onInit = function () { + //如果上传接口不是七牛云,则不处理 + if (this.options.url !== Config.upload.uploadurl) { + return; + } + var _success = this.options.success; + + $.extend(this.options, { + chunkSuccess: function (chunk, file, response) { + this.contexts = this.contexts ? this.contexts : []; + this.contexts.push(typeof response.ctx !== 'undefined' ? response.ctx : response.data.ctx); + }, + chunksUploaded: function (file, done) { + var that = this; + Fast.api.ajax({ + url: "/addons/qiniu/index/upload", + data: { + action: 'merge', + filesize: file.size, + filename: file.name, + chunkid: file.upload.uuid, + chunkcount: file.upload.totalChunkCount, + width: file.width || 0, + height: file.height || 0, + type: file.type, + qiniutoken: Config.upload.multipart.qiniutoken, + contexts: this.contexts + }, + }, function (data, ret) { + done(JSON.stringify(ret)); + return false; + }, function (data, ret) { + file.accepted = false; + that._errorProcessing([file], ret.msg); + return false; + }); + + }, + }); + + //先移除已有的事件 + this.off("success", _success).on("success", function (file, response) { + var ret = {code: 0, msg: response}; + try { + ret = typeof response === 'string' ? JSON.parse(response) : response; + if (file.xhr.status === 200) { + if (typeof ret.key !== 'undefined') { + ret = {code: 1, msg: "", data: {url: '/' + ret.key, hash: ret.hash}}; + } + Fast.api.ajax({ + url: "/addons/qiniu/index/notify", + data: {name: file.name, url: ret.data.url, hash: ret.data.hash, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, qiniutoken: Config.upload.multipart.qiniutoken} + }, function () { + return false; + }, function () { + return false; + }); + } + } catch (e) { + console.error(e); + } + _success.call(this, file, ret); + }); + + //如果是直传模式 + if (Config.upload.uploadmode === 'client') { + var _url = this.options.url; + + //分片上传时URL链接不同 + this.options.url = function (files) { + this.options.headers = {"Authorization": "UpToken " + Config.upload.multipart.qiniutoken}; + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + if (!chunk) { + return Config.upload.uploadurl + '/mkfile/' + files[0].size; + } else { + return Config.upload.uploadurl + '/mkblk/' + chunk.dataBlock.data.size; + } + } + return _url; + }; + + this.options.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, + chunkfilesize: chunk.dataBlock.data.size, + width: chunk.file.width || 0, + height: chunk.file.height || 0, + type: chunk.file.type, + }); + } else { + var retParams = $.extend({}, params); + //七牛云直传使用的是token参数 + retParams.token = retParams.qiniutoken; + delete retParams.qiniutoken; + return retParams; + } + }; + + //分片上传时需要变更提交的内容 + this.on("sending", function (file, xhr, formData) { + 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); + } + }; + } + }); + } + }; + +}); diff --git a/addons/qiniu/config.php b/addons/qiniu/config.php new file mode 100644 index 0000000..4e3c707 --- /dev/null +++ b/addons/qiniu/config.php @@ -0,0 +1,211 @@ +<?php + +return [ + [ + 'name' => 'accessKey', + 'title' => 'accessKey', + 'type' => 'string', + 'content' => [], + 'value' => 'N3xUImXz6EBxMz9uqPWSroc7uiTF87HfB-4bFvNR', + 'rule' => 'required', + 'msg' => '', + 'tip' => '请在个人中心 > 密钥管理中获取 > AK', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'secretKey', + 'title' => 'secretKey', + 'type' => 'string', + 'content' => [], + 'value' => '1Z4QR_hEj4jEgbck8v1qiWc3gQkHtQUHaTXOIDsO', + 'rule' => 'required', + 'msg' => '', + 'tip' => '请在个人中心 > 密钥管理中获取 > SK', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'bucket', + 'title' => 'bucket', + 'type' => 'string', + 'content' => [], + 'value' => 'antsfresh', + 'rule' => 'required', + 'msg' => '', + 'tip' => '存储空间名称', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'uploadurl', + 'title' => '上传接口地址', + 'type' => 'select', + 'content' => [ + 'https://upload-z0.qiniup.com' => '华东 https://upload-z0.qiniup.com', + 'https://upload-z1.qiniup.com' => '华北 https://upload-z1.qiniup.com', + 'https://upload-z2.qiniup.com' => '华南 https://upload-z2.qiniup.com', + 'https://upload-na0.qiniup.com' => '北美 https://upload-na0.qiniup.com', + 'https://upload-as0.qiniup.com' => '东南亚 https://upload-as0.qiniup.com', + ], + 'value' => 'https://upload-z2.qiniup.com', + 'rule' => 'required', + 'msg' => '', + 'tip' => '推荐选择最近的地址', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'cdnurl', + 'title' => 'CDN地址', + 'type' => 'string', + 'content' => [], + 'value' => 'https://yixiaoxian.qiniu.broing.cn', + 'rule' => 'required', + 'msg' => '', + 'tip' => '未绑定CDN的话可使用七牛分配的测试域名', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'uploadmode', + 'title' => '上传模式', + 'type' => 'select', + 'content' => [ + 'client' => '客户端直传(速度快,无备份)', + 'server' => '服务器中转(占用服务器带宽,可备份)', + ], + 'value' => 'client', + 'rule' => '', + 'msg' => '', + 'tip' => '启用服务器中转时务必配置操作员和密码', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'savekey', + 'title' => '保存文件名', + 'type' => 'string', + 'content' => [], + 'value' => '/uploads/$(year)$(mon)$(day)/$(etag)$(ext)', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'expire', + 'title' => '上传有效时长', + 'type' => 'string', + 'content' => [], + 'value' => '600', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'maxsize', + 'title' => '最大可上传', + 'type' => 'string', + 'content' => [], + 'value' => '10M', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'mimetype', + 'title' => '可上传后缀格式', + 'type' => 'string', + 'content' => [], + 'value' => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'multiple', + 'title' => '多文件上传', + 'type' => 'radio', + 'content' => [ + 1 => '开启', + 0 => '关闭', + ], + 'value' => '1', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'thumbstyle', + 'title' => '缩略图样式', + 'type' => 'string', + 'content' => [], + 'value' => '', + 'rule' => '', + 'msg' => '', + 'tip' => '用于附件管理缩略图样式,可使用:?imageView2/2/w/120/h/90/q/80', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'chunking', + 'title' => '分片上传', + 'type' => 'radio', + 'content' => [ + 1 => '开启', + 0 => '关闭', + ], + 'value' => '1', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'chunksize', + 'title' => '分片大小', + 'type' => 'number', + 'content' => [], + 'value' => '4194304', + 'rule' => 'required', + 'msg' => '', + 'tip' => '固定大小,不能修改', + 'ok' => '', + 'extend' => 'readonly', + ], + [ + 'name' => 'syncdelete', + 'title' => '附件删除时是否同步删除文件', + 'type' => 'bool', + 'content' => [], + 'value' => '1', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => '__tips__', + 'title' => '温馨提示', + 'type' => '', + 'content' => [], + 'value' => '在使用之前请注册七牛账号并进行认证,注册链接:<a href="https://portal.qiniu.com/signup?code=3l79xtos9w9qq" target="_blank">https://portal.qiniu.com/signup?code=3l79xtos9w9qq</a>', + 'rule' => '', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], +]; diff --git a/addons/qiniu/controller/Index.php b/addons/qiniu/controller/Index.php new file mode 100644 index 0000000..8c6d720 --- /dev/null +++ b/addons/qiniu/controller/Index.php @@ -0,0 +1,209 @@ +<?php + +namespace addons\qiniu\controller; + +use app\common\exception\UploadException; +use app\common\library\Upload; +use app\common\model\Attachment; +use Qiniu\Auth; +use Qiniu\Storage\ResumeUploader; +use Qiniu\Storage\UploadManager; +use think\addons\Controller; +use think\Config; + +/** + * 七牛管理 + * + */ +class Index extends Controller +{ + + public function _initialize() + { + //跨域检测 + check_cors_request(); + + parent::_initialize(); + Config::set('default_return_type', 'json'); + } + + public function index() + { + Config::set('default_return_type', 'html'); + $this->error("当前插件暂无前台页面"); + } + + /** + * 中转上传文件 + * 上传分片 + * 合并分片 + */ + public function upload() + { + Config::set('default_return_type', 'json'); + + $this->check(); + $config = get_addon_config('qiniu'); + $config['savekey'] = str_replace(['{year}', '{mon}', '{day}', '{filemd5}', '{.suffix}'], ['$(year)', '$(mon)', '$(day)', '$(etag)', '$(ext)'], $config['savekey']); + + // 构建鉴权对象 + $auth = new Auth($config['accessKey'], $config['secretKey']); + // 生成上传 Token + $token = $auth->uploadToken($config['bucket'], null, 3600, ['saveKey' => ltrim($config['savekey'], '/')]); + // 初始化 UploadManager 对象并进行文件的上传。 + $uploadMgr = new UploadManager(); + + $chunkid = $this->request->post("chunkid"); + if ($chunkid) { + $action = $this->request->post("action"); + $chunkindex = $this->request->post("chunkindex/d"); + $chunkcount = $this->request->post("chunkcount/d"); + $filesize = $this->request->post("filesize"); + $filename = $this->request->post("filename"); + if ($action == 'merge') { + if ($config['uploadmode'] == 'server') { + $attachment = null; + //合并分片文件 + try { + $upload = new Upload(); + $attachment = $upload->merge($chunkid, $chunkcount, $filename); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + } + + $contexts = $this->request->post("contexts/a", []); + $uploader = new ResumeUploader($token, null, null, $filesize); + list($ret, $err) = $uploader->setContexts($contexts)->makeFile($filename); + if ($err !== null) { + $this->error("上传失败"); + } else { + $this->success("上传成功", '', ['url' => '/' . $ret['key'], 'fullurl' => cdnurl('/' . $ret['key'], true), 'hash' => $ret['hash']]); + } + } else { + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $file = $upload->chunk($chunkid, $chunkindex, $chunkcount); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + + //上传分片文件 + //$file = $this->request->file('file'); + $filesize = $file->getSize(); + //合并分片文件 + $uploader = new ResumeUploader($token, null, fopen($file->getRealPath(), 'rb'), $filesize); + $ret = $uploader->uploadChunk($chunkindex, $file, $filesize); + $this->success("上传成功", "", $ret); + } + } else { + $attachment = null; + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + + $suffix = $upload->getSuffix(); + $md5 = md5_file($file->getRealPath()); + $search = ['$(year)', '$(mon)', '$(day)', '$(etag)', '$(ext)']; + $replace = [date("Y"), date("m"), date("d"), $md5, '.' . $suffix]; + $savekey = ltrim(str_replace($search, $replace, $config['savekey']), '/'); + + $attachment = $upload->upload($savekey); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + + //文件绝对路径 + $filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname(); + + //上传到七牛后保存的文件名 + $saveKey = ltrim($attachment->url, '/'); + + try { + // 调用 UploadManager 的 putFile 方法进行文件的上传。 + list($ret, $err) = $uploadMgr->putFile($token, $saveKey, $filePath); + + if ($err !== null) { + throw new \Exception("上传失败"); + } + //成功不做任何操作 + } catch (\Exception $e) { + $attachment->delete(); + @unlink($filePath); + $this->error("上传失败"); + } + + $this->success("上传成功", '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]); + } + } + + /** + * 通知回调 + */ + public function notify() + { + Config::set('default_return_type', 'json'); + + $this->check(); + + $size = $this->request->post('size'); + $name = $this->request->post('name'); + $hash = $this->request->post('hash', ''); + $type = $this->request->post('type'); + $width = $this->request->post('width'); + $height = $this->request->post('height'); + $url = $this->request->post('url'); + $suffix = substr($name, stripos($name, '.') + 1); + + $attachment = Attachment::where('url', $url)->where('storage', 'qiniu')->find(); + if (!$attachment) { + $params = array( + 'admin_id' => (int)session('admin.id'), + 'user_id' => (int)cookie('uid'), + 'filename' => $name, + 'filesize' => $size, + 'imagewidth' => $width, + 'imageheight' => $height, + 'imagetype' => $suffix, + 'imageframes' => 0, + 'mimetype' => $type, + 'url' => $url, + 'uploadtime' => time(), + 'storage' => 'qiniu', + 'sha1' => $hash, + ); + Attachment::create($params); + } + $this->success(); + } + + /** + * 检查签名是否正确或过期 + */ + protected function check() + { + $qiniutoken = $this->request->post('qiniutoken', $this->request->server('AUTHORIZATION'), 'trim'); + if (!$qiniutoken) { + $this->error("参数不正确"); + } + $config = get_addon_config('qiniu'); + $auth = new Auth($config['accessKey'], $config['secretKey']); + list($accessKey, $sign, $data) = explode(':', $qiniutoken); + if (!$accessKey || !$sign || !$data) { + $this->error("参数不正确"); + } + if ($accessKey !== $config['accessKey']) { + $this->error("参数不正确"); + } + if ($accessKey . ':' . $sign !== $auth->sign($data)) { + $this->error("签名不正确"); + } + $json = json_decode(\Qiniu\base64_urlSafeDecode($data), true); + if ($json['deadline'] < time()) { + $this->error("请求已经超时"); + } + } +} diff --git a/addons/qiniu/info.ini b/addons/qiniu/info.ini new file mode 100644 index 0000000..0d5d98b --- /dev/null +++ b/addons/qiniu/info.ini @@ -0,0 +1,10 @@ +name = qiniu +title = 七牛云储存 +intro = 使用七牛云储存,支持直传、服务器中转、分片上传 +author = FastAdmin +website = https://www.fastadmin.net +version = 1.1.3 +state = 1 +url = /addons/qiniu +license = regular +licenseto = 10789 diff --git a/addons/qiniu/library/Qiniu/Auth.php b/addons/qiniu/library/Qiniu/Auth.php new file mode 100644 index 0000000..b5e4a6b --- /dev/null +++ b/addons/qiniu/library/Qiniu/Auth.php @@ -0,0 +1,187 @@ +<?php +namespace Qiniu; + +use Qiniu\Zone; + +final class Auth +{ + private $accessKey; + private $secretKey; + + public function __construct($accessKey, $secretKey) + { + $this->accessKey = $accessKey; + $this->secretKey = $secretKey; + } + + public function getAccessKey() + { + return $this->accessKey; + } + + public function sign($data) + { + $hmac = hash_hmac('sha1', $data, $this->secretKey, true); + return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac); + } + + public function signWithData($data) + { + $encodedData = \Qiniu\base64_urlSafeEncode($data); + return $this->sign($encodedData) . ':' . $encodedData; + } + + public function signRequest($urlString, $body, $contentType = null) + { + $url = parse_url($urlString); + $data = ''; + if (array_key_exists('path', $url)) { + $data = $url['path']; + } + if (array_key_exists('query', $url)) { + $data .= '?' . $url['query']; + } + $data .= "\n"; + + if ($body !== null && $contentType === 'application/x-www-form-urlencoded') { + $data .= $body; + } + return $this->sign($data); + } + + public function verifyCallback($contentType, $originAuthorization, $url, $body) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return $originAuthorization === $authorization; + } + + public function privateDownloadUrl($baseUrl, $expires = 3600) + { + $deadline = time() + $expires; + + $pos = strpos($baseUrl, '?'); + if ($pos !== false) { + $baseUrl .= '&e='; + } else { + $baseUrl .= '?e='; + } + $baseUrl .= $deadline; + + $token = $this->sign($baseUrl); + return "$baseUrl&token=$token"; + } + + public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true) + { + $deadline = time() + $expires; + $scope = $bucket; + if ($key !== null) { + $scope .= ':' . $key; + } + + $args = self::copyPolicy($args, $policy, $strictPolicy); + $args['scope'] = $scope; + $args['deadline'] = $deadline; + + $b = json_encode($args); + return $this->signWithData($b); + } + + /** + *上传策略,参数规格详见 + *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html + */ + private static $policyFields = array( + 'callbackUrl', + 'callbackBody', + 'callbackHost', + 'callbackBodyType', + 'callbackFetchKey', + + 'returnUrl', + 'returnBody', + + 'endUser', + 'saveKey', + 'insertOnly', + + 'detectMime', + 'mimeLimit', + 'fsizeMin', + 'fsizeLimit', + + 'persistentOps', + 'persistentNotifyUrl', + 'persistentPipeline', + + 'deleteAfterDays', + 'fileType', + 'isPrefixalScope', + ); + + private static function copyPolicy(&$policy, $originPolicy, $strictPolicy) + { + if ($originPolicy === null) { + return array(); + } + foreach ($originPolicy as $key => $value) { + if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) { + $policy[$key] = $value; + } + } + return $policy; + } + + public function authorization($url, $body = null, $contentType = null) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return array('Authorization' => $authorization); + } + + public function authorizationV2($url, $method, $body = null, $contentType = null) + { + $urlItems = parse_url($url); + $host = $urlItems['host']; + + if (isset($urlItems['port'])) { + $port = $urlItems['port']; + } else { + $port = ''; + } + + $path = $urlItems['path']; + if (isset($urlItems['query'])) { + $query = $urlItems['query']; + } else { + $query = ''; + } + + //write request uri + $toSignStr = $method . ' ' . $path; + if (!empty($query)) { + $toSignStr .= '?' . $query; + } + + //write host and port + $toSignStr .= "\nHost: " . $host; + if (!empty($port)) { + $toSignStr .= ":" . $port; + } + + //write content type + if (!empty($contentType)) { + $toSignStr .= "\nContent-Type: " . $contentType; + } + + $toSignStr .= "\n\n"; + + //write body + if (!empty($body)) { + $toSignStr .= $body; + } + + $sign = $this->sign($toSignStr); + $auth = 'Qiniu ' . $sign; + return array('Authorization' => $auth); + } +} diff --git a/addons/qiniu/library/Qiniu/Cdn/CdnManager.php b/addons/qiniu/library/Qiniu/Cdn/CdnManager.php new file mode 100644 index 0000000..9b462f3 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Cdn/CdnManager.php @@ -0,0 +1,185 @@ +<?php + +namespace Qiniu\Cdn; + +use Qiniu\Auth; +use Qiniu\Http\Error; +use Qiniu\Http\Client; + +final class CdnManager +{ + + private $auth; + private $server; + + public function __construct(Auth $auth) + { + $this->auth = $auth; + $this->server = 'http://fusion.qiniuapi.com'; + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @return array + */ + public function refreshUrls(array $urls) + { + return $this->refreshUrlsAndDirs($urls, array()); + } + + /** + * @param array $dirs 待刷新的文件链接数组 + * @return array + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshDirs(array $dirs) + { + return $this->refreshUrlsAndDirs(array(), $dirs); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @param array $dirs 待刷新的目录链接数组 + * + * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码 + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + * + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshUrlsAndDirs(array $urls, array $dirs) + { + $req = array(); + if (!empty($urls)) { + $req['urls'] = $urls; + } + if (!empty($dirs)) { + $req['dirs'] = $dirs; + } + + $url = $this->server . '/v2/tune/refresh'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $urls 待预取的文件链接数组 + * + * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + */ + public function prefetchUrls(array $urls) + { + $req = array( + 'urls' => $urls, + ); + + $url = $this->server . '/v2/tune/prefetch'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取带宽数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getBandwidthData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/bandwidth'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取流量数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getFluxData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/flux'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取日志下载链接的域名数组 + * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01 + * + * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/log.html + */ + public function getCdnLogList(array $domains, $logDate) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['day'] = $logDate; + + $url = $this->server . '/v2/tune/log/list'; + $body = json_encode($req); + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + /** + * 构建时间戳防盗链鉴权的访问外链 + * + * @param string $rawUrl 需要签名的资源url + * @param string $encryptKey 时间戳防盗链密钥 + * @param string $durationInSeconds 链接的有效期(以秒为单位) + * + * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码 + */ + public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds) + { + $parsedUrl = parse_url($rawUrl); + $deadline = time() + $durationInSeconds; + $expireHex = dechex($deadline); + $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : ''; + $strToSign = $encryptKey . $path . $expireHex; + $signStr = md5($strToSign); + if (isset($parsedUrl['query'])) { + $signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex; + } else { + $signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex; + } + return $signedUrl; + } +} diff --git a/addons/qiniu/library/Qiniu/Config.php b/addons/qiniu/library/Qiniu/Config.php new file mode 100644 index 0000000..c80cd30 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Config.php @@ -0,0 +1,140 @@ +<?php +namespace Qiniu; + +final class Config +{ + const SDK_VER = '7.2.10'; + + const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改 + + const RSF_HOST = 'rsf.qiniu.com'; + const API_HOST = 'api.qiniu.com'; + const RS_HOST = 'rs.qiniu.com'; //RS Host + const UC_HOST = 'uc.qbox.me'; //UC Host + const RTCAPI_HOST = 'http://rtc.qiniuapi.com'; + const ARGUS_HOST = 'argus.atlab.ai'; + const CASTER_HOST = 'pili-caster.qiniuapi.com'; + const SMS_HOST="https://sms.qiniuapi.com"; + const RTCAPI_VERSION = 'v3'; + const SMS_VERSION='v1'; + + // Zone 空间对应的存储区域 + public $region; + //BOOL 是否使用https域名 + public $useHTTPS; + //BOOL 是否使用CDN加速上传域名 + public $useCdnDomains; + // Zone Cache + private $regionCache; + + // 构造函数 + public function __construct(Region $z = null) + { + $this->zone = $z; + $this->useHTTPS = false; + $this->useCdnDomains = false; + $this->regionCache = array(); + } + + public function getUpHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->cdnUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpBackupHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->srcUpHosts[0]; + } + + return $scheme . $host; + } + + public function getRsHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsHost; + } + + public function getRsfHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsfHost; + } + + public function getIovipHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->iovipHost; + } + + public function getApiHost($accessKey, $bucket) + { + $region = $this->getRegion($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->apiHost; + } + + private function getRegion($accessKey, $bucket) + { + $cacheId = "$accessKey:$bucket"; + + if (isset($this->regionCache[$cacheId])) { + $region = $this->regionCache[$cacheId]; + } elseif (isset($this->zone)) { + $region = $this->zone; + $this->regionCache[$cacheId] = $region; + } else { + $region = Zone::queryZone($accessKey, $bucket); + $this->regionCache[$cacheId] = $region; + } + return $region; + } +} diff --git a/addons/qiniu/library/Qiniu/Etag.php b/addons/qiniu/library/Qiniu/Etag.php new file mode 100644 index 0000000..d7be064 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Etag.php @@ -0,0 +1,76 @@ +<?php + +namespace Qiniu; + +use Qiniu\Config; + +final class Etag +{ + private static function packArray($v, $a) + { + return call_user_func_array('pack', array_merge(array($v), (array)$a)); + } + + private static function blockCount($fsize) + { + return intval(($fsize + (Config::BLOCK_SIZE - 1)) / Config::BLOCK_SIZE); + } + + private static function calcSha1($data) + { + $sha1Str = sha1($data, true); + $err = error_get_last(); + if ($err !== null) { + return array(null, $err); + } + $byteArray = unpack('C*', $sha1Str); + return array($byteArray, null); + } + + + public static function sum($filename) + { + $fhandler = fopen($filename, 'r'); + $err = error_get_last(); + if ($err !== null) { + return array(null, $err); + } + + $fstat = fstat($fhandler); + $fsize = $fstat['size']; + if ((int)$fsize === 0) { + fclose($fhandler); + return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null); + } + $blockCnt = self::blockCount($fsize); + $sha1Buf = array(); + + if ($blockCnt <= 1) { + array_push($sha1Buf, 0x16); + $fdata = fread($fhandler, Config::BLOCK_SIZE); + if ($err !== null) { + fclose($fhandler); + return array(null, $err); + } + list($sha1Code,) = self::calcSha1($fdata); + $sha1Buf = array_merge($sha1Buf, $sha1Code); + } else { + array_push($sha1Buf, 0x96); + $sha1BlockBuf = array(); + for ($i = 0; $i < $blockCnt; $i++) { + $fdata = fread($fhandler, Config::BLOCK_SIZE); + list($sha1Code, $err) = self::calcSha1($fdata); + if ($err !== null) { + fclose($fhandler); + return array(null, $err); + } + $sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code); + } + $tmpData = self::packArray('C*', $sha1BlockBuf); + list($sha1Final,) = self::calcSha1($tmpData); + $sha1Buf = array_merge($sha1Buf, $sha1Final); + } + $etag = \Qiniu\base64_urlSafeEncode(self::packArray('C*', $sha1Buf)); + return array($etag, null); + } +} diff --git a/addons/qiniu/library/Qiniu/Http/Client.php b/addons/qiniu/library/Qiniu/Http/Client.php new file mode 100644 index 0000000..0fe03ce --- /dev/null +++ b/addons/qiniu/library/Qiniu/Http/Client.php @@ -0,0 +1,160 @@ +<?php +namespace Qiniu\Http; + +use Qiniu\Config; +use Qiniu\Http\Request; +use Qiniu\Http\Response; + +final class Client +{ + public static function get($url, array $headers = array()) + { + $request = new Request('GET', $url, $headers); + return self::sendRequest($request); + } + + public static function delete($url, array $headers = array()) + { + $request = new Request('DELETE', $url, $headers); + return self::sendRequest($request); + } + + public static function post($url, $body, array $headers = array()) + { + $request = new Request('POST', $url, $headers, $body); + return self::sendRequest($request); + } + + public static function PUT($url, $body, array $headers = array()) + { + $request = new Request('PUT', $url, $headers, $body); + return self::sendRequest($request); + } + + public static function multipartPost( + $url, + $fields, + $name, + $fileName, + $fileBody, + $mimeType = null, + array $headers = array() + ) { + $data = array(); + $mimeBoundary = md5(microtime()); + + foreach ($fields as $key => $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$key\""); + array_push($data, ''); + array_push($data, $val); + } + + array_push($data, '--' . $mimeBoundary); + $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType; + $finalFileName = self::escapeQuotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\""); + array_push($data, "Content-Type: $finalMimeType"); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + // var_dump($data);exit; + $contentType = 'multipart/form-data; boundary=' . $mimeBoundary; + $headers['Content-Type'] = $contentType; + $request = new Request('POST', $url, $headers, $body); + return self::sendRequest($request); + } + + private static function userAgent() + { + $sdkInfo = "QiniuPHP/" . Config::SDK_VER; + + $systemInfo = php_uname("s"); + $machineInfo = php_uname("m"); + + $envInfo = "($systemInfo/$machineInfo)"; + + $phpVer = phpversion(); + + $ua = "$sdkInfo $envInfo PHP/$phpVer"; + return $ua; + } + + public static function sendRequest($request) + { + $t1 = microtime(true); + $ch = curl_init(); + $options = array( + CURLOPT_USERAGENT => self::userAgent(), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => $request->method, + CURLOPT_URL => $request->url, + ); + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + if (!empty($request->headers)) { + $headers = array(); + foreach ($request->headers as $key => $val) { + array_push($headers, "$key: $val"); + } + $options[CURLOPT_HTTPHEADER] = $headers; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + if (!empty($request->body)) { + $options[CURLOPT_POSTFIELDS] = $request->body; + } + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + $t2 = microtime(true); + $duration = round($t2 - $t1, 3); + $ret = curl_errno($ch); + if ($ret !== 0) { + $r = new Response(-1, $duration, array(), null, curl_error($ch)); + curl_close($ch); + return $r; + } + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = self::parseHeaders(substr($result, 0, $header_size)); + $body = substr($result, $header_size); + curl_close($ch); + return new Response($code, $duration, $headers, $body, null); + } + + private static function parseHeaders($raw) + { + $headers = array(); + $headerLines = explode("\r\n", $raw); + foreach ($headerLines as $line) { + $headerLine = trim($line); + $kv = explode(':', $headerLine); + if (count($kv) > 1) { + $kv[0] =self::ucwordsHyphen($kv[0]); + $headers[$kv[0]] = trim($kv[1]); + } + } + return $headers; + } + + private static function escapeQuotes($str) + { + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } + + private static function ucwordsHyphen($str) + { + return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str))); + } +} diff --git a/addons/qiniu/library/Qiniu/Http/Error.php b/addons/qiniu/library/Qiniu/Http/Error.php new file mode 100644 index 0000000..73477cf --- /dev/null +++ b/addons/qiniu/library/Qiniu/Http/Error.php @@ -0,0 +1,35 @@ +<?php +namespace Qiniu\Http; + +/** + * 七牛业务请求逻辑错误封装类,主要用来解析API请求返回如下的内容: + * <pre> + * {"error" : "detailed error message"} + * </pre> + */ +final class Error +{ + private $url; + private $response; + + public function __construct($url, $response) + { + $this->url = $url; + $this->response = $response; + } + + public function code() + { + return $this->response->statusCode; + } + + public function getResponse() + { + return $this->response; + } + + public function message() + { + return $this->response->error; + } +} diff --git a/addons/qiniu/library/Qiniu/Http/Request.php b/addons/qiniu/library/Qiniu/Http/Request.php new file mode 100644 index 0000000..43b0bfd --- /dev/null +++ b/addons/qiniu/library/Qiniu/Http/Request.php @@ -0,0 +1,18 @@ +<?php +namespace Qiniu\Http; + +final class Request +{ + public $url; + public $headers; + public $body; + public $method; + + public function __construct($method, $url, array $headers = array(), $body = null) + { + $this->method = strtoupper($method); + $this->url = $url; + $this->headers = $headers; + $this->body = $body; + } +} diff --git a/addons/qiniu/library/Qiniu/Http/Response.php b/addons/qiniu/library/Qiniu/Http/Response.php new file mode 100644 index 0000000..f22ab37 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Http/Response.php @@ -0,0 +1,176 @@ +<?php + +namespace Qiniu\Http; + +/** + * HTTP response Object + */ +final class Response +{ + public $statusCode; + public $headers; + public $body; + public $error; + private $jsonData; + public $duration; + + /** @var array Mapping of status codes to reason phrases */ + private static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** + * @param int $code 状态码 + * @param double $duration 请求时长 + * @param array $headers 响应头部 + * @param string $body 响应内容 + * @param string $error 错误描述 + */ + public function __construct($code, $duration, array $headers = array(), $body = null, $error = null) + { + $this->statusCode = $code; + $this->duration = $duration; + $this->headers = $headers; + $this->body = $body; + $this->error = $error; + $this->jsonData = null; + if ($error !== null) { + return; + } + + if ($body === null) { + if ($code >= 400) { + $this->error = self::$statusTexts[$code]; + } + return; + } + if (self::isJson($headers)) { + try { + $jsonData = self::bodyJson($body); + if ($code >= 400) { + $this->error = $body; + if ($jsonData['error'] !== null) { + $this->error = $jsonData['error']; + } + } + $this->jsonData = $jsonData; + } catch (\InvalidArgumentException $e) { + $this->error = $body; + if ($code >= 200 && $code < 300) { + $this->error = $e->getMessage(); + } + } + } elseif ($code >= 400) { + $this->error = $body; + } + return; + } + + public function json() + { + return $this->jsonData; + } + + private static function bodyJson($body) + { + return \Qiniu\json_decode((string) $body, true, 512); + } + + public function xVia() + { + $via = $this->headers['X-Via']; + if ($via === null) { + $via = $this->headers['X-Px']; + } + if ($via === null) { + $via = $this->headers['Fw-Via']; + } + return $via; + } + + public function xLog() + { + return $this->headers['X-Log']; + } + + public function xReqId() + { + return $this->headers['X-Reqid']; + } + + public function ok() + { + return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null; + } + + public function needRetry() + { + $code = $this->statusCode; + if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) { + return true; + } + } + + private static function isJson($headers) + { + return array_key_exists('Content-Type', $headers) && + strpos($headers['Content-Type'], 'application/json') === 0; + } +} diff --git a/addons/qiniu/library/Qiniu/Processing/ImageUrlBuilder.php b/addons/qiniu/library/Qiniu/Processing/ImageUrlBuilder.php new file mode 100644 index 0000000..1ac5bf7 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Processing/ImageUrlBuilder.php @@ -0,0 +1,282 @@ +<?php +namespace Qiniu\Processing; + +use Qiniu; + +/** + * 主要涉及图片链接拼接 + * + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + */ +final class ImageUrlBuilder +{ + /** + * mode合法范围值 + * + * @var array + */ + protected $modeArr = array(0, 1, 2, 3, 4, 5); + + /** + * format合法值 + * + * @var array + */ + protected $formatArr = array('psd', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'bmp'); + + /** + * 水印图片位置合法值 + * + * @var array + */ + protected $gravityArr = array('NorthWest', 'North', 'NorthEast', + 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast'); + + /** + * 缩略图链接拼接 + * + * @param string $url 图片链接 + * @param int $mode 缩略模式 + * @param int $width 宽度 + * @param int $height 长度 + * @param string $format 输出类型 + * @param int $quality 图片质量 + * @param int $interlace 是否支持渐进显示 + * @param int $ignoreError 忽略结果 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + public function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $interlace = null, + $quality = null, + $ignoreError = 1 + ) { + + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + // 参数合法性效验 + if (!in_array(intval($mode), $this->modeArr, true)) { + return $url; + } + + if (!$width || !$height) { + return $url; + } + + $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/'; + + // 拼接输出格式 + if (!is_null($format) + && in_array($format, $this->formatArr) + ) { + $thumbStr .= 'format/' . $format . '/'; + } + + // 拼接渐进显示 + if (!is_null($interlace) + && in_array(intval($interlace), array(0, 1), true) + ) { + $thumbStr .= 'interlace/' . $interlace . '/'; + } + + // 拼接图片质量 + if (!is_null($quality) + && intval($quality) >= 0 + && intval($quality) <= 100 + ) { + $thumbStr .= 'q/' . $quality . '/'; + } + + $thumbStr .= 'ignore-error/' . $ignoreError . '/'; + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr; + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + public function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/'; + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 拼接自适应原图的短边比例 + if (!is_null($watermarkScale) + && is_numeric($watermarkScale) + && $watermarkScale > 0 + && $watermarkScale < 1 + ) { + $waterStr .= 'ws/' . $watermarkScale . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + public function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/2/text/' + . \Qiniu\base64_urlSafeEncode($text) . '/font/' + . \Qiniu\base64_urlSafeEncode($font) . '/'; + + // 拼接文字大小 + if (is_int($fontSize)) { + $waterStr .= 'fontsize/' . $fontSize . '/'; + } + + // 拼接文字颜色 + if (!is_null($fontColor) + && $fontColor + ) { + $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/'; + } + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 效验url合法性 + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + protected function isUrl($url) + { + $urlArr = parse_url($url); + + return $urlArr['scheme'] + && in_array($urlArr['scheme'], array('http', 'https')) + && $urlArr['host'] + && $urlArr['path']; + } + + /** + * 检测是否有query + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + protected function hasQuery($url) + { + $urlArr = parse_url($url); + + return !empty($urlArr['query']); + } +} diff --git a/addons/qiniu/library/Qiniu/Processing/Operation.php b/addons/qiniu/library/Qiniu/Processing/Operation.php new file mode 100644 index 0000000..919136f --- /dev/null +++ b/addons/qiniu/library/Qiniu/Processing/Operation.php @@ -0,0 +1,60 @@ +<?php + +namespace Qiniu\Processing; + +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +final class Operation +{ + + private $auth; + private $token_expire; + private $domain; + + public function __construct($domain, $auth = null, $token_expire = 3600) + { + $this->auth = $auth; + $this->domain = $domain; + $this->token_expire = $token_expire; + } + + + /** + * 对资源文件进行处理 + * + * @param $key 待处理的资源文件名 + * @param $fops string|array fop操作,多次fop操作以array的形式传入。 + * eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px + * + * @return array 文件处理后的结果及错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($key, $fops) + { + $url = $this->buildUrl($key, $fops); + $resp = Client::get($url); + if (!$resp->ok()) { + return array(null, new Error($url, $resp)); + } + if ($resp->json() !== null) { + return array($resp->json(), null); + } + return array($resp->body, null); + } + + public function buildUrl($key, $fops, $protocol = 'http') + { + if (is_array($fops)) { + $fops = implode('|', $fops); + } + + $url = $protocol . "://$this->domain/$key?$fops"; + if ($this->auth !== null) { + $url = $this->auth->privateDownloadUrl($url, $this->token_expire); + } + + return $url; + } +} diff --git a/addons/qiniu/library/Qiniu/Processing/PersistentFop.php b/addons/qiniu/library/Qiniu/Processing/PersistentFop.php new file mode 100644 index 0000000..24e7b73 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Processing/PersistentFop.php @@ -0,0 +1,94 @@ +<?php +namespace Qiniu\Processing; + +use Qiniu\Config; +use Qiniu\Http\Client; +use Qiniu\Http\Error; +use Qiniu\Processing\Operation; + +/** + * 持久化处理类,该类用于主动触发异步持久化操作. + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/pfop/pfop.html + */ +final class PersistentFop +{ + /** + * @var 账号管理密钥对,Auth对象 + */ + private $auth; + + /* + * @var 配置对象,Config 对象 + * */ + private $config; + + + public function __construct($auth, $config = null) + { + $this->auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 对资源文件进行异步持久化处理 + * @param $bucket 资源所在空间 + * @param $key 待处理的源文件 + * @param $fops string|array 待处理的pfop操作,多个pfop操作以array的形式传入。 + * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360 + * @param $pipeline 资源处理队列 + * @param $notify_url 处理结果通知地址 + * @param $force 是否强制执行一次新的指令 + * + * + * @return array 返回持久化处理的persistentId, 和返回的错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = null, $force = false) + { + if (is_array($fops)) { + $fops = implode(';', $fops); + } + $params = array('bucket' => $bucket, 'key' => $key, 'fops' => $fops); + \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline); + \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url); + if ($force) { + $params['force'] = 1; + } + $data = http_build_query($params); + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::API_HOST . '/pfop/'; + $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded'); + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $response = Client::post($url, $data, $headers); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + $r = $response->json(); + $id = $r['persistentId']; + return array($id, null); + } + + public function status($id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id"; + $response = Client::get($url); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } +} diff --git a/addons/qiniu/library/Qiniu/Region.php b/addons/qiniu/library/Qiniu/Region.php new file mode 100644 index 0000000..eae21d1 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Region.php @@ -0,0 +1,196 @@ +<?php +namespace Qiniu; + +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +class Region +{ + + //源站上传域名 + public $srcUpHosts; + //CDN加速上传域名 + public $cdnUpHosts; + //资源管理域名 + public $rsHost; + //资源列举域名 + public $rsfHost; + //资源处理域名 + public $apiHost; + //IOVIP域名 + public $iovipHost; + + //构造一个Region对象 + public function __construct( + $srcUpHosts = array(), + $cdnUpHosts = array(), + $rsHost = "rs.qiniu.com", + $rsfHost = "rsf.qiniu.com", + $apiHost = "api.qiniu.com", + $iovipHost = null + ) { + + $this->srcUpHosts = $srcUpHosts; + $this->cdnUpHosts = $cdnUpHosts; + $this->rsHost = $rsHost; + $this->rsfHost = $rsfHost; + $this->apiHost = $apiHost; + $this->iovipHost = $iovipHost; + } + + //华东机房 + public static function regionHuadong() + { + $regionHuadong = new Region( + array("up.qiniup.com", 'up-jjh.qiniup.com', 'up-xs.qiniup.com'), + array('upload.qiniup.com', 'upload-jjh.qiniup.com', 'upload-xs.qiniup.com'), + 'rs.qbox.me', + 'rsf.qbox.me', + 'api.qiniu.com', + 'iovip.qbox.me' + ); + return $regionHuadong; + } + + //华东机房内网上传 + public static function qvmRegionHuadong() + { + $qvmRegionHuadong = new Region( + array("free-qvm-z0-xs.qiniup.com"), + 'rs.qbox.me', + 'rsf.qbox.me', + 'api.qiniu.com', + 'iovip.qbox.me' + ); + return $qvmRegionHuadong; + } + + //华北机房内网上传 + public static function qvmRegionHuabei() + { + $qvmRegionHuabei = new Region( + array("free-qvm-z1-zz.qiniup.com"), + "rs-z1.qbox.me", + "rsf-z1.qbox.me", + "api-z1.qiniu.com", + "iovip-z1.qbox.me" + ); + return $qvmRegionHuabei; + } + + //华北机房 + public static function regionHuabei() + { + $regionHuabei = new Region( + array('up-z1.qiniup.com'), + array('upload-z1.qiniup.com'), + "rs-z1.qbox.me", + "rsf-z1.qbox.me", + "api-z1.qiniu.com", + "iovip-z1.qbox.me" + ); + + return $regionHuabei; + } + + //华南机房 + public static function regionHuanan() + { + $regionHuanan = new Region( + array('up-z2.qiniup.com', 'up-dg.qiniup.com', 'up-fs.qiniup.com'), + array('upload-z2.qiniup.com', 'upload-dg.qiniup.com', 'upload-fs.qiniup.com'), + "rs-z2.qbox.me", + "rsf-z2.qbox.me", + "api-z2.qiniu.com", + "iovip-z2.qbox.me" + ); + return $regionHuanan; + } + + //北美机房 + public static function regionNorthAmerica() + { + //北美机房 + $regionNorthAmerica = new Region( + array('up-na0.qiniup.com'), + array('upload-na0.qiniup.com'), + "rs-na0.qbox.me", + "rsf-na0.qbox.me", + "api-na0.qiniu.com", + "iovip-na0.qbox.me" + ); + return $regionNorthAmerica; + } + + //新加坡机房 + public static function regionSingapore() + { + //新加坡机房 + $regionSingapore = new Region( + array('up-as0.qiniup.com'), + array('upload-as0.qiniup.com'), + "rs-as0.qbox.me", + "rsf-as0.qbox.me", + "api-as0.qiniu.com", + "iovip-as0.qbox.me" + ); + return $regionSingapore; + } + + /* + * GET /v2/query?ak=<ak>&&bucket=<bucket> + **/ + public static function queryRegion($ak, $bucket) + { + $Region = new Region(); + $url = Config::API_HOST . '/v2/query' . "?ak=$ak&bucket=$bucket"; + $ret = Client::Get($url); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + //parse Region; + + $iovipHost = $r['io']['src']['main'][0]; + $Region->iovipHost = $iovipHost; + $accMain = $r['up']['acc']['main'][0]; + array_push($Region->cdnUpHosts, $accMain); + if (isset($r['up']['acc']['backup'])) { + foreach ($r['up']['acc']['backup'] as $key => $value) { + array_push($Region->cdnUpHosts, $value); + } + } + $srcMain = $r['up']['src']['main'][0]; + array_push($Region->srcUpHosts, $srcMain); + if (isset($r['up']['src']['backup'])) { + foreach ($r['up']['src']['backup'] as $key => $value) { + array_push($Region->srcUpHosts, $value); + } + } + + //set specific hosts + if (strstr($Region->iovipHost, "z1") !== false) { + $Region->rsHost = "rs-z1.qbox.me"; + $Region->rsfHost = "rsf-z1.qbox.me"; + $Region->apiHost = "api-z1.qiniu.com"; + } elseif (strstr($Region->iovipHost, "z2") !== false) { + $Region->rsHost = "rs-z2.qbox.me"; + $Region->rsfHost = "rsf-z2.qbox.me"; + $Region->apiHost = "api-z2.qiniu.com"; + } elseif (strstr($Region->iovipHost, "na0") !== false) { + $Region->rsHost = "rs-na0.qbox.me"; + $Region->rsfHost = "rsf-na0.qbox.me"; + $Region->apiHost = "api-na0.qiniu.com"; + } elseif (strstr($Region->iovipHost, "as0") !== false) { + $Region->rsHost = "rs-as0.qbox.me"; + $Region->rsfHost = "rsf-as0.qbox.me"; + $Region->apiHost = "api-as0.qiniu.com"; + } else { + $Region->rsHost = "rs.qbox.me"; + $Region->rsfHost = "rsf.qbox.me"; + $Region->apiHost = "api.qiniu.com"; + } + + return $Region; + } +} diff --git a/addons/qiniu/library/Qiniu/Rtc/AppClient.php b/addons/qiniu/library/Qiniu/Rtc/AppClient.php new file mode 100644 index 0000000..a7dc11e --- /dev/null +++ b/addons/qiniu/library/Qiniu/Rtc/AppClient.php @@ -0,0 +1,209 @@ +<?php +namespace Qiniu\Rtc; + +use Qiniu\Http\Client; +use Qiniu\Http\Error; +use Qiniu\Config; +use Qiniu\Auth; + +class AppClient +{ + private $auth; + private $baseURL; + + public function __construct(Auth $auth) + { + $this->auth = $auth; + + $this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION); + } + + /* + * 创建应用 + * hub: 直播空间名 + * title: app 的名称 注意,Title 不是唯一标识,重复 create 动作将生成多个 app + * maxUsers:人数限制 + * NoAutoKickUser: bool 类型,可选,禁止自动踢人(抢流)。默认为 false , + 即同一个身份的 client (app/room/user) ,新的连麦请求可以成功,旧连接被关闭。 + */ + public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null) + { + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + $body = json_encode($params); + $ret = $this->post($this->baseURL, $body); + return $ret; + } + + /* + * 更新应用 + * appId: app 的唯一标识,创建的时候由系统生成。 + * Title: app 的名称, 可选。 + * Hub: 绑定的直播 hub,可选,用于合流后 rtmp 推流。 + * MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。 + * NoAutoKickUser: bool 类型,可选,禁止自动踢人。 + * MergePublishRtmp: 连麦合流转推 RTMP 的配置,可选择。其详细配置包括如下 + Enable: 布尔类型,用于开启和关闭所有房间的合流功能。 + AudioOnly: 布尔类型,可选,指定是否只合成音频。 + Height, Width: int64,可选,指定合流输出的高和宽,默认为 640 x 480。 + OutputFps: int64,可选,指定合流输出的帧率,默认为 25 fps 。 + OutputKbps: int64,可选,指定合流输出的码率,默认为 1000 。 + URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置。 + StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub 为 qn-zhibo ,配置 StreamTitle 为 $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。 + */ + public function updateApp($appId, $hub, $title, $maxUsers = null, $mergePublishRtmp = null, $noAutoKickUser = null) + { + $url = $this->baseURL . '/' . $appId; + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + if (!empty($mergePublishRtmp)) { + $params['mergePublishRtmp'] = $mergePublishRtmp; + } + $body = json_encode($params); + $ret = $this->post($url, $body); + return $ret; + } + + /* + * 获取应用信息 + * appId: app 的唯一标识,创建的时候由系统生成。 + */ + public function getApp($appId) + { + $url = $this->baseURL . '/' . $appId; + $ret = $this->get($url); + return $ret; + } + + /* + * 删除应用 + * appId: app 的唯一标识,创建的时候由系统生成 + */ + public function deleteApp($appId) + { + $url = $this->baseURL . '/' . $appId; + list(, $err) = $this->delete($url); + return $err; + } + + /* + * 获取房间内用户列表 + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 操作所查询的连麦房间。 + */ + public function listUser($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName); + $ret = $this->get($url); + return $ret; + } + + /* + * 踢出用户 + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 连麦房间 + * userId: 请求加入房间的用户ID + */ + public function kickUser($appId, $roomName, $userId) + { + $url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId); + list(, $err) = $this->delete($url); + return $err; + } + + /* + * 获取应用中活跃房间 + * appId: app 的唯一标识,创建的时候由系统生成。 + * prefix: 所查询房间名的前缀索引,可以为空。 + * offset: int 类型,分页查询的位移标记。 + * limit: int 类型,此次查询的最大长度。 + * GET /v3/apps/<AppID>/rooms?prefix=<RoomNamePrefix>&offset=<Offset>&limit=<Limit> + */ + public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null) + { + if (isset($prefix)) { + $query['prefix'] = $prefix; + } + if (isset($offset)) { + $query['offset'] = $offset; + } + if (isset($limit)) { + $query['limit'] = $limit; + } + if (isset($query) && !empty($query)) { + $query = '?' . http_build_query($query); + $url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query); + } else { + $url = sprintf("%s/%s/rooms", $this->baseURL, $appId); + } + $ret = $this->get($url); + return $ret; + } + + /* + * 生成加入房间的令牌 + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$ + * userId: 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$ + * expireAt: int64 类型,鉴权的有效时间,传入以秒为单位的64位Unix + 绝对时间,token 将在该时间后失效。 + * permission: 该用户的房间管理权限,"admin" 或 "user",默认为 "user" 。 + 当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权. + */ + public function appToken($appId, $roomName, $userId, $expireAt, $permission) + { + $params['appId'] = $appId; + $params['userId'] = $userId; + $params['roomName'] = $roomName; + $params['permission'] = $permission; + $params['expireAt'] = $expireAt; + $appAccessString = json_encode($params); + return $this->auth->signWithData($appAccessString); + } + + private function get($url, $cType = null) + { + $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType); + $rtcToken['Content-Type'] = $cType; + $ret = Client::get($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::delete($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/addons/qiniu/library/Qiniu/Sms/Sms.php b/addons/qiniu/library/Qiniu/Sms/Sms.php new file mode 100644 index 0000000..f19a124 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Sms/Sms.php @@ -0,0 +1,337 @@ +<?php +namespace Qiniu\Sms; + +use Qiniu\Http\Client; +use Qiniu\Http\Error; +use Qiniu\Config; +use Qiniu\Auth; + +class Sms +{ + private $auth; + private $baseURL; + + public function __construct(Auth $auth) + { + $this->auth = $auth; + + $this->baseURL = sprintf("%s/%s/", Config::SMS_HOST, Config::SMS_VERSION); + } + + /* + * 创建签名 + * signature: string 类型,必填,【长度限制8个字符内】超过长度会报错 + * source: string 类型,必填,申请签名时必须指定签名来源。取值范围为: + nterprises_and_institutions 企事业单位的全称或简称 + website 工信部备案网站的全称或简称 + app APP应用的全称或简称 + public_number_or_small_program 公众号或小程序的全称或简称 + store_name 电商平台店铺名的全称或简称 + trade_name 商标名的全称或简称, + * pics: 本地的图片路径 string 类型,可选 + *@return: 类型array { + "signature_id": <signature_id> + } + */ + public function createSignature($signature, $source, $pics = null) + { + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = $this->imgToBase64($pics); + } + $body = json_encode($params); + $url =$this->baseURL.'signature'; + $ret = $this->post($url, $body); + return $ret; + } + + /* + * 编辑签名 + * id 签名id : string 类型,必填, + * signature: string 类型,必填, + * source: string 类型,必填,申请签名时必须指定签名来源。取值范围为: + enterprises_and_institutions 企事业单位的全称或简称 + website 工信部备案网站的全称或简称 + app APP应用的全称或简称 + public_number_or_small_program 公众号或小程序的全称或简称 + store_name 电商平台店铺名的全称或简称 + trade_name 商标名的全称或简称, + * pics: 本地的图片路径 string 类型,可选, + * @return: 类型array { + "signature": string + } + */ + public function updateSignature($id, $signature, $source, $pics = null) + { + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = $this->imgToBase64($pics); + } + $body = json_encode($params); + $url =$this->baseURL.'signature/'.$id; + $ret = $this->PUT($url, $body); + return $ret; + } + + /* + * 查询签名 + * audit_status: 审核状态 string 类型,可选, + 取值范围为: "passed"(通过), "rejected"(未通过), "reviewing"(审核中) + * page:页码 int 类型, + * page_size: 分页大小 int 类型,可选, 默认为20 + *@return: 类型array { + "items": [{ + "id": string, + "signature": string, + "source": string, + "audit_status": string, + "reject_reason": string, + "created_at": int64, + "updated_at": int64 + }...], + "total": int, + "page": int, + "page_size": int, + } + */ + public function checkSignature($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL.'signature', + $audit_status, + $page, + $page_size + ); + $ret = $this->get($url); + return $ret; + } + + + /* + * 删除签名 + * id 签名id string 类型,必填, + * @retrun : 请求成功 HTTP 状态码为 200 + */ + public function deleteSignature($id) + { + $url = $this->baseURL . 'signature/' . $id; + list(, $err) = $this->delete($url); + return $err; + } + + + + + /* + * 创建模板 + * name : 模板名称 string 类型 ,必填 + * template: 模板内容 string 类型,必填 + * type: 模板类型 string 类型,必填, + 取值范围为: notification (通知类短信), verification (验证码短信), marketing (营销类短信) + * description: 申请理由简述 string 类型,必填 + * signature_id: 已经审核通过的签名 string 类型,必填 + * @return: 类型 array { + "template_id": string + } + */ + public function createTemplate( + $name, + $template, + $type, + $description, + $signture_id + ) { + $params['name'] = $name; + $params['template'] = $template; + $params['type'] = $type; + $params['description'] = $description; + $params['signature_id'] = $signture_id; + + $body = json_encode($params); + $url =$this->baseURL.'template'; + $ret = $this->post($url, $body); + return $ret; + } + + /* + * 查询模板 + * audit_status: 审核状态 string 类型 ,可选, + 取值范围为: passed (通过), rejected (未通过), reviewing (审核中) + * page: 页码 int 类型,可选,默认为 1 + * page_size: 分页大小 int 类型,可选,默认为 20 + * @return: 类型array{ + "items": [{ + "id": string, + "name": string, + "template": string, + "audit_status": string, + "reject_reason": string, + "type": string, + "signature_id": string, // 模版绑定的签名ID + "signature_text": string, // 模版绑定的签名内容 + "created_at": int64, + "updated_at": int64 + }...], + "total": int, + "page": int, + "page_size": int + } + */ + public function queryTemplate($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL.'template', + $audit_status, + $page, + $page_size + ); + $ret = $this->get($url); + return $ret; + } + + /* + * 编辑模板 + * id :模板id + * name : 模板名称 string 类型 ,必填 + * template: 模板内容 string 类型,必填 + * description: 申请理由简述 string 类型,必填 + * signature_id: 已经审核通过的签名 string 类型,必填 + * @retrun : 请求成功 HTTP 状态码为 200 + */ + public function updateTemplate( + $id, + $name, + $template, + $description, + $signature_id + ) { + $params['name'] = $name; + $params['template'] = $template; + $params['description'] = $description; + $params['signature_id'] = $signature_id; + $body = json_encode($params); + $url =$this->baseURL.'template/'.$id; + $ret = $this->PUT($url, $body); + return $ret; + } + + /* + * 删除模板 + * id :模板id string 类型,必填, + * @retrun : 请求成功 HTTP 状态码为 200 + */ + public function deleteTemplate($id) + { + $url = $this->baseURL . 'template/' . $id; + list(, $err) = $this->delete($url); + return $err; + } + + /* + * 发送短信 + * 编辑模板 + * template_id :模板id string类型,必填 + * mobiles : 手机号数组 []string 类型 ,必填 + * parameters: 模板内容 map[string]string 类型,可选 + * @return: 类型json { + "job_id": string + } + */ + public function sendMessage($template_id, $mobiles, $parameters = null) + { + $params['template_id'] = $template_id; + $params['mobiles'] = $mobiles; + if (!empty($parameters)) { + $params['parameters'] = $parameters; + } + $body = json_encode($params); + $url =$this->baseURL.'message'; + $ret = $this->post($url, $body); + return $ret; + } + + public function imgToBase64($img_file) + { + $img_base64 = ''; + if (file_exists($img_file)) { + $app_img_file = $img_file; // 图片路径 + $img_info = getimagesize($app_img_file); // 取得图片的大小,类型等 + $fp = fopen($app_img_file, "r"); // 图片是否可读权限 + if ($fp) { + $filesize = filesize($app_img_file); + if ($filesize > 5*1024*1024) { + die("pic size < 5M !"); + } + $content = fread($fp, $filesize); + $file_content = chunk_split(base64_encode($content)); // base64编码 + switch ($img_info[2]) { //判读图片类型 + case 1: + $img_type = 'gif'; + break; + case 2: + $img_type = 'jpg'; + break; + case 3: + $img_type = 'png'; + break; + } + //合成图片的base64编码 + $img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content; + } + fclose($fp); + } + + return $img_base64; + } + + private function get($url, $cType = null) + { + $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType); + $rtcToken['Content-Type'] = $cType; + $ret = Client::get($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::delete($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + private function PUT($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "PUT", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::put($url, $body, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/addons/qiniu/library/Qiniu/Storage/ArgusManager.php b/addons/qiniu/library/Qiniu/Storage/ArgusManager.php new file mode 100644 index 0000000..0889370 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Storage/ArgusManager.php @@ -0,0 +1,73 @@ +<?php +namespace Qiniu\Storage; + +use Qiniu\Auth; +use Qiniu\Config; +use Qiniu\Zone; +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +/** + * 主要涉及了鉴黄接口的实现,具体的接口规格可以参考 + * + * @link https://developer.qiniu.com/dora/manual/3674/kodo-product-introduction + */ +final class ArgusManager +{ + private $auth; + private $config; + + public function __construct(Auth $auth, Config $config = null) + { + $this->auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 视频鉴黄 + * + * @param $body body信息 + * @param $vid videoID + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/dora/manual/4258/video-pulp + */ + public function pulpVideo($body, $vid) + { + $path = '/v1/video/' . $vid; + + return $this->arPost($path, $body); + } + + private function getArHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::ARGUS_HOST; + } + + private function arPost($path, $body = null) + { + $url = $this->getArHost() . $path; + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json'); + $headers['Content-Type']='application/json'; + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + print($ret->statusCode); + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/addons/qiniu/library/Qiniu/Storage/BucketManager.php b/addons/qiniu/library/Qiniu/Storage/BucketManager.php new file mode 100644 index 0000000..0a2413d --- /dev/null +++ b/addons/qiniu/library/Qiniu/Storage/BucketManager.php @@ -0,0 +1,1107 @@ +<?php +namespace Qiniu\Storage; + +use Qiniu\Auth; +use Qiniu\Config; +use Qiniu\Zone; +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +/** + * 主要涉及了空间资源管理及批量操作接口的实现,具体的接口规格可以参考 + * + * @link https://developer.qiniu.com/kodo/api/1274/rs + */ +final class BucketManager +{ + private $auth; + private $config; + + public function __construct(Auth $auth, Config $config = null) + { + $this->auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 获取指定账号下所有的空间名。 + * + * @return string[] 包含所有空间名 + */ + public function buckets($shared = true) + { + $includeShared = "false"; + if ($shared === true) { + $includeShared = "true"; + } + return $this->rsGet('/buckets?shared=' . $includeShared); + } + + /** + * 列举空间,返回bucket列表 + * region 指定区域,global 指定全局空间。 + * 在指定了 region 参数时, + * 如果指定 global 为 true,那么忽略 region 参数指定的区域,返回所有区域的全局空间。 + * 如果没有指定 global 为 true,那么返回指定区域中非全局空间。 + * 在没有指定 region 参数时(包括指定为空""), + * 如果指定 global 为 true,那么返回所有区域的全局空间。 + * 如果没有指定 global 为 true,那么返回指定区域中所有的空间,包括全局空间。 + * 在指定了line为 true 时,只返回 Line 空间;否则,只返回非 Line 空间。 + * share 参数用于指定共享空间。 + */ + + public function listbuckets( + $region = null, + $line = 'false', + $shared = 'false' + ) { + $path = '/v3/buckets?region=' . $region . '&line=' . $line . '&shared=' . $shared; + $info = $this->ucPost($path); + return $info; + } + + /** + * 创建空间 + * + * @param $name 创建的空间名 + * @param $region 创建的区域,默认华东 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function createBucket($name, $region = 'z0') + { + $path = '/mkbucketv2/'.$name.'/region/' . $region; + return $this->rsPost($path, null); + } + + /** + * 删除空间 + * + * @param $name 删除的空间名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function deleteBucket($name) + { + $path = '/drop/'.$name; + return $this->rsPost($path, null); + } + + /** + * 获取指定空间绑定的所有的域名 + * + * @return string[] 包含所有空间域名 + */ + public function domains($bucket) + { + return $this->apiGet('/v6/domain/list?tbl=' . $bucket); + } + + /** + * 获取指定空间的相关信息 + * + * @return string[] 包含空间信息 + */ + public function bucketInfo($bucket) + { + $path = '/v2/bucketInfo?bucket=' . $bucket; + $info = $this->ucPost($path); + return $info; + } + + /** + * 获取指定zone的空间信息列表 + * 在Region 未指定且Global 不为 true 时(包含未指定的情况,下同),返回用户的所有空间。 + * 在指定了 region 参数且 global 不为 true 时,只列举非全局空间。 + * shared 不指定shared参数或指定shared为rw或false时,返回包含具有读写权限空间, + * 指定shared为rd或true时,返回包含具有读权限空间。 + * fs:如果为 true,会返回每个空间当前的文件数和存储量(实时数据)。 + * @return string[] 包含空间信息 + */ + public function bucketInfos($region = null, $shared = 'false', $fs = 'false') + { + $path = '/v2/bucketInfos?region=' . $region . '&shared=' . $shared . '&fs=' . $fs; + $info = $this->ucPost($path); + return $info; + } + + /** + * 获取空间绑定的域名列表 + * @return string[] 包含空间绑定的所有域名 + */ + + /** + * 列取空间的文件列表 + * + * @param $bucket 空间名 + * @param $prefix 列举前缀 + * @param $marker 列举标识符 + * @param $limit 单次列举个数限制 + * @param $delimiter 指定目录分隔符 + * + * @return array 包含文件信息的数组,类似:[ +* { +* "hash" => "<Hash string>", +* "key" => "<Key string>", +* "fsize" => "<file size>", +* "putTime" => "<file modify time>" +* }, +* ... +* ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html + */ + public function listFiles( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + $url = $this->getRsfHost() . '/list?' . http_build_query($query); + return $this->get($url); + } + + /** + * 列取空间的文件列表 + * + * @param $bucket 空间名 + * @param $prefix 列举前缀 + * @param $marker 列举标识符 + * @param $limit 单次列举个数限制 + * @param $delimiter 指定目录分隔符 + * @param $skipconfirm 是否跳过已删除条目的确认机制 + * + * @return array 包含文件信息的数组,类似:[ +* { +* "hash" => "<Hash string>", +* "key" => "<Key string>", +* "fsize" => "<file size>", +* "putTime" => "<file modify time>" +* }, +* ... +* ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html + */ + public function listFilesv2( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null, + $skipconfirm = true + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + \Qiniu\setWithoutEmpty($query, 'skipconfirm', $skipconfirm); + $path = '/v2/list?' . http_build_query($query); + $url = $this->getRsfHost() . $path; + $headers = $this->auth->authorization($url, null, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, null, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = explode("\n", $ret->body); + $pop = array_pop($r); + return array($r, null); + } + + /** + * 设置Referer防盗链 + * + * @param $bucket 空间名 + * @param $mode 0: 表示关闭Referer(使用此选项将会忽略以下参数并将恢复默认值); + * 1: 表示设置Referer白名单; 2:表示设置Referer黑名单 + * @param $norefer 0: 表示不允许空 Refer 访问; 1: 表示允许空 Refer 访问 + * @param $pattern 规则字符串, 当前允许格式分为三种: 一种为空主机头域名, + * 比如 foo.com; 一种是泛域名,比如 *.bar.com; 一种是完全通配符, + * 即一个 *; 多个规则之间用;隔开, 比如: foo.com;*.bar.com;sub.foo.com;*.sub.bar.com + * @param $source_enabled 源站是否支持,默认为0只给CDN配置, 设置为1表示开启源站防盗链 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + // public function referAntiLeech(){ + + // } + + /** + * 增加bucket生命规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,只能为 + * 字母、数字、下划线 + * @param $prefix 同一个 bucket 里面前缀不能重复 + * @param $delete_after_days 指定上传文件多少天后删除,指定为0表示不删除, + * 大于0表示多少天后删除,需大于 to_line_after_days + * @param $to_line_after_days 指定文件上传多少天后转低频存储。指定为0表示 + * 不转低频存储,小于0表示上传的文件立即变低频存储 + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function bucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days + ) { + $path = '/rules/add'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket生命规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、 + * 数字、下划线 + * @param $prefix 同一个 bucket 里面前缀不能重复 + * @param $delete_after_days 指定上传文件多少天后删除,指定为0表示不删除, + * 大于0表示多少天后删除,需大于 to_line_after_days + * @param $to_line_after_days 指定文件上传多少天后转低频存储。指定为0表示不 + * 转低频存储,小于0表示上传的文件立即变低频存储 + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function updateBucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days + ) { + $path = '/rules/update'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 获取bucket生命规则 + * + * @param $bucket 空间名 + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function getBucketLifecycleRules($bucket) + { + $path = '/rules/get?bucket=' . $bucket; + $info = $this->ucGet($path); + return $info; + } + + /** + * 删除bucket生命规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function deleteBucketLifecycleRule($bucket, $name) + { + $path = '/rules/delete'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 增加bucket事件通知规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param $prefix 同一个 bucket 里面前缀不能重复 + * @param $suffix 可选,文件配置的后缀 + * @param $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append, + * disable,enable,deleteMarkerCreate + * @param $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param $host 可选,通知请求的host + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function putBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/add'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($suffix) { + $params['suffix'] = $suffix; + } + if ($callbackURL) { + $params['callbackURL'] = $callbackURL; + } + if ($access_key) { + $params['access_key'] = $access_key; + } + if ($host) { + $params['host'] = $host; + } + $data = http_build_query($params); + if ($event) { + $eventpath = ""; + foreach ($event as $key => $value) { + $eventpath .= "&event=$value"; + } + $data .= $eventpath; + } + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket事件通知规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param $prefix 同一个 bucket 里面前缀不能重复 + * @param $suffix 可选,文件配置的后缀 + * @param $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable, + * enable,deleteMarkerCreate + * @param $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param $host 可选,通知请求的host + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function updateBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/update'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($suffix) { + $params['suffix'] = $suffix; + } + if ($event) { + $params['event'] = $event; + } + if ($callbackURL) { + $params['callbackURL'] = $callbackURL; + } + if ($access_key) { + $params['access_key'] = $access_key; + } + if ($host) { + $params['host'] = $host; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 获取bucket事件通知规则 + * + * @param $bucket 空间名 + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function getBucketEvents($bucket) + { + $path = '/events/get?bucket=' . $bucket; + $info = $this->ucGet($path); + return $info; + } + + /** + * 删除bucket事件通知规则 + * + * @param $bucket 空间名 + * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线 + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function deleteBucketEvent($bucket, $name) + { + $path = '/events/delete'; + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 设置bucket的跨域信息,最多允许设置10条跨域规则。 + * 对于同一个域名如果设置了多条规则,那么按顺序使用第一条匹配的规则去生成返回值。 + * 对于简单跨域请求,只匹配 Origin; + * allowed_orgin: 允许的域名。必填;支持通配符*;*表示全部匹配;只有第一个*生效; + * 需要设置"Scheme";大小写敏感。例如 + * 规则:http://*.abc.*.com 请求:"http://test.abc.test.com" 结果:不通过 + * 规则:"http://abc.com" 请求:"https://abc.com"/"abc.com" 结果:不通过 + * 规则:"abc.com" 请求:"http://abc.com" 结果:不通过 + * allowed_method: 允许的方法。必填;不支持通配符;大小写不敏感; + * allowed_header: 允许的header。选填;支持通配符*, + * 但只能是单独的*,表示允许全部header,其他*不生效; + * 空则不允许任何header;大小写不敏感; + * exposed_header: 暴露的header。选填;不支持通配符; + * X-Log, X-Reqid是默认会暴露的两个header; + * 其他的header如果没有设置,则不会暴露;大小写不敏感; + * max_age: 结果可以缓存的时间。选填;空则不缓存; + * allowed_credentials:该配置不支持设置,默认为true。 + * 备注:如果没有设置任何corsRules,那么默认允许所有的跨域请求 + */ + // public function putCorsRules(string $bucket, array $params) + // { + // $path = '/corsRules/set/' . $bucket; + // $data = json_encode($params); + // $info = $this->ucPost($path, $data); + // return $info; + // } + + /** + * 获取bucket的跨域信息 + * $bucket 空间名 + */ + public function getCorsRules($bucket) + { + $path = '/corsRules/get/' . $bucket; + $info = $this->ucGet($path); + return $info; + } + + /** + * 设置回源规则 + * 使用该API设置源站优先级高于/image设置的源站,即IO优先读取source接口设置的源站配置, + * 如果存在会忽略/image设置的源站 + * Bucket 空间名 + * Host(可选)回源Host + * RetryCodes(可选),镜像回源时源站返回Code可以重试,最多指定3个,当前只支持4xx错误码重试 + * SourceQiniuAK,SourceQiniuSK(可选)如果存在将在回源时对URL进行签名,客户源站可以验证 + * 以保证请求来自Qiniu服务器 + * Expires(可选) 签名过期时间,如果不设置默认为1小时 + * Addr 回源地址,不可重复。 + * Weight 权重,范围限制1-100,不填默认为1,回源时会根据所有源的权重值进行源站选择, + * 主备源会分开计算. + * Backup 是否备用回源,回源优先尝试主源 + */ + // public function putBucktSourceConfig(array $params) + // { + // $path = '/mirrorConfig/set'; + // $data = json_encode($params); + // $info = $this->ucPostV2($path, $data); + // return $info; + // } + + /** + * 获取空间回源配置 + */ + public function getBucktSourceConfig(array $params) + { + $path = '/mirrorConfig/get'; + $data = json_encode($params); + $info = $this->ucPostV2($path, $data); + return $info; + } + + /** + * 开关原图保护 + * mode 为1表示开启原图保护,0表示关闭 + */ + public function putBucketAccessStyleMode($bucket, $mode) + { + $path = '/accessMode/' . $bucket . '/mode/' . $mode; + $info = $this->ucPost($path, null); + return $info; + } + + /** + * 设置私有属性 + * private为0表示公开,为1表示私有 + */ + public function putBucketAccessMode($bucket, $private) + { + $path = '/bucket/' . $bucket . '/private/' . $private; + $info = $this->ucPost($path, null); + return $info; + } + + /** + * 设置referer防盗链 + * bucket=<BucketName>: bucket 名 + * mode=<AntiLeechMode>: + * 0: 表示关闭Referer(使用此选项将会忽略以下参数并将恢复默认值); + * 1: 表示设置Referer白名单; 2: 表示设置Referer黑名单 + * norefer=<NoRefer>: 0: 表示不允许空 Refer 访问; + * 1: 表示允许空 Refer 访问 + * pattern=<Pattern>: 规则字符串, 当前允许格式分为三种: + * 一种为空主机头域名, 比如 foo.com; + * 一种是泛域名, 比如 *.bar.com; 一种是完全通配符, 即一个 *; + * 多个规则之间用;隔开, 比如: foo.com;*.bar.com;sub.foo.com;*.sub.bar.com + * 空主机头域名可以是多级域名,比如 foo.bar.com。 + * 多个域名之间不允许夹带空白字符。 + * source_enabled=:1 + */ + public function putReferAntiLeech($bucket, $mode, $norefer, $pattern, $enabled = 1) + { + $path = "/referAntiLeech?bucket=$bucket&mode=$mode&norefer=$norefer&pattern=$pattern&source_enabled=$enabled"; + $info = $this->ucPost($path, null); + return $info; + } + + /** + * 设置Bucket的maxAge + * maxAge为0或者负数表示为默认值(31536000) + */ + public function putBucketMaxAge($bucket, $maxAge) + { + $path = '/maxAge?bucket=' . $bucket . '&maxAge=' . $maxAge; + $info = $this->ucPost($path, null); + return $info; + } + + /** + * 设置配额 + * <bucket>: 空间名称,不支持授权空间 + * <size>: 空间存储量配额,参数传入0或不传表示不更改当前配置,传入-1表示取消限额, + * 新创建的空间默认没有限额。 + * <count>: 空间文件数配额,参数含义同<size> + */ + public function putBucketQuota($bucket, $size, $count) + { + $path = '/setbucketquota/' . $bucket . '/size/' . $size . '/count/' . $count; + $info = $this->apiPost($path, null); + return $info; + } + + /** + * 获取配额 + * bucket 空间名称 + */ + public function getBucketQuota($bucket) + { + $path = '/getbucketquota/' . $bucket; + $info = $this->apiPost($path, null); + return $info; + } + + /** + * 获取资源的元信息,但不返回文件内容 + * + * @param $bucket 待获取信息资源所在的空间 + * @param $key 待获取资源的文件名 + * + * @return array 包含文件信息的数组,类似: +* [ +* "hash" => "<Hash string>", +* "key" => "<Key string>", +* "fsize" => <file size>, +* "putTime" => "<file modify time>" +* "fileType" => <file type> +* ] + * + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html + */ + public function stat($bucket, $key) + { + $path = '/stat/' . \Qiniu\entry($bucket, $key); + return $this->rsGet($path); + } + + /** + * 删除指定资源 + * + * @param $bucket 待删除资源所在的空间 + * @param $key 待删除资源的文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html + */ + public function delete($bucket, $key) + { + $path = '/delete/' . \Qiniu\entry($bucket, $key); + list(, $error) = $this->rsPost($path); + return $error; + } + + + /** + * 给资源进行重命名,本质为move操作。 + * + * @param $bucket 待操作资源所在空间 + * @param $oldname 待操作资源文件名 + * @param $newname 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function rename($bucket, $oldname, $newname) + { + return $this->move($bucket, $oldname, $bucket, $newname); + } + + /** + * 对资源进行复制。 + * + * @param $from_bucket 待操作资源所在空间 + * @param $from_key 待操作资源文件名 + * @param $to_bucket 目标资源空间名 + * @param $to_key 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html + */ + public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/copy/' . $from . '/' . $to; + if ($force === true) { + $path .= '/force/true'; + } + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 将资源从一个空间到另一个空间 + * + * @param $from_bucket 待操作资源所在空间 + * @param $from_key 待操作资源文件名 + * @param $to_bucket 目标资源空间名 + * @param $to_key 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/move.html + */ + public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/move/' . $from . '/' . $to; + if ($force) { + $path .= '/force/true'; + } + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 主动修改指定资源的文件元信息 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $mime 待操作文件目标mimeType + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/chgm.html + */ + public function changeMime($bucket, $key, $mime) + { + $resource = \Qiniu\entry($bucket, $key); + $encode_mime = \Qiniu\base64_urlSafeEncode($mime); + $path = '/chgm/' . $resource . '/mime/' . $encode_mime; + list(, $error) = $this->rsPost($path); + return $error; + } + + + /** + * 修改指定资源的存储类型 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $fileType 待操作文件目标文件类型 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/kodo/api/3710/modify-the-file-type + */ + public function changeType($bucket, $key, $fileType) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chtype/' . $resource . '/type/' . $fileType; + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 修改文件的存储状态,即禁用状态和启用状态间的的互相转换 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $status 待操作文件目标文件类型 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status + */ + public function changeStatus($bucket, $key, $status) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chstatus/' . $resource . '/status/' . $status; + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 从指定URL抓取资源,并将该资源存储到指定空间中 + * + * @param $url 指定的URL + * @param $bucket 目标资源空间 + * @param $key 目标资源文件名 + * + * @return array 包含已拉取的文件信息。 + * 成功时: [ + * [ + * "hash" => "<Hash string>", + * "key" => "<Key string>" + * ], + * null + * ] + * + * 失败时: [ + * null, + * Qiniu/Http/Error + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/fetch.html + */ + public function fetch($url, $bucket, $key = null) + { + + $resource = \Qiniu\base64_urlSafeEncode($url); + $to = \Qiniu\entry($bucket, $key); + $path = '/fetch/' . $resource . '/to/' . $to; + + $ak = $this->auth->getAccessKey(); + $ioHost = $this->config->getIovipHost($ak, $bucket); + + $url = $ioHost . $path; + return $this->post($url, null); + } + + /** + * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源 + * + * @param $bucket 待获取资源所在的空间 + * @param $key 代获取资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/prefetch.html + */ + public function prefetch($bucket, $key) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/prefetch/' . $resource; + + $ak = $this->auth->getAccessKey(); + $ioHost = $this->config->getIovipHost($ak, $bucket); + + $url = $ioHost . $path; + list(, $error) = $this->post($url, null); + return $error; + } + + /** + * 在单次请求中进行多个资源管理操作 + * + * @param $operations 资源管理操作数组 + * + * @return array 每个资源的处理情况,结果类似: + * [ + * { "code" => <HttpCode int>, "data" => <Data> }, + * { "code" => <HttpCode int> }, + * { "code" => <HttpCode int> }, + * { "code" => <HttpCode int> }, + * { "code" => <HttpCode int>, "data" => { "error": "<ErrorMessage string>" } }, + * ... + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html + */ + public function batch($operations) + { + $params = 'op=' . implode('&op=', $operations); + return $this->rsPost('/batch', $params); + } + + /** + * 设置文件的生命周期 + * + * @param $bucket 设置文件生命周期文件所在的空间 + * @param $key 设置文件生命周期文件的文件名 + * @param $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期 + * + * @return Mixed + * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle + */ + public function deleteAfterDays($bucket, $key, $days) + { + $entry = \Qiniu\entry($bucket, $key); + $path = "/deleteAfterDays/$entry/$days"; + list(, $error) = $this->rsPost($path); + return $error; + } + + private function getRsfHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::RSF_HOST; + } + + private function getRsHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::RS_HOST; + } + + private function getApiHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::API_HOST; + } + + private function getUcHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::UC_HOST; + } + + private function rsPost($path, $body = null) + { + $url = $this->getRsHost() . $path; + return $this->post($url, $body); + } + + private function apiPost($path, $body = null) + { + $url = $this->getApiHost() . $path; + return $this->post($url, $body); + } + + private function ucPost($path, $body = null) + { + $url = $this->getUcHost() . $path; + return $this->post($url, $body); + } + + private function ucGet($path) + { + $url = $this->getUcHost() . $path; + return $this->get($url); + } + + private function apiGet($path) + { + $url = $this->getApiHost() . $path; + return $this->get($url); + } + + private function rsGet($path) + { + $url = $this->getRsHost() . $path; + return $this->get($url); + } + + private function get($url) + { + $headers = $this->auth->authorization($url); + $ret = Client::get($url, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + private function ucPostV2($path, $body) + { + $url = $this->getUcHost() . $path; + return $this->postV2($url, $body); + } + + private function postV2($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json'); + $headers["Content-Type"] = 'application/json'; + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchRename($bucket, $key_pairs, $force) + { + return self::buildBatchMove($bucket, $key_pairs, $bucket, $force); + } + + + public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchDelete($bucket, $keys) + { + return self::oneKeyBatch('/delete', $bucket, $keys); + } + + + public static function buildBatchStat($bucket, $keys) + { + return self::oneKeyBatch('/stat', $bucket, $keys); + } + + public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs) + { + $data = array(); + foreach ($key_day_pairs as $key => $day) { + array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day); + } + return $data; + } + + public static function buildBatchChangeMime($bucket, $key_mime_pairs) + { + $data = array(); + foreach ($key_mime_pairs as $key => $mime) { + array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime)); + } + return $data; + } + + public static function buildBatchChangeType($bucket, $key_type_pairs) + { + $data = array(); + foreach ($key_type_pairs as $key => $type) { + array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type); + } + return $data; + } + + private static function oneKeyBatch($operation, $bucket, $keys) + { + $data = array(); + foreach ($keys as $key) { + array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key)); + } + return $data; + } + + private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force) + { + if ($target_bucket === null) { + $target_bucket = $source_bucket; + } + $data = array(); + $forceOp = "false"; + if ($force) { + $forceOp = "true"; + } + foreach ($key_pairs as $from_key => $to_key) { + $from = \Qiniu\entry($source_bucket, $from_key); + $to = \Qiniu\entry($target_bucket, $to_key); + array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp); + } + return $data; + } +} diff --git a/addons/qiniu/library/Qiniu/Storage/FormUploader.php b/addons/qiniu/library/Qiniu/Storage/FormUploader.php new file mode 100644 index 0000000..a415073 --- /dev/null +++ b/addons/qiniu/library/Qiniu/Storage/FormUploader.php @@ -0,0 +1,122 @@ +<?php + +namespace Qiniu\Storage; + +use Qiniu\Config; +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +final class FormUploader +{ + + /** + * 上传二进制流到七牛, 内部使用 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param resource $data 上传二进制流 + * @param Config $config 上传配置 + * @param array $params 自定义变量,规格参考 http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "<Hash string>", + * "key" => "<Key string>" + * ] + */ + public static function put($upToken, $key, $data, $config, $params, $mime, $fname) + { + $fields = array('token' => $upToken); + if ($key === null) { + } else { + $fields['key'] = $key; + } + + //enable crc32 check by default + $fields['crc32'] = \Qiniu\crc32_data($data); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $config->getUpHost($accessKey, $bucket); + + $response = Client::multipartPost($upHost, $fields, 'file', $fname, $data, $mime); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + /** + * 上传文件到七牛,内部使用 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param Config $config 上传配置 + * @param array $params 自定义变量,规格参考 http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "<Hash string>", + * "key" => "<Key string>" + * ] + */ + public static function putFile($upToken, $key, $filePath, $config, $params, $mime) + { + $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime)); + if ($key !== null) { + $fields['key'] = $key; + } + + $fields['crc32'] = \Qiniu\crc32_file($filePath); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + $fields['key'] = $key; + $headers = array('Content-Type' => 'multipart/form-data'); + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $config->getUpHost($accessKey, $bucket); + + $response = Client::post($upHost, $fields, $headers); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + private static function createFile($filename, $mime) + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($filename, $mime); + } + + // Use the old style if using an older version of PHP + $value = "@{$filename}"; + if (!empty($mime)) { + $value .= ';type=' . $mime; + } + + return $value; + } +} diff --git a/addons/qiniu/library/Qiniu/Storage/ResumeUploader.php b/addons/qiniu/library/Qiniu/Storage/ResumeUploader.php new file mode 100644 index 0000000..63045bf --- /dev/null +++ b/addons/qiniu/library/Qiniu/Storage/ResumeUploader.php @@ -0,0 +1,210 @@ +<?php + +namespace Qiniu\Storage; + +use Qiniu\Config; +use Qiniu\Http\Client; +use Qiniu\Http\Error; + +/** + * 断点续上传类, 该类主要实现了断点续上传中的分块上传, + * 以及相应地创建块和创建文件过程. + * + * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkblk.html + * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkfile.html + */ +final class ResumeUploader +{ + private $upToken; + private $key; + private $inputStream; + private $size; + private $params; + private $mime; + private $contexts; + private $host; + private $currentUrl; + private $config; + + /** + * 上传二进制流到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param resource $inputStream 上传二进制流 + * @param int $size 上传流的大小 + * @param array $params 自定义变量 + * @param string $mime 上传数据的mimeType + * + * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + */ + public function __construct( + $upToken, + $key, + $inputStream, + $size, + $params = null, + $mime = '', + $config = null + ) { + + $this->upToken = $upToken; + $this->key = $key; + $this->inputStream = $inputStream; + $this->size = $size; + $this->params = $params; + $this->mime = $mime ? $mime : 'application/octet-stream'; + $this->contexts = array(); + $this->config = $config ? $config : new Config(); + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $this->config->getUpHost($accessKey, $bucket); + if ($err != null) { + throw new \Exception($err->message(), 1); + } + $this->host = $upHost; + } + + /** + * 上传操作 + */ + public function upload($fname) + { + $uploaded = 0; + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $crc = \Qiniu\crc32_data($data); + $response = $this->makeBlock($data, $blockSize); + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + + $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket); + $this->host = $upHostBackup; + } + if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + $response = $this->makeBlock($data, $blockSize); + $ret = $response->json(); + } + + if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + return array(null, new Error($this->currentUrl, $response)); + } + array_push($this->contexts, $ret['ctx']); + $uploaded += $blockSize; + } + return $this->makeFile($fname); + } + + public function uploadChunk($index, $file, $size) + { + $blockSize = $this->size; + $data = fread($this->inputStream, $size); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $crc = \Qiniu\crc32_data($data); + $response = $this->makeBlock($data, $blockSize); + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + + $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket); + $this->host = $upHostBackup; + } + if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + $response = $this->makeBlock($data, $blockSize); + $ret = $response->json(); + } + + if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + return array(null, new Error($this->currentUrl, $response)); + } + array_push($this->contexts, $ret['ctx']); + return $ret; + } + + public function setContexts($contexts) + { + $this->contexts = is_array($contexts) ? $contexts : explode(',', $contexts); + return $this; + } + + /** + * 创建块 + */ + private function makeBlock($block, $blockSize) + { + $url = $this->host . '/mkblk/' . $blockSize; + return $this->post($url, $block); + } + + private function fileUrl($fname) + { + $url = $this->host . '/mkfile/' . $this->size; + $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime); + if ($this->key != null) { + $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key); + } + $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname); + if (!empty($this->params)) { + foreach ($this->params as $key => $value) { + $val = \Qiniu\base64_urlSafeEncode($value); + $url .= "/$key/$val"; + } + } + return $url; + } + + /** + * 创建文件 + */ + public function makeFile($fname) + { + $url = $this->fileUrl($fname); + $body = implode(',', $this->contexts); + $response = $this->post($url, $body); + if ($response->needRetry()) { + $response = $this->post($url, $body); + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function post($url, $data) + { + $this->currentUrl = $url; + $headers = array('Authorization' => 'UpToken ' . $this->upToken); + return Client::post($url, $data, $headers); + } + + private function blockSize($uploaded) + { + if ($this->size < $uploaded + Config::BLOCK_SIZE) { + return $this->size - $uploaded; + } + return Config::BLOCK_SIZE; + } +} diff --git a/addons/qiniu/library/Qiniu/Storage/UploadManager.php b/addons/qiniu/library/Qiniu/Storage/UploadManager.php new file mode 100644 index 0000000..c29683b --- /dev/null +++ b/addons/qiniu/library/Qiniu/Storage/UploadManager.php @@ -0,0 +1,106 @@ +<?php + +namespace Qiniu\Storage; + +use Qiniu\Config; +use Qiniu\Http\HttpClient; +use Qiniu\Storage\ResumeUploader; +use Qiniu\Storage\FormUploader; + +/** + * 主要涉及了资源上传接口的实现 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/up/ + */ +final class UploadManager +{ + private $config; + + public function __construct(Config $config = null) + { + if ($config === null) { + $config = new Config(); + } + $this->config = $config; + } + + /** + * 上传二进制流到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param resource $data 上传二进制流 + * @param array $params 自定义变量,规格参考 http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * @param bool $checkCrc 是否校验crc32 + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "<Hash string>", + * "key" => "<Key string>" + * ] + */ + public function put($upToken, $key, $data, $params = null, $mime = 'application/octet-stream', $fname = "default_filename") + { + $params = self::trimParams($params); + return FormUploader::put($upToken, $key, $data, $this->config, $params, $mime, $fname); + } + + + /** + * 上传文件到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param array $params 自定义变量,规格参考 http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * @param bool $checkCrc 是否校验crc32 + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "<Hash string>", + * "key" => "<Key string>" + * ] + */ + public function putFile($upToken, $key, $filePath, $params = null, $mime = 'application/octet-stream', $checkCrc = false) + { + $file = fopen($filePath, 'rb'); + if ($file === false) { + throw new \Exception("file can not open", 1); + } + $params = self::trimParams($params); + $stat = fstat($file); + $size = $stat['size']; + //不满足分片上传条件 + if ($size <= Config::BLOCK_SIZE) { + $data = fread($file, $size); + fclose($file); + if ($data === false) { + throw new \Exception("file can not read", 1); + } + return FormUploader::put($upToken, $key, $data, $this->config, $params, $mime, basename($filePath)); + } else { + $up = new ResumeUploader($upToken, $key, $file, $size, $params, $mime, $this->config); + $ret = $up->upload(basename($filePath)); + fclose($file); + return $ret; + } + } + + public static function trimParams($params) + { + if ($params === null) { + return null; + } + $ret = array(); + foreach ($params as $k => $v) { + $pos1 = strpos($k, 'x:'); + $pos2 = strpos($k, 'x-qn-meta-'); + if (($pos1 === 0 || $pos2 === 0) && !empty($v)) { + $ret[$k] = $v; + } + } + return $ret; + } +} diff --git a/addons/qiniu/library/Qiniu/Zone.php b/addons/qiniu/library/Qiniu/Zone.php new file mode 100644 index 0000000..7af0e3f --- /dev/null +++ b/addons/qiniu/library/Qiniu/Zone.php @@ -0,0 +1,47 @@ +<?php +namespace Qiniu; + +use Qiniu\Region; + +class Zone extends Region +{ + public static function zonez0() + { + return parent::regionHuadong(); + } + + public static function zonez1() + { + return parent::regionHuabei(); + } + + public static function zonez2() + { + return parent::regionHuanan(); + } + + public static function zoneAs0() + { + return parent::regionSingapore(); + } + + public static function zoneNa0() + { + return parent::regionNorthAmerica(); + } + + public static function qvmZonez0() + { + return parent::qvmRegionHuadong(); + } + + public static function qvmZonez1() + { + return parent::qvmRegionHuabei(); + } + + public static function queryZone($ak, $bucket) + { + return parent::queryRegion($ak, $bucket); + } +} diff --git a/addons/qiniu/library/Qiniu/functions.php b/addons/qiniu/library/Qiniu/functions.php new file mode 100644 index 0000000..5831a51 --- /dev/null +++ b/addons/qiniu/library/Qiniu/functions.php @@ -0,0 +1,264 @@ +<?php + +namespace Qiniu; + +use Qiniu\Config; + +if (!defined('QINIU_FUNCTIONS_VERSION')) { + define('QINIU_FUNCTIONS_VERSION', Config::SDK_VER); + + /** + * 计算文件的crc32检验码: + * + * @param $file string 待计算校验码的文件路径 + * + * @return string 文件内容的crc32校验码 + */ + function crc32_file($file) + { + $hash = hash_file('crc32b', $file); + $array = unpack('N', pack('H*', $hash)); + return sprintf('%u', $array[1]); + } + + /** + * 计算输入流的crc32检验码 + * + * @param $data 待计算校验码的字符串 + * + * @return string 输入字符串的crc32校验码 + */ + function crc32_data($data) + { + $hash = hash('crc32b', $data); + $array = unpack('N', pack('H*', $hash)); + return sprintf('%u', $array[1]); + } + + /** + * 对提供的数据进行urlsafe的base64编码。 + * + * @param string $data 待编码的数据,一般为字符串 + * + * @return string 编码后的字符串 + * @link http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64 + */ + function base64_urlSafeEncode($data) + { + $find = array('+', '/'); + $replace = array('-', '_'); + return str_replace($find, $replace, base64_encode($data)); + } + + /** + * 对提供的urlsafe的base64编码的数据进行解码 + * + * @param string $str 待解码的数据,一般为字符串 + * + * @return string 解码后的字符串 + */ + function base64_urlSafeDecode($str) + { + $find = array('-', '_'); + $replace = array('+', '/'); + return base64_decode(str_replace($find, $replace, $str)); + } + + /** + * Wrapper for JSON decode that implements error detection with helpful + * error messages. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be parsed. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + function json_decode($json, $assoc = false, $depth = 512) + { + static $jsonErrors = array( + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + if (empty($json)) { + return null; + } + $data = \json_decode($json, $assoc, $depth); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * 计算七牛API中的数据格式 + * + * @param $bucket 待操作的空间名 + * @param $key 待操作的文件名 + * + * @return string 符合七牛API规格的数据格式 + * @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html + */ + function entry($bucket, $key) + { + $en = $bucket; + if (!empty($key)) { + $en = $bucket . ':' . $key; + } + return base64_urlSafeEncode($en); + } + + /** + * array 辅助方法,无值时不set + * + * @param $array 待操作array + * @param $key key + * @param $value value 为null时 不设置 + * + * @return array 原来的array,便于连续操作 + */ + function setWithoutEmpty(&$array, $key, $value) + { + if (!empty($value)) { + $array[$key] = $value; + } + return $array; + } + + /** + * 缩略图链接拼接 + * + * @param string $url 图片链接 + * @param int $mode 缩略模式 + * @param int $width 宽度 + * @param int $height 长度 + * @param string $format 输出类型 + * @param int $quality 图片质量 + * @param int $interlace 是否支持渐进显示 + * @param int $ignoreError 忽略结果 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $quality = null, + $interlace = null, + $ignoreError = 1 + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args()); + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args()); + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren <sherlock_ren@icloud.com> + */ + function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args()); + } + + /** + * 从uptoken解析accessKey和bucket + * + * @param $upToken + * @return array(ak,bucket,err=null) + */ + function explodeUpToken($upToken) + { + $items = explode(':', $upToken); + if (count($items) != 3) { + return array(null, null, "invalid uptoken"); + } + $accessKey = $items[0]; + $putPolicy = json_decode(base64_urlSafeDecode($items[2])); + $scope = $putPolicy->scope; + $scopeItems = explode(':', $scope); + $bucket = $scopeItems[0]; + return array($accessKey, $bucket, null); + } +} diff --git a/application/extra/addons.php b/application/extra/addons.php index 54dda7e..1eaea32 100644 --- a/application/extra/addons.php +++ b/application/extra/addons.php @@ -6,10 +6,17 @@ return [ 'config_init' => [ 'nkeditor', ], - 'upgrade' => [ - 'shopro', + 'upload_config_init' => [ + 'qiniu', + ], + 'upload_delete' => [ + 'qiniu', ], 'app_init' => [ + 'qiniu', + 'shopro', + ], + 'upgrade' => [ 'shopro', ], ], diff --git a/public/assets/js/addons.js b/public/assets/js/addons.js index e2e5682..b9b910f 100644 --- a/public/assets/js/addons.js +++ b/public/assets/js/addons.js @@ -284,4 +284,140 @@ require(['form'], function (Form) { } }); +//修改上传的接口调用 +require(['upload'], function (Upload) { + + //初始化中完成判断 + Upload.events.onInit = function () { + //如果上传接口不是七牛云,则不处理 + if (this.options.url !== Config.upload.uploadurl) { + return; + } + var _success = this.options.success; + + $.extend(this.options, { + chunkSuccess: function (chunk, file, response) { + this.contexts = this.contexts ? this.contexts : []; + this.contexts.push(typeof response.ctx !== 'undefined' ? response.ctx : response.data.ctx); + }, + chunksUploaded: function (file, done) { + var that = this; + Fast.api.ajax({ + url: "/addons/qiniu/index/upload", + data: { + action: 'merge', + filesize: file.size, + filename: file.name, + chunkid: file.upload.uuid, + chunkcount: file.upload.totalChunkCount, + width: file.width || 0, + height: file.height || 0, + type: file.type, + qiniutoken: Config.upload.multipart.qiniutoken, + contexts: this.contexts + }, + }, function (data, ret) { + done(JSON.stringify(ret)); + return false; + }, function (data, ret) { + file.accepted = false; + that._errorProcessing([file], ret.msg); + return false; + }); + + }, + }); + + //先移除已有的事件 + this.off("success", _success).on("success", function (file, response) { + var ret = {code: 0, msg: response}; + try { + ret = typeof response === 'string' ? JSON.parse(response) : response; + if (file.xhr.status === 200) { + if (typeof ret.key !== 'undefined') { + ret = {code: 1, msg: "", data: {url: '/' + ret.key, hash: ret.hash}}; + } + Fast.api.ajax({ + url: "/addons/qiniu/index/notify", + data: {name: file.name, url: ret.data.url, hash: ret.data.hash, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, qiniutoken: Config.upload.multipart.qiniutoken} + }, function () { + return false; + }, function () { + return false; + }); + } + } catch (e) { + console.error(e); + } + _success.call(this, file, ret); + }); + + //如果是直传模式 + if (Config.upload.uploadmode === 'client') { + var _url = this.options.url; + + //分片上传时URL链接不同 + this.options.url = function (files) { + this.options.headers = {"Authorization": "UpToken " + Config.upload.multipart.qiniutoken}; + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + if (!chunk) { + return Config.upload.uploadurl + '/mkfile/' + files[0].size; + } else { + return Config.upload.uploadurl + '/mkblk/' + chunk.dataBlock.data.size; + } + } + return _url; + }; + + this.options.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, + chunkfilesize: chunk.dataBlock.data.size, + width: chunk.file.width || 0, + height: chunk.file.height || 0, + type: chunk.file.type, + }); + } else { + var retParams = $.extend({}, params); + //七牛云直传使用的是token参数 + retParams.token = retParams.qiniutoken; + delete retParams.qiniutoken; + return retParams; + } + }; + + //分片上传时需要变更提交的内容 + this.on("sending", function (file, xhr, formData) { + 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); + } + }; + } + }); + } + }; + +}); + }); \ No newline at end of file -- libgit2 0.24.0